track hashtags usage
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
98b7b701fd
commit
bdd5509b79
7 changed files with 200 additions and 5 deletions
76
lib/pleroma/user_hashtag.ex
Normal file
76
lib/pleroma/user_hashtag.ex
Normal file
|
@ -0,0 +1,76 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.UserHashtag do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.Hashtag
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserHashtag
|
||||
|
||||
schema "user_hashtags" do
|
||||
field(:use_count, :integer, default: 0)
|
||||
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:hashtag, Hashtag)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(%UserHashtag{} = user_hashtag, params \\ %{}) do
|
||||
user_hashtag
|
||||
|> cast(params, [:user_id, :hashtag_id, :use_count])
|
||||
|> validate_required([:user_id, :hashtag_id, :use_count])
|
||||
end
|
||||
|
||||
def get(%User{} = user, %Hashtag{} = hashtag) do
|
||||
Repo.get_by(UserHashtag, user_id: user.id, hashtag_id: hashtag.id)
|
||||
end
|
||||
|
||||
def increase_use_count(%User{} = user, %Hashtag{} = hashtag) do
|
||||
case get(user, hashtag) do
|
||||
%UserHashtag{use_count: use_count} = user_hashtag ->
|
||||
user_hashtag
|
||||
|> changeset(%{use_count: use_count + 1})
|
||||
|> Repo.update!()
|
||||
|
||||
nil ->
|
||||
%UserHashtag{}
|
||||
|> changeset(%{user_id: user.id, hashtag_id: hashtag.id, use_count: 1})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
||||
|
||||
def decrease_use_count(%User{} = user, %Hashtag{} = hashtag) do
|
||||
case get(user, hashtag) do
|
||||
%UserHashtag{use_count: use_count} = user_hashtag ->
|
||||
user_hashtag
|
||||
|> changeset(%{use_count: max(use_count - 1, 0)})
|
||||
|> Repo.update!()
|
||||
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def all() do
|
||||
Hashtag
|
||||
|> group_by([h], [h.id, h.name])
|
||||
|> join(:inner, [h], uh in UserHashtag, on: h.id == uh.hashtag_id)
|
||||
|> select([h, uh], %{hashtag: h, use_count: sum(uh.use_count)})
|
||||
|> order_by([_, uh], desc: sum(uh.use_count))
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def for_user(%User{id: user_id}) do
|
||||
UserHashtag
|
||||
|> where(user_id: ^user_id)
|
||||
|> order_by(desc: :use_count)
|
||||
|> Repo.all()
|
||||
|> Repo.preload(:hashtag)
|
||||
end
|
||||
end
|
|
@ -111,6 +111,15 @@ defp increase_quotes_count_if_quote(%{
|
|||
|
||||
defp increase_quotes_count_if_quote(_create_data), do: :noop
|
||||
|
||||
defp increase_hashtag_count(actor, %{object: %{hashtags: hashtags} = object})
|
||||
when is_list(hashtags) do
|
||||
if is_public?(object) do
|
||||
Enum.each(hashtags, fn hashtag -> Pleroma.UserHashtag.increase_use_count(actor, hashtag) end)
|
||||
end
|
||||
end
|
||||
|
||||
defp increase_hashtag_count(_, _), do: :noop
|
||||
|
||||
@object_types ~w[ChatMessage Question Answer Audio Video Image Event Article Note Page]
|
||||
@impl true
|
||||
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
||||
|
@ -318,6 +327,7 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param
|
|||
{:fake, false, activity} <- {:fake, fake, activity},
|
||||
_ <- increase_replies_count_if_reply(create_data),
|
||||
_ <- increase_quotes_count_if_quote(create_data),
|
||||
_ <- increase_hashtag_count(actor, activity),
|
||||
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
|
||||
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
||||
{:ok, _actor} <- update_last_status_at_if_public(actor, activity),
|
||||
|
|
|
@ -21,6 +21,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Push
|
||||
alias Pleroma.Web.Streamer
|
||||
alias Pleroma.Workers.EventReminderWorker
|
||||
|
@ -292,8 +293,9 @@ def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do
|
|||
@impl true
|
||||
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
|
||||
deleted_object =
|
||||
Object.normalize(deleted_object, fetch: false) ||
|
||||
User.get_cached_by_ap_id(deleted_object)
|
||||
(Object.normalize(deleted_object, fetch: false) ||
|
||||
User.get_cached_by_ap_id(deleted_object))
|
||||
|> Repo.preload(:hashtags)
|
||||
|
||||
result =
|
||||
case deleted_object do
|
||||
|
@ -313,6 +315,10 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object,
|
|||
Object.decrease_quotes_count(quote_url)
|
||||
end
|
||||
|
||||
if Visibility.is_public?(deleted_object) do
|
||||
Enum.each(deleted_object.hashtags, fn hashtag -> Pleroma.UserHashtag.decrease_use_count(user, hashtag) end)
|
||||
end
|
||||
|
||||
MessageReference.delete_for_object(deleted_object)
|
||||
|
||||
ap_streamer().stream_out(object)
|
||||
|
|
|
@ -141,10 +141,61 @@ def birthdays_operation do
|
|||
}
|
||||
end
|
||||
|
||||
defp id_param do
|
||||
def hashtags_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve account information"],
|
||||
summary: "Hashtags",
|
||||
description: "Most used hashtags",
|
||||
operationId: "PleromaAPI.AccountController.hashtags",
|
||||
parameters: [id_param()],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"Array of Hashtags",
|
||||
"application/json",
|
||||
array_of_hashtags()
|
||||
),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def all_hashtags_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve account information"],
|
||||
summary: "Hashtags",
|
||||
description: "Most used hashtags",
|
||||
operationId: "PleromaAPI.AccountController.all_hashtags",
|
||||
parameters: [],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"Array of Hashtags",
|
||||
"application/json",
|
||||
array_of_hashtags()
|
||||
),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp id_param() do
|
||||
Operation.parameter(:id, :path, FlakeID.schema(), "Account ID",
|
||||
example: "9umDrYheeY451cQnEe",
|
||||
required: true
|
||||
)
|
||||
end
|
||||
|
||||
defp array_of_hashtags() do
|
||||
%Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
name: %Schema{type: :string},
|
||||
use_count: %Schema{type: :integer}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
]
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserHashtag
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
@ -48,7 +49,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
|
||||
when action == :endorsements
|
||||
when action in [:endorsements, :hashtags, :all_hashtags]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
@ -60,7 +61,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
|
||||
plug(
|
||||
:assign_account_by_id
|
||||
when action in [:favourites, :endorsements, :subscribe, :unsubscribe]
|
||||
when action in [:favourites, :endorsements, :subscribe, :unsubscribe, :hashtags]
|
||||
)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation
|
||||
|
@ -156,4 +157,34 @@ def birthdays(%{assigns: %{user: %User{} = user}} = conn, %{day: day, month: mon
|
|||
as: :user
|
||||
)
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/pleroma/accounts/:id/hashtags"
|
||||
def hashtags(%{assigns: %{account: user}} = conn, _params) do
|
||||
with hashtags <- UserHashtag.for_user(user) do
|
||||
conn
|
||||
|> json(
|
||||
hashtags
|
||||
|> Enum.map(fn %{hashtag: hashtag, use_count: use_count} ->
|
||||
%{name: hashtag.name, use_count: use_count}
|
||||
end)
|
||||
)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||
end
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/pleroma/accounts/hashtags"
|
||||
def all_hashtags(conn, _params) do
|
||||
with hashtags <- UserHashtag.all() do
|
||||
conn
|
||||
|> json(
|
||||
hashtags
|
||||
|> Enum.map(fn %{hashtag: hashtag, use_count: use_count} ->
|
||||
%{name: hashtag.name, use_count: use_count}
|
||||
end)
|
||||
)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -687,6 +687,8 @@ defmodule Pleroma.Web.Router do
|
|||
pipe_through(:api)
|
||||
get("/accounts/:id/favourites", AccountController, :favourites)
|
||||
get("/accounts/:id/endorsements", AccountController, :endorsements)
|
||||
get("/accounts/:id/hashtags", AccountController, :hashtags)
|
||||
get("/accounts/hashtags", AccountController, :all_hashtags)
|
||||
|
||||
get("/statuses/:id/quotes", StatusController, :quotes)
|
||||
end
|
||||
|
|
19
priv/repo/migrations/20230111000000_create_user_hashtags.exs
Normal file
19
priv/repo/migrations/20230111000000_create_user_hashtags.exs
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Repo.Migrations.CreateUserHashtags do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create_if_not_exists table(:user_hashtags) do
|
||||
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||
add(:hashtag_id, references(:hashtags, on_delete: :delete_all))
|
||||
add(:use_count, :integer, default: 0)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create_if_not_exists(unique_index(:user_hashtags, [:user_id, :hashtag_id]))
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue