X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;ds=sidebyside;f=lib%2Fpleroma%2Fuser.ex;h=eb4218ebee8d5d0f4900182d10fafb895829eeba;hb=79e44042bc08cf69274008e408cac912ae693afe;hp=8edf1c73065979dc5fc075ed5e3875715d8e12ff;hpb=3aee8bb67bd3f78ad4e638ea056bbd7b3ff4b7e7;p=akkoma diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 8edf1c730..eb4218ebe 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -35,7 +35,7 @@ defmodule Pleroma.User do field(:avatar, :map) field(:local, :boolean, default: true) field(:follower_address, :string) - field(:search_distance, :float, virtual: true) + field(:search_rank, :float, virtual: true) field(:tags, {:array, :string}, default: []) field(:last_refreshed_at, :naive_datetime) has_many(:notifications, Notification) @@ -44,20 +44,28 @@ defmodule Pleroma.User do timestamps() end - def auth_active?(%User{} = user) do - (user.info && !user.info.confirmation_pending) || - !Pleroma.Config.get([:instance, :account_activation_required]) - end + def auth_active?(%User{local: false}), do: true + + def auth_active?(%User{info: %User.Info{confirmation_pending: false}}), do: true + + def auth_active?(%User{info: %User.Info{confirmation_pending: true}}), + do: !Pleroma.Config.get([:instance, :account_activation_required]) + + def auth_active?(_), do: false - def remote_or_auth_active?(%User{} = user), do: !user.local || auth_active?(user) + def visible_for?(user, for_user \\ nil) - def visible_for?(%User{} = user, for_user \\ nil) do - User.remote_or_auth_active?(user) || (for_user && for_user.id == user.id) || - User.superuser?(for_user) + def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true + + def visible_for?(%User{} = user, for_user) do + auth_active?(user) || superuser?(for_user) end - def superuser?(nil), do: false - def superuser?(%User{} = user), do: user.info && User.Info.superuser?(user.info) + def visible_for?(_, _), do: false + + def superuser?(%User{local: true, info: %User.Info{is_admin: true}}), do: true + def superuser?(%User{local: true, info: %User.Info{is_moderator: true}}), do: true + def superuser?(_), do: false def avatar_url(user) do case user.avatar do @@ -239,10 +247,7 @@ defmodule Pleroma.User do ) |> Repo.all() - autofollowed_users - |> Enum.reduce({:ok, user}, fn other_user, {:ok, user} -> - follow(user, other_user) - end) + follow_all(user, autofollowed_users) end @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)" @@ -299,6 +304,25 @@ defmodule Pleroma.User do end end + @doc "A mass follow for local users. Ignores blocks and has no side effects" + @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()} + def follow_all(follower, followeds) do + following = + (follower.following ++ Enum.map(followeds, fn %{follower_address: fa} -> fa end)) + |> Enum.uniq() + + {:ok, follower} = + follower + |> follow_changeset(%{following: following}) + |> update_and_set_cache + + Enum.each(followeds, fn followed -> + update_follower_count(followed) + end) + + {:ok, follower} + end + def follow(%User{} = follower, %User{info: info} = followed) do user_config = Application.get_env(:pleroma, :user) deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked) @@ -463,7 +487,7 @@ defmodule Pleroma.User do end end - def get_followers_query(%User{id: id, follower_address: follower_address}) do + def get_followers_query(%User{id: id, follower_address: follower_address}, nil) do from( u in User, where: fragment("? <@ ?", ^[follower_address], u.following), @@ -471,13 +495,29 @@ defmodule Pleroma.User do ) end - def get_followers(user) do - q = get_followers_query(user) + def get_followers_query(user, page) do + from( + u in get_followers_query(user, nil), + limit: 20, + offset: ^((page - 1) * 20) + ) + end + + def get_followers_query(user), do: get_followers_query(user, nil) + + def get_followers(user, page \\ nil) do + q = get_followers_query(user, page) {:ok, Repo.all(q)} end - def get_friends_query(%User{id: id, following: following}) do + def get_followers_ids(user, page \\ nil) do + q = get_followers_query(user, page) + + Repo.all(from(u in q, select: u.id)) + end + + def get_friends_query(%User{id: id, following: following}, nil) do from( u in User, where: u.follower_address in ^following, @@ -485,12 +525,28 @@ defmodule Pleroma.User do ) end - def get_friends(user) do - q = get_friends_query(user) + def get_friends_query(user, page) do + from( + u in get_friends_query(user, nil), + limit: 20, + offset: ^((page - 1) * 20) + ) + end + + def get_friends_query(user), do: get_friends_query(user, nil) + + def get_friends(user, page \\ nil) do + q = get_friends_query(user, page) {:ok, Repo.all(q)} end + def get_friends_ids(user, page \\ nil) do + q = get_friends_query(user, page) + + Repo.all(from(u in q, select: u.id)) + end + def get_follow_requests_query(%User{} = user) do from( a in Activity, @@ -622,37 +678,107 @@ defmodule Pleroma.User do Repo.all(query) end - def search(query, resolve \\ false) do - # strip the beginning @ off if there is a query + def search(query, resolve \\ false, for_user \\ nil) do + # Strip the beginning @ off if there is a query query = String.trim_leading(query, "@") - if resolve do - User.get_or_fetch_by_nickname(query) - end + if resolve, do: User.get_or_fetch_by_nickname(query) - inner = - from( - u in User, - select_merge: %{ - search_distance: - fragment( - "? <-> (? || coalesce(?, ''))", - ^query, - u.nickname, - u.name - ) - }, - where: not is_nil(u.nickname) - ) + fts_results = do_search(fts_search_subquery(query), for_user) + trigram_results = do_search(trigram_search_subquery(query), for_user) + + Enum.uniq_by(fts_results ++ trigram_results, & &1.id) + end + + defp do_search(subquery, for_user, options \\ []) do q = from( - s in subquery(inner), - order_by: s.search_distance, - limit: 20 + s in subquery(subquery), + order_by: [desc: s.search_rank], + limit: ^(options[:limit] || 20) ) - Repo.all(q) + results = + q + |> Repo.all() + |> Enum.filter(&(&1.search_rank > 0)) + + boost_search_results(results, for_user) + end + + defp fts_search_subquery(query) do + processed_query = + query + |> String.replace(~r/\W+/, " ") + |> String.trim() + |> String.split() + |> Enum.map(&(&1 <> ":*")) + |> Enum.join(" | ") + + from( + u in User, + select_merge: %{ + search_rank: + fragment( + """ + ts_rank_cd( + setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') || + setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B'), + to_tsquery('simple', ?), + 32 + ) + """, + u.nickname, + u.name, + ^processed_query + ) + }, + where: not is_nil(u.nickname) + ) + end + + defp trigram_search_subquery(query) do + from( + u in User, + select_merge: %{ + search_rank: + fragment( + "similarity(?, trim(? || ' ' || coalesce(?, '')))", + ^query, + u.nickname, + u.name + ) + }, + where: not is_nil(u.nickname) + ) + end + + defp boost_search_results(results, nil), do: results + + defp boost_search_results(results, for_user) do + friends_ids = get_friends_ids(for_user) + followers_ids = get_followers_ids(for_user) + + Enum.map( + results, + fn u -> + search_rank_coef = + cond do + u.id in friends_ids -> + 1.2 + + u.id in followers_ids -> + 1.1 + + true -> + 1 + end + + Map.put(u, :search_rank, u.search_rank * search_rank_coef) + end + ) + |> Enum.sort_by(&(-&1.search_rank)) end def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do