From: Egor Kislitsyn Date: Tue, 14 May 2019 11:15:56 +0000 (+0700) Subject: Merge remote-tracking branch 'pleroma/develop' into feature/disable-account X-Git-Url: https://git.squeep.com/?a=commitdiff_plain;h=5e2b491276d5cd8d90fddf219f7653d1c9b31ef3;p=akkoma Merge remote-tracking branch 'pleroma/develop' into feature/disable-account --- 5e2b491276d5cd8d90fddf219f7653d1c9b31ef3 diff --cc lib/pleroma/user.ex index 10ee01b8c,474de9ba5..cf378d467 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@@ -117,23 -117,6 +115,20 @@@ defmodule Pleroma.User d } end + defp restrict_deactivated(query) do + from(u in query, + where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info) + ) + end + + def following_count(%User{following: []}), do: 0 + - def following_count(%User{following: following, id: id}) do - from(u in User, - where: u.follower_address in ^following, - where: u.id != ^id - ) - |> restrict_deactivated() ++ def following_count(%User{} = user) do ++ user ++ |> get_friends_query() + |> Repo.aggregate(:count, :id) + end + def remote_user_creation(params) do params = params @@@ -271,10 -255,7 +267,7 @@@ candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames]) autofollowed_users = - from(u in User, - where: u.local == true, - where: u.nickname in ^candidates - ) - User.Query.build(%{nickname: candidates, local: true}) ++ User.Query.build(%{nickname: candidates, local: true, deactivated: false}) |> Repo.all() follow_all(user, autofollowed_users) @@@ -593,13 -574,9 +586,9 @@@ ) end - def get_followers_query(%User{id: id, follower_address: follower_address}, nil) do - from( - u in User, - where: fragment("? <@ ?", ^[follower_address], u.following), - where: u.id != ^id - ) - |> restrict_deactivated() + @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t() + def get_followers_query(%User{} = user, nil) do - User.Query.build(%{followers: user}) ++ User.Query.build(%{followers: user, deactivated: false}) end def get_followers_query(user, page) do @@@ -621,13 -599,9 +611,9 @@@ 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, - where: u.id != ^id - ) - |> restrict_deactivated() + @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t() + def get_friends_query(%User{} = user, nil) do - User.Query.build(%{friends: user}) ++ User.Query.build(%{friends: user, deactivated: false}) end def get_friends_query(user, page) do @@@ -747,11 -700,7 +711,8 @@@ def update_follower_count(%User{} = user) do follower_count_query = - User - |> where([u], ^user.follower_address in u.following) - |> where([u], u.id != ^user.id) - User.Query.build(%{followers: user}) |> select([u], %{count: count(u.id)}) ++ User.Query.build(%{followers: user, deactivated: false}) + |> select([u], %{count: count(u.id)}) - |> restrict_deactivated() User |> where(id: ^user.id) @@@ -774,38 -723,19 +735,19 @@@ end end - def get_users_from_set_query(ap_ids, false) do - from( - u in User, - where: u.ap_id in ^ap_ids - ) - end - - def get_users_from_set_query(ap_ids, true) do - query = get_users_from_set_query(ap_ids, false) - - from( - u in query, - where: u.local == true - ) - end - + @spec get_users_from_set([String.t()], boolean()) :: [User.t()] def get_users_from_set(ap_ids, local_only \\ true) do - get_users_from_set_query(ap_ids, local_only) - criteria = %{ap_id: ap_ids} ++ criteria = %{ap_id: ap_ids, deactivated: false} + criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria + + User.Query.build(criteria) |> Repo.all() end + @spec get_recipients_from_activity(Activity.t()) :: [User.t()] def get_recipients_from_activity(%Activity{recipients: to}) do - query = - from( - u in User, - where: u.ap_id in ^to, - or_where: fragment("? && ?", u.following, ^to) - ) - - query = from(u in query, where: u.local == true) - - Repo.all(query) - User.Query.build(%{recipients_from_activity: to, local: true}) ++ User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false}) + |> Repo.all() end def search(query, resolve \\ false, for_user \\ nil) do @@@ -1069,14 -997,23 +1011,23 @@@ end end - def muted_users(user), - do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes)) + @spec muted_users(User.t()) :: [User.t()] + def muted_users(user) do - User.Query.build(%{ap_id: user.info.mutes}) ++ User.Query.build(%{ap_id: user.info.mutes, deactivated: false}) + |> Repo.all() + end - def blocked_users(user), - do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks)) + @spec blocked_users(User.t()) :: [User.t()] + def blocked_users(user) do - User.Query.build(%{ap_id: user.info.blocks}) ++ User.Query.build(%{ap_id: user.info.blocks, deactivated: false}) + |> Repo.all() + end - def subscribers(user), - do: Repo.all(from(u in User, where: u.ap_id in ^user.info.subscribers)) + @spec subscribers(User.t()) :: [User.t()] + def subscribers(user) do - User.Query.build(%{ap_id: user.info.subscribers}) ++ User.Query.build(%{ap_id: user.info.subscribers, deactivated: false}) + |> Repo.all() + end def block_domain(user, domain) do info_cng = @@@ -1102,75 -1039,6 +1053,12 @@@ update_and_set_cache(cng) end - def maybe_local_user_query(query, local) do - if local, do: local_user_query(query), else: query - end - - def local_user_query(query \\ User) do - from( - u in query, - where: u.local == true, - where: not is_nil(u.nickname) - ) - end - - def maybe_external_user_query(query, external) do - if external, do: external_user_query(query), else: query - end - - def external_user_query(query \\ User) do - from( - u in query, - where: u.local == false, - where: not is_nil(u.nickname) - ) - end - - def maybe_active_user_query(query, active) do - if active, do: active_user_query(query), else: query - end - - def active_user_query(query \\ User) do - from( - u in query, - where: fragment("not (?->'deactivated' @> 'true')", u.info), - where: not is_nil(u.nickname) - ) - end - - def maybe_deactivated_user_query(query, deactivated) do - if deactivated, do: deactivated_user_query(query), else: query - end - - def deactivated_user_query(query \\ User) do - from( - u in query, - where: fragment("(?->'deactivated' @> 'true')", u.info), - where: not is_nil(u.nickname) - ) - end - - def active_local_user_query do - from( - u in local_user_query(), - where: fragment("not (?->'deactivated' @> 'true')", u.info) - ) - end - - def moderator_user_query do - from( - u in User, - where: u.local == true, - where: fragment("?->'is_moderator' @> 'true'", u.info) - ) - end - + def deactivate_async(user, status \\ true) do - PleromaJobQueue.enqueue(:user, __MODULE__, [:deactivate_async, user, status]) ++ PleromaJobQueue.enqueue(:background, __MODULE__, [:deactivate_async, user, status]) + end + + def perform(:deactivate_async, user, status), do: deactivate(user, status) + def deactivate(%User{} = user, status \\ true) do info_cng = User.Info.set_activation_status(user.info, status) @@@ -1457,12 -1318,9 +1345,9 @@@ } end + @spec all_superusers() :: [User.t()] def all_superusers do - from( - u in User, - where: u.local == true, - where: fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info) - ) - User.Query.build(%{super_users: true, local: true}) ++ User.Query.build(%{super_users: true, local: true, deactivated: false}) |> Repo.all() end diff --cc lib/pleroma/user/query.ex index 000000000,2dfe5ce92..3873ef80c mode 000000,100644..100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@@ -1,0 -1,150 +1,156 @@@ + # Pleroma: A lightweight social networking server + # Copyright © 2017-2018 Pleroma Authors + # SPDX-License-Identifier: AGPL-3.0-only + + defmodule Pleroma.User.Query do + @moduledoc """ + User query builder module. Builds query from new query or another user query. + + ## Example: + query = Pleroma.User.Query(%{nickname: "nickname"}) + another_query = Pleroma.User.Query.build(query, %{email: "email@example.com"}) + Pleroma.Repo.all(query) + Pleroma.Repo.all(another_query) + + Adding new rules: + - *ilike criteria* + - add field to @ilike_criteria list + - pass non empty string + - e.g. Pleroma.User.Query.build(%{nickname: "nickname"}) + - *equal criteria* + - add field to @equal_criteria list + - pass non empty string + - e.g. Pleroma.User.Query.build(%{email: "email@example.com"}) + - *contains criteria* + - add field to @containns_criteria list + - pass values list + - e.g. Pleroma.User.Query.build(%{ap_id: ["http://ap_id1", "http://ap_id2"]}) + """ + import Ecto.Query + import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1] + alias Pleroma.User + + @type criteria :: + %{ + query: String.t(), + tags: [String.t()], + name: String.t(), + email: String.t(), + local: boolean(), + external: boolean(), + active: boolean(), + deactivated: boolean(), + is_admin: boolean(), + is_moderator: boolean(), + super_users: boolean(), + followers: User.t(), + friends: User.t(), + recipients_from_activity: [String.t()], + nickname: [String.t()], + ap_id: [String.t()] + } + | %{} + + @ilike_criteria [:nickname, :name, :query] + @equal_criteria [:email] + @role_criteria [:is_admin, :is_moderator] + @contains_criteria [:ap_id, :nickname] + + @spec build(criteria()) :: Query.t() + def build(query \\ base_query(), criteria) do + prepare_query(query, criteria) + end + + @spec paginate(Ecto.Query.t(), pos_integer(), pos_integer()) :: Ecto.Query.t() + def paginate(query, page, page_size) do + from(u in query, + limit: ^page_size, + offset: ^((page - 1) * page_size) + ) + end + + defp base_query do + from(u in User) + end + + defp prepare_query(query, criteria) do + Enum.reduce(criteria, query, &compose_query/2) + end + + defp compose_query({key, value}, query) + when key in @ilike_criteria and not_empty_string(value) do + # hack for :query key + key = if key == :query, do: :nickname, else: key + where(query, [u], ilike(field(u, ^key), ^"%#{value}%")) + end + + defp compose_query({key, value}, query) + when key in @equal_criteria and not_empty_string(value) do + where(query, [u], ^[{key, value}]) + end + + defp compose_query({key, values}, query) when key in @contains_criteria and is_list(values) do + where(query, [u], field(u, ^key) in ^values) + end + + defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0 do + Enum.reduce(tags, query, &prepare_tag_criteria/2) + end + + defp compose_query({key, _}, query) when key in @role_criteria do + where(query, [u], fragment("(?->? @> 'true')", u.info, ^to_string(key))) + end + + defp compose_query({:super_users, _}, query) do + where( + query, + [u], + fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info) + ) + end + + defp compose_query({:local, _}, query), do: location_query(query, true) + + defp compose_query({:external, _}, query), do: location_query(query, false) + + defp compose_query({:active, _}, query) do + where(query, [u], fragment("not (?->'deactivated' @> 'true')", u.info)) + |> where([u], not is_nil(u.nickname)) + end + - defp compose_query({:deactivated, _}, query) do ++ defp compose_query({:deactivated, false}, query) do ++ from(u in query, ++ where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info) ++ ) ++ end ++ ++ defp compose_query({:deactivated, true}, query) do + where(query, [u], fragment("?->'deactivated' @> 'true'", u.info)) + |> where([u], not is_nil(u.nickname)) + end + + defp compose_query({:followers, %User{id: id, follower_address: follower_address}}, query) do + where(query, [u], fragment("? <@ ?", ^[follower_address], u.following)) + |> where([u], u.id != ^id) + end + + defp compose_query({:friends, %User{id: id, following: following}}, query) do + where(query, [u], u.follower_address in ^following) + |> where([u], u.id != ^id) + end + + defp compose_query({:recipients_from_activity, to}, query) do + where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to)) + end + + defp compose_query(_unsupported_param, query), do: query + + defp prepare_tag_criteria(tag, query) do + or_where(query, [u], fragment("? = any(?)", ^tag, u.tags)) + end + + defp location_query(query, local) do + where(query, [u], u.local == ^local) + |> where([u], not is_nil(u.nickname)) + end + end diff --cc test/user_test.exs index 00c06dfaa,60de0206e..0b65e89e9 --- a/test/user_test.exs +++ b/test/user_test.exs @@@ -817,71 -816,13 +817,73 @@@ defmodule Pleroma.UserTest d assert addressed in recipients end - test ".deactivate can de-activate then re-activate a user" do - user = insert(:user) - assert false == user.info.deactivated - {:ok, user} = User.deactivate(user) - assert true == user.info.deactivated - {:ok, user} = User.deactivate(user, false) - assert false == user.info.deactivated + describe ".deactivate" do + test "can de-activate then re-activate a user" do + user = insert(:user) + assert false == user.info.deactivated + {:ok, user} = User.deactivate(user) + assert true == user.info.deactivated + {:ok, user} = User.deactivate(user, false) + assert false == user.info.deactivated + end + + test "hide a user from followers " do + user = insert(:user) + user2 = insert(:user) + + {:ok, user} = User.follow(user, user2) + {:ok, _user} = User.deactivate(user) + + info = User.get_cached_user_info(user2) + + assert info.follower_count == 0 + assert {:ok, []} = User.get_followers(user2) + end + + test "hide a user from friends" do + user = insert(:user) + user2 = insert(:user) + + {:ok, user2} = User.follow(user2, user) + assert User.following_count(user2) == 1 + + {:ok, _user} = User.deactivate(user) + + info = User.get_cached_user_info(user2) + + assert info.following_count == 0 + assert User.following_count(user2) == 0 + assert {:ok, []} = User.get_friends(user2) + end + + test "hide a user's statuses from timelines and notifications" do + user = insert(:user) + user2 = insert(:user) + + {:ok, user2} = User.follow(user2, user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{user2.nickname}"}) + ++ activity = Repo.preload(activity, :bookmark) ++ + [notification] = Pleroma.Notification.for_user(user2) + assert notification.activity.id == activity.id + - assert [activity] == ActivityPub.fetch_public_activities(%{}) ++ assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark) + + assert [activity] == + ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2}) + |> ActivityPub.contain_timeline(user2) + + {:ok, _user} = User.deactivate(user) + + assert [] == ActivityPub.fetch_public_activities(%{}) + assert [] == Pleroma.Notification.for_user(user2) + + assert [] == + ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2}) + |> ActivityPub.contain_timeline(user2) + end end test ".delete_user_activities deletes all create activities" do