X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fuser.ex;h=fcb1d5143942bc7738aff01ca8ecf70ca7166181;hb=f17e0f8e4f8f6249d1de9ad8a21953cca4963045;hp=165342011e36e3ce7d21c49ce5f3e225b4cd9ddc;hpb=10ff01acd95d42314b4eb923e5b7a7191356b73e;p=akkoma diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 165342011..fcb1d5143 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -13,6 +13,7 @@ defmodule Pleroma.User do alias Pleroma.Activity alias Pleroma.Conversation.Participation alias Pleroma.Delivery + alias Pleroma.FollowingRelationship alias Pleroma.Keys alias Pleroma.Notification alias Pleroma.Object @@ -26,9 +27,7 @@ defmodule Pleroma.User do alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils alias Pleroma.Web.OAuth - alias Pleroma.Web.OStatus alias Pleroma.Web.RelMe - alias Pleroma.Web.Websub alias Pleroma.Workers.BackgroundWorker require Logger @@ -52,7 +51,6 @@ defmodule Pleroma.User do field(:password, :string, virtual: true) field(:password_confirmation, :string, virtual: true) field(:keys, :string) - field(:following, {:array, :string}, default: []) field(:ap_id, :string) field(:avatar, :map) field(:local, :boolean, default: true) @@ -91,9 +89,6 @@ defmodule Pleroma.User do field(:settings, :map, default: nil) field(:magic_key, :string, default: nil) field(:uri, :string, default: nil) - field(:topic, :string, default: nil) - field(:hub, :string, default: nil) - field(:salmon, :string, default: nil) field(:hide_followers_count, :boolean, default: false) field(:hide_follows_count, :boolean, default: false) field(:hide_followers, :boolean, default: false) @@ -105,9 +100,10 @@ defmodule Pleroma.User do field(:mascot, :map, default: nil) field(:emoji, {:array, :map}, default: []) field(:pleroma_settings_store, :map, default: %{}) - field(:fields, {:array, :map}, default: nil) + field(:fields, {:array, :map}, default: []) field(:raw_fields, {:array, :map}, default: []) field(:discoverable, :boolean, default: false) + field(:invisible, :boolean, default: false) field(:skip_thread_containment, :boolean, default: false) field(:notification_settings, :map, @@ -122,11 +118,15 @@ defmodule Pleroma.User do has_many(:notifications, Notification) has_many(:registrations, Registration) has_many(:deliveries, Delivery) - embeds_one(:info, User.Info) + + field(:info, :map, default: %{}) timestamps() end + @doc "Returns if the user should be allowed to authenticate" + def auth_active?(%User{deactivated: true}), do: false + def auth_active?(%User{confirmation_pending: true}), do: !Pleroma.Config.get([:instance, :account_activation_required]) @@ -146,6 +146,9 @@ defmodule Pleroma.User do def superuser?(%User{local: true, is_moderator: true}), do: true def superuser?(_), do: false + def invisible?(%User{invisible: true}), do: true + def invisible?(_), do: false + def avatar_url(user, options \\ []) do case user.avatar do %{"url" => [%{"href" => href} | _]} -> href @@ -216,63 +219,7 @@ defmodule Pleroma.User do from(u in query, where: u.deactivated != ^true) end - def following_count(%User{following: []}), do: 0 - - def following_count(%User{} = user) do - user - |> get_friends_query() - |> Repo.aggregate(:count, :id) - end - - @info_fields [ - :banner, - :background, - :source_data, - :note_count, - :follower_count, - :following_count, - :locked, - :confirmation_pending, - :password_reset_pending, - :confirmation_token, - :default_scope, - :blocks, - :domain_blocks, - :mutes, - :muted_reblogs, - :muted_notifications, - :subscribers, - :deactivated, - :no_rich_text, - :ap_enabled, - :is_moderator, - :is_admin, - :show_role, - :settings, - :magic_key, - :uri, - :topic, - :hub, - :salmon, - :hide_followers_count, - :hide_follows_count, - :hide_followers, - :hide_follows, - :hide_favorites, - :unread_conversation_count, - :pinned_activities, - :email_notifications, - :mascot, - :emoji, - :pleroma_settings_store, - :fields, - :raw_fields, - :discoverable, - :skip_thread_containment, - :notification_settings - ] - - def info_fields, do: @info_fields + defdelegate following_count(user), to: FollowingRelationship defp truncate_fields_param(params) do if Map.has_key?(params, :fields) do @@ -304,14 +251,37 @@ defmodule Pleroma.User do changeset = %User{local: false} - |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar] ++ @info_fields) + |> cast( + params, + [ + :bio, + :name, + :ap_id, + :nickname, + :avatar, + :ap_enabled, + :source_data, + :banner, + :locked, + :magic_key, + :uri, + :hide_followers, + :hide_follows, + :hide_followers_count, + :hide_follows_count, + :follower_count, + :fields, + :following_count, + :discoverable, + :invisible + ] + ) |> validate_required([:name, :ap_id]) |> unique_constraint(:nickname) |> validate_format(:nickname, @email_regex) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, max: name_limit) |> validate_fields(true) - |> change_info(& &1) case params[:source_data] do %{"followers" => followers, "following" => following} -> @@ -330,7 +300,30 @@ defmodule Pleroma.User do name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) struct - |> cast(params, [:bio, :name, :avatar, :following] ++ @info_fields) + |> cast( + params, + [ + :bio, + :name, + :avatar, + :locked, + :no_rich_text, + :default_scope, + :banner, + :hide_follows, + :hide_followers, + :hide_followers_count, + :hide_follows_count, + :hide_favorites, + :background, + :show_role, + :skip_thread_containment, + :fields, + :raw_fields, + :pleroma_settings_store, + :discoverable + ] + ) |> unique_constraint(:nickname) |> validate_format(:nickname, local_nickname_regex()) |> validate_length(:bio, max: bio_limit) @@ -355,15 +348,27 @@ defmodule Pleroma.User do :follower_address, :following_address, :avatar, - :last_refreshed_at - ] ++ @info_fields + :last_refreshed_at, + :ap_enabled, + :source_data, + :banner, + :locked, + :magic_key, + :follower_count, + :following_count, + :hide_follows, + :fields, + :hide_followers, + :discoverable, + :hide_followers_count, + :hide_follows_count + ] ) |> unique_constraint(:nickname) |> validate_format(:nickname, local_nickname_regex()) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, max: name_limit) |> validate_fields(remote?) - |> change_info(& &1) end def password_update_changeset(struct, params) do @@ -426,7 +431,6 @@ defmodule Pleroma.User do |> validate_format(:email, @email_regex) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, min: 1, max: name_limit) - |> change_info(& &1) |> maybe_validate_required_email(opts[:external]) |> put_password_hash |> put_ap_id() @@ -446,7 +450,6 @@ defmodule Pleroma.User do followers = ap_followers(%User{nickname: get_field(changeset, :nickname)}) changeset - |> put_change(:following, [followers]) |> put_change(:follower_address, followers) end @@ -500,8 +503,8 @@ defmodule Pleroma.User do def needs_update?(_), do: true @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()} - def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true}) do - {:ok, follower} + def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do + follow(follower, followed, "pending") end def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do @@ -519,37 +522,22 @@ defmodule Pleroma.User do @doc "A mass follow for local users. Respects blocks in both directions but does not create activities." @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()} def follow_all(follower, followeds) do - followed_addresses = - followeds - |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end) - |> Enum.map(fn %{follower_address: fa} -> fa end) - - q = - from(u in User, - where: u.id == ^follower.id, - update: [ - set: [ - following: - fragment( - "array(select distinct unnest (array_cat(?, ?)))", - u.following, - ^followed_addresses - ) - ] - ], - select: u - ) + followeds = + Enum.reject(followeds, fn followed -> + blocks?(follower, followed) || blocks?(followed, follower) + end) - {1, [follower]} = Repo.update_all(q, []) + Enum.each(followeds, &follow(follower, &1, "accept")) Enum.each(followeds, &update_follower_count/1) set_cache(follower) end - def follow(%User{} = follower, %User{} = followed) do + defdelegate following(user), to: FollowingRelationship + + def follow(%User{} = follower, %User{} = followed, state \\ "accept") do deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) - ap_followers = followed.follower_address cond do followed.deactivated -> @@ -559,18 +547,7 @@ defmodule Pleroma.User do {:error, "Could not follow user: #{followed.nickname} blocked you."} true -> - if !followed.local && follower.local && !ap_enabled?(followed) do - Websub.subscribe(follower, followed) - end - - q = - from(u in User, - where: u.id == ^follower.id, - update: [push: [following: ^ap_followers]], - select: u - ) - - {1, [follower]} = Repo.update_all(q, []) + FollowingRelationship.follow(follower, followed, state) follower = maybe_update_following_count(follower) @@ -581,17 +558,8 @@ defmodule Pleroma.User do end def unfollow(%User{} = follower, %User{} = followed) do - ap_followers = followed.follower_address - if following?(follower, followed) and follower.ap_id != followed.ap_id do - q = - from(u in User, - where: u.id == ^follower.id, - update: [pull: [following: ^ap_followers]], - select: u - ) - - {1, [follower]} = Repo.update_all(q, []) + FollowingRelationship.unfollow(follower, followed) follower = maybe_update_following_count(follower) @@ -605,10 +573,7 @@ defmodule Pleroma.User do end end - @spec following?(User.t(), User.t()) :: boolean - def following?(%User{} = follower, %User{} = followed) do - Enum.member?(follower.following, followed.follower_address) - end + defdelegate following?(follower, followed), to: FollowingRelationship def locked?(%User{} = user) do user.locked || false @@ -742,12 +707,7 @@ defmodule Pleroma.User do Cachex.fetch!(:user_cache, key, fn -> user_info(user) end) end - def fetch_by_nickname(nickname) do - case ActivityPub.make_user_from_nickname(nickname) do - {:ok, user} -> {:ok, user} - _ -> OStatus.make_user(nickname) - end - end + def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname) def get_or_fetch_by_nickname(nickname) do with %User{} = user <- get_by_nickname(nickname) do @@ -835,16 +795,7 @@ defmodule Pleroma.User do |> Repo.all() end - @spec get_follow_requests(User.t()) :: {:ok, [User.t()]} - def get_follow_requests(%User{} = user) do - user - |> Activity.follow_requests_for_actor() - |> join(:inner, [a], u in User, on: a.actor == u.ap_id) - |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address])) - |> group_by([a, u], u.id) - |> select([a, u], u) - |> Repo.all() - end + defdelegate get_follow_requests(user), to: FollowingRelationship def increase_note_count(%User{} = user) do User @@ -884,7 +835,9 @@ defmodule Pleroma.User do ) |> Repo.one() - update_and_set_cache(user, %{note_count: note_count}) + user + |> cast(%{note_count: note_count}, [:note_count]) + |> update_and_set_cache() end @spec maybe_fetch_follow_information(User.t()) :: User.t() @@ -970,7 +923,7 @@ defmodule Pleroma.User do end end - def set_unread_conversation_count(_), do: :noop + def set_unread_conversation_count(user), do: {:ok, user} def increment_unread_conversation_count(conversation, %User{local: true} = user) do unread_query = @@ -992,19 +945,7 @@ defmodule Pleroma.User do end end - def increment_unread_conversation_count(_, _), do: :noop - - def remove_duplicated_following(%User{following: following} = user) do - uniq_following = Enum.uniq(following) - - if length(following) == length(uniq_following) do - {:ok, user} - else - user - |> update_changeset(%{following: uniq_following}) - |> update_and_set_cache() - end - end + def increment_unread_conversation_count(_, user), do: {:ok, user} @spec get_users_from_set([String.t()], boolean()) :: [User.t()] def get_users_from_set(ap_ids, local_only \\ true) do @@ -1023,11 +964,11 @@ defmodule Pleroma.User do @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()} def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do - update_info(muter, &User.Info.add_to_mutes(&1, ap_id, notifications?)) + add_to_mutes(muter, ap_id, notifications?) end def unmute(muter, %{ap_id: ap_id}) do - update_info(muter, &User.Info.remove_from_mutes(&1, ap_id)) + remove_from_mutes(muter, ap_id) end def subscribe(subscriber, %{ap_id: ap_id}) do @@ -1076,8 +1017,8 @@ defmodule Pleroma.User do if following?(blocked, blocker), do: unfollow(blocked, blocker) {:ok, blocker} = update_follower_count(blocker) - - update_info(blocker, &User.Info.add_to_block(&1, ap_id)) + {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked) + add_to_block(blocker, ap_id) end # helper to handle the block given only an actor's AP id @@ -1086,17 +1027,17 @@ defmodule Pleroma.User do end def unblock(blocker, %{ap_id: ap_id}) do - update_info(blocker, &User.Info.remove_from_block(&1, ap_id)) + remove_from_block(blocker, ap_id) end def mutes?(nil, _), do: false - def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id) + def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id) @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean() def muted_notifications?(nil, _), do: false def muted_notifications?(user, %{ap_id: ap_id}), - do: Enum.member?(user.info.muted_notifications, ap_id) + do: Enum.member?(user.muted_notifications, ap_id) def blocks?(%User{} = user, %User{} = target) do blocks_ap_id?(user, target) || blocks_domain?(user, target) @@ -1105,13 +1046,13 @@ defmodule Pleroma.User do def blocks?(nil, _), do: false def blocks_ap_id?(%User{} = user, %User{} = target) do - Enum.member?(user.info.blocks, target.ap_id) + Enum.member?(user.blocks, target.ap_id) end def blocks_ap_id?(_, _), do: false def blocks_domain?(%User{} = user, %User{} = target) do - domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks) + domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) %{host: host} = URI.parse(target.ap_id) Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host) end @@ -1126,13 +1067,13 @@ defmodule Pleroma.User do @spec muted_users(User.t()) :: [User.t()] def muted_users(user) do - User.Query.build(%{ap_id: user.info.mutes, deactivated: false}) + User.Query.build(%{ap_id: user.mutes, deactivated: false}) |> Repo.all() end @spec blocked_users(User.t()) :: [User.t()] def blocked_users(user) do - User.Query.build(%{ap_id: user.info.blocks, deactivated: false}) + User.Query.build(%{ap_id: user.blocks, deactivated: false}) |> Repo.all() end @@ -1142,22 +1083,27 @@ defmodule Pleroma.User do |> Repo.all() end - def block_domain(user, domain) do - update_info(user, &User.Info.add_to_domain_block(&1, domain)) + def deactivate_async(user, status \\ true) do + BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status}) end - def unblock_domain(user, domain) do - update_info(user, &User.Info.remove_from_domain_block(&1, domain)) - end + def deactivate(user, status \\ true) - def deactivate_async(user, status \\ true) do - BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status}) + def deactivate(users, status) when is_list(users) do + Repo.transaction(fn -> + for user <- users, do: deactivate(user, status) + end) end - def deactivate(%User{} = user, status \\ true) do + def deactivate(%User{} = user, status) do with {:ok, user} <- set_activation_status(user, status) do Enum.each(get_followers(user), &invalidate_cache/1) - Enum.each(get_friends(user), &update_follower_count/1) + + # Only update local user counts, remote will be update during the next pull. + user + |> get_friends() + |> Enum.filter(& &1.local) + |> Enum.each(&update_follower_count/1) {:ok, user} end @@ -1182,6 +1128,10 @@ defmodule Pleroma.User do |> update_and_set_cache() end + def delete(users) when is_list(users) do + for user <- users, do: delete(user) + end + def delete(%User{} = user) do BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) end @@ -1344,18 +1294,7 @@ defmodule Pleroma.User do def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy]) - def fetch_by_ap_id(ap_id) do - case ActivityPub.make_user_from_ap_id(ap_id) do - {:ok, user} -> - {:ok, user} - - _ -> - case OStatus.make_user(ap_id) do - {:ok, user} -> {:ok, user} - _ -> {:error, "Could not fetch by AP id"} - end - end - end + def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id) def get_or_fetch_by_ap_id(ap_id) do user = get_cached_by_ap_id(ap_id) @@ -1385,7 +1324,7 @@ defmodule Pleroma.User do else _ -> {:ok, user} = - %User{info: %User.Info{}} + %User{} |> cast(%{}, [:ap_id, :nickname, :local]) |> put_change(:ap_id, uri) |> put_change(:nickname, nickname) @@ -1408,11 +1347,6 @@ defmodule Pleroma.User do {:ok, key} end - # OStatus Magic Key - def public_key(%{magic_key: magic_key}) when not is_nil(magic_key) do - {:ok, Pleroma.Web.Salmon.decode_key(magic_key)} - end - def public_key(_), do: {:error, "not found key"} def get_public_key_for_ap_id(ap_id) do @@ -1549,7 +1483,6 @@ defmodule Pleroma.User do %User{ name: ap_id, ap_id: ap_id, - info: %User.Info{}, nickname: "erroruser@example.com", inserted_at: NaiveDateTime.utc_now() } @@ -1562,7 +1495,7 @@ defmodule Pleroma.User do end def showing_reblogs?(%User{} = user, %User{} = target) do - target.ap_id not in user.info.muted_reblogs + target.ap_id not in user.muted_reblogs end @doc """ @@ -1741,28 +1674,6 @@ defmodule Pleroma.User do |> update_and_set_cache() end - @doc """ - Changes `user.info` and returns the user changeset. - - `fun` is called with the `user.info`. - """ - def change_info(user, fun) do - changeset = change(user) - info = get_field(changeset, :info) || %User.Info{} - put_embed(changeset, :info, fun.(info)) - end - - @doc """ - Updates `user.info` and sets cache. - - `fun` is called with the `user.info`. - """ - def update_info(user, fun) do - user - |> change_info(fun) - |> update_and_set_cache() - end - def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do %{ admin: is_admin, @@ -1918,4 +1829,105 @@ defmodule Pleroma.User do def remove_from_subscribers(user, subscribed) do set_subscribers(user, List.delete(user.subscribers, subscribed)) end + + defp set_domain_blocks(user, domain_blocks) do + params = %{domain_blocks: domain_blocks} + + user + |> cast(params, [:domain_blocks]) + |> validate_required([:domain_blocks]) + |> update_and_set_cache() + end + + def block_domain(user, domain_blocked) do + set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks])) + end + + def unblock_domain(user, domain_blocked) do + set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked)) + end + + defp set_blocks(user, blocks) do + params = %{blocks: blocks} + + user + |> cast(params, [:blocks]) + |> validate_required([:blocks]) + |> update_and_set_cache() + end + + def add_to_block(user, blocked) do + set_blocks(user, Enum.uniq([blocked | user.blocks])) + end + + def remove_from_block(user, blocked) do + set_blocks(user, List.delete(user.blocks, blocked)) + end + + defp set_mutes(user, mutes) do + params = %{mutes: mutes} + + user + |> cast(params, [:mutes]) + |> validate_required([:mutes]) + |> update_and_set_cache() + end + + def add_to_mutes(user, muted, notifications?) do + with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do + set_notification_mutes( + user, + Enum.uniq([muted | user.muted_notifications]), + notifications? + ) + end + end + + def remove_from_mutes(user, muted) do + with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do + set_notification_mutes( + user, + List.delete(user.muted_notifications, muted), + true + ) + end + end + + defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do + {:ok, user} + end + + defp set_notification_mutes(user, muted_notifications, true = _notifications?) do + params = %{muted_notifications: muted_notifications} + + user + |> cast(params, [:muted_notifications]) + |> validate_required([:muted_notifications]) + |> update_and_set_cache() + end + + def add_reblog_mute(user, ap_id) do + params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]} + + user + |> cast(params, [:muted_reblogs]) + |> update_and_set_cache() + end + + def remove_reblog_mute(user, ap_id) do + params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)} + + user + |> cast(params, [:muted_reblogs]) + |> update_and_set_cache() + end + + def set_invisible(user, invisible) do + params = %{invisible: invisible} + + user + |> cast(params, [:invisible]) + |> validate_required([:invisible]) + |> update_and_set_cache() + end end