X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fuser.ex;h=bcd5256c8e0d51203e6a00aab2ee974e2c6d1da3;hb=bd853199d93e03fedf43397455939c6d633fa36b;hp=a240579f35030cda6fd4fbb4b614041d718fc909;hpb=6b32e1de234a84d0bdbbee5eeb5b2a543f24ce6b;p=akkoma diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index a240579f3..c25023dc1 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors +# Copyright © 2017-2021 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.User do @@ -27,13 +27,13 @@ defmodule Pleroma.User do alias Pleroma.Repo alias Pleroma.User alias Pleroma.UserRelationship - alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils + alias Pleroma.Web.Endpoint alias Pleroma.Web.OAuth alias Pleroma.Web.RelMe alias Pleroma.Workers.BackgroundWorker @@ -81,6 +81,8 @@ defmodule Pleroma.User do ] ] + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + schema "users" do field(:bio, :string, default: "") field(:raw_bio, :string) @@ -97,6 +99,7 @@ defmodule Pleroma.User do field(:local, :boolean, default: true) field(:follower_address, :string) field(:following_address, :string) + field(:featured_address, :string) field(:search_rank, :float, virtual: true) field(:search_type, :integer, virtual: true) field(:tags, {:array, :string}, default: []) @@ -108,27 +111,25 @@ defmodule Pleroma.User do field(:follower_count, :integer, default: 0) field(:following_count, :integer, default: 0) field(:is_locked, :boolean, default: false) - field(:confirmation_pending, :boolean, default: false) + field(:is_confirmed, :boolean, default: true) field(:password_reset_pending, :boolean, default: false) - field(:approval_pending, :boolean, default: false) + field(:is_approved, :boolean, default: true) field(:registration_reason, :string, default: nil) field(:confirmation_token, :string, default: nil) field(:default_scope, :string, default: "public") field(:domain_blocks, {:array, :string}, default: []) - field(:deactivated, :boolean, default: false) + field(:is_active, :boolean, default: true) field(:no_rich_text, :boolean, default: false) field(:ap_enabled, :boolean, default: false) field(:is_moderator, :boolean, default: false) field(:is_admin, :boolean, default: false) field(:show_role, :boolean, default: true) - field(:mastofe_settings, :map, default: nil) field(:uri, ObjectValidators.Uri, default: nil) field(:hide_followers_count, :boolean, default: false) field(:hide_follows_count, :boolean, default: false) field(:hide_followers, :boolean, default: false) field(:hide_follows, :boolean, default: false) field(:hide_favorites, :boolean, default: true) - field(:pinned_activities, {:array, :string}, default: []) field(:email_notifications, :map, default: %{"digest" => false}) field(:mascot, :map, default: nil) field(:emoji, :map, default: %{}) @@ -140,10 +141,14 @@ defmodule Pleroma.User do field(:allow_following_move, :boolean, default: true) field(:skip_thread_containment, :boolean, default: false) field(:actor_type, :string, default: "Person") - field(:also_known_as, {:array, :string}, default: []) + field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: []) field(:inbox, :string) field(:shared_inbox, :string) field(:accepts_chat_messages, :boolean, default: nil) + field(:last_active_at, :naive_datetime) + field(:disclose_client, :boolean, default: true) + field(:pinned_objects, :map, default: %{}) + field(:is_suggested, :boolean, default: false) embeds_one( :notification_settings, @@ -215,7 +220,8 @@ defmodule Pleroma.User do target_users_query = assoc(user, unquote(outgoing_relation_target)) if restrict_deactivated? do - restrict_deactivated(target_users_query) + target_users_query + |> User.Query.build(%{deactivated: false}) else target_users_query end @@ -245,6 +251,18 @@ defmodule Pleroma.User do end end + def cached_blocked_users_ap_ids(user) do + @cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ -> + blocked_users_ap_ids(user) + end) + end + + def cached_muted_users_ap_ids(user) do + @cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ -> + muted_users_ap_ids(user) + end) + end + defdelegate following_count(user), to: FollowingRelationship defdelegate following(user), to: FollowingRelationship defdelegate following?(follower, followed), to: FollowingRelationship @@ -272,18 +290,10 @@ defmodule Pleroma.User do @doc "Returns status account" @spec account_status(User.t()) :: account_status() - def account_status(%User{deactivated: true}), do: :deactivated + def account_status(%User{is_active: false}), do: :deactivated def account_status(%User{password_reset_pending: true}), do: :password_reset_pending - def account_status(%User{local: true, approval_pending: true}), do: :approval_pending - - def account_status(%User{local: true, confirmation_pending: true}) do - if Config.get([:instance, :account_activation_required]) do - :confirmation_pending - else - :active - end - end - + def account_status(%User{local: true, is_approved: false}), do: :approval_pending + def account_status(%User{local: true, is_confirmed: false}), do: :confirmation_pending def account_status(%User{}), do: :active @spec visible_for(User.t(), User.t() | nil) :: @@ -350,7 +360,7 @@ defmodule Pleroma.User do _ -> unless options[:no_default] do - Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png") + Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png") end end end @@ -358,13 +368,15 @@ defmodule Pleroma.User do def banner_url(user, options \\ []) do case user.banner do %{"url" => [%{"href" => href} | _]} -> href - _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png" + _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png" end end # Should probably be renamed or removed - def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}" + @spec ap_id(User.t()) :: String.t() + def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}" + @spec ap_followers(User.t()) :: String.t() def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers" @@ -372,10 +384,10 @@ defmodule Pleroma.User do def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa def ap_following(%User{} = user), do: "#{ap_id(user)}/following" - @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t() - def restrict_deactivated(query) do - from(u in query, where: u.deactivated != ^true) - end + @spec ap_featured_collection(User.t()) :: String.t() + def ap_featured_collection(%User{featured_address: fa}) when is_binary(fa), do: fa + + def ap_featured_collection(%User{} = user), do: "#{ap_id(user)}/collections/featured" defp truncate_fields_param(params) do if Map.has_key?(params, :fields) do @@ -439,6 +451,7 @@ defmodule Pleroma.User do :uri, :follower_address, :following_address, + :featured_address, :hide_followers, :hide_follows, :hide_followers_count, @@ -450,7 +463,8 @@ defmodule Pleroma.User do :invisible, :actor_type, :also_known_as, - :accepts_chat_messages + :accepts_chat_messages, + :pinned_objects ] ) |> cast(params, [:name], empty_values: []) @@ -461,6 +475,18 @@ defmodule Pleroma.User do |> validate_length(:bio, max: bio_limit) |> validate_length(:name, max: name_limit) |> validate_fields(true) + |> validate_non_local() + end + + defp validate_non_local(cng) do + local? = get_field(cng, :local) + + if local? do + cng + |> add_error(:local, "User is local, can't update with this changeset.") + else + cng + end end def update_changeset(struct, params \\ %{}) do @@ -489,6 +515,7 @@ defmodule Pleroma.User do :hide_follows_count, :hide_favorites, :allow_following_move, + :also_known_as, :background, :show_role, :skip_thread_containment, @@ -497,8 +524,8 @@ defmodule Pleroma.User do :pleroma_settings_store, :is_discoverable, :actor_type, - :also_known_as, - :accepts_chat_messages + :accepts_chat_messages, + :disclose_client ] ) |> unique_constraint(:nickname) @@ -669,7 +696,7 @@ defmodule Pleroma.User do |> validate_format(:nickname, local_nickname_regex()) |> put_ap_id() |> unique_constraint(:ap_id) - |> put_following_and_follower_address() + |> put_following_and_follower_and_featured_address() end def register_changeset(struct, params \\ %{}, opts \\ []) do @@ -678,23 +705,23 @@ defmodule Pleroma.User do reason_limit = Config.get([:instance, :registration_reason_length], 500) params = Map.put_new(params, :accepts_chat_messages, true) - need_confirmation? = - if is_nil(opts[:need_confirmation]) do - Config.get([:instance, :account_activation_required]) + confirmed? = + if is_nil(opts[:confirmed]) do + !Config.get([:instance, :account_activation_required]) else - opts[:need_confirmation] + opts[:confirmed] end - need_approval? = - if is_nil(opts[:need_approval]) do - Config.get([:instance, :account_approval_required]) + approved? = + if is_nil(opts[:approved]) do + !Config.get([:instance, :account_approval_required]) else - opts[:need_approval] + opts[:approved] end struct - |> confirmation_changeset(need_confirmation: need_confirmation?) - |> approval_changeset(need_approval: need_approval?) + |> confirmation_changeset(set_confirmation: confirmed?) + |> approval_changeset(set_approval: approved?) |> cast(params, [ :bio, :raw_bio, @@ -730,7 +757,7 @@ defmodule Pleroma.User do |> put_password_hash |> put_ap_id() |> unique_constraint(:ap_id) - |> put_following_and_follower_address() + |> put_following_and_follower_and_featured_address() end def maybe_validate_required_email(changeset, true), do: changeset @@ -748,18 +775,23 @@ defmodule Pleroma.User do put_change(changeset, :ap_id, ap_id) end - defp put_following_and_follower_address(changeset) do - followers = ap_followers(%User{nickname: get_field(changeset, :nickname)}) + defp put_following_and_follower_and_featured_address(changeset) do + user = %User{nickname: get_field(changeset, :nickname)} + followers = ap_followers(user) + following = ap_following(user) + featured = ap_featured_collection(user) changeset |> put_change(:follower_address, followers) + |> put_change(:following_address, following) + |> put_change(:featured_address, featured) end defp autofollow_users(user) do candidates = Config.get([:instance, :autofollowed_nicknames]) autofollowed_users = - User.Query.build(%{nickname: candidates, local: true, deactivated: false}) + User.Query.build(%{nickname: candidates, local: true, is_active: true}) |> Repo.all() follow_all(user, autofollowed_users) @@ -782,19 +814,52 @@ defmodule Pleroma.User do end end - def post_register_action(%User{} = user) do + def post_register_action(%User{is_confirmed: false} = user) do + with {:ok, _} <- maybe_send_confirmation_email(user) do + {:ok, user} + end + end + + def post_register_action(%User{is_approved: false} = user) do + with {:ok, _} <- send_user_approval_email(user), + {:ok, _} <- send_admin_approval_emails(user) do + {:ok, user} + end + end + + def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do with {:ok, user} <- autofollow_users(user), {:ok, _} <- autofollowing_users(user), {:ok, user} <- set_cache(user), - {:ok, _} <- send_welcome_email(user), - {:ok, _} <- send_welcome_message(user), - {:ok, _} <- send_welcome_chat_message(user), - {:ok, _} <- try_send_confirmation_email(user) do + {:ok, _} <- maybe_send_registration_email(user), + {:ok, _} <- maybe_send_welcome_email(user), + {:ok, _} <- maybe_send_welcome_message(user), + {:ok, _} <- maybe_send_welcome_chat_message(user) do {:ok, user} end end - def send_welcome_message(user) do + defp send_user_approval_email(user) do + user + |> Pleroma.Emails.UserEmail.approval_pending_email() + |> Pleroma.Emails.Mailer.deliver_async() + + {:ok, :enqueued} + end + + defp send_admin_approval_emails(user) do + all_superusers() + |> Enum.filter(fn user -> not is_nil(user.email) end) + |> Enum.each(fn superuser -> + superuser + |> Pleroma.Emails.AdminEmail.new_unapproved_registration(user) + |> Pleroma.Emails.Mailer.deliver_async() + end) + + {:ok, :enqueued} + end + + defp maybe_send_welcome_message(user) do if User.WelcomeMessage.enabled?() do User.WelcomeMessage.post_message(user) {:ok, :enqueued} @@ -803,7 +868,7 @@ defmodule Pleroma.User do end end - def send_welcome_chat_message(user) do + defp maybe_send_welcome_chat_message(user) do if User.WelcomeChatMessage.enabled?() do User.WelcomeChatMessage.post_message(user) {:ok, :enqueued} @@ -812,7 +877,7 @@ defmodule Pleroma.User do end end - def send_welcome_email(%User{email: email} = user) when is_binary(email) do + defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do if User.WelcomeEmail.enabled?() do User.WelcomeEmail.send_email(user) {:ok, :enqueued} @@ -821,10 +886,10 @@ defmodule Pleroma.User do end end - def send_welcome_email(_), do: {:ok, :noop} + defp maybe_send_welcome_email(_), do: {:ok, :noop} - @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop} - def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user) + @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop} + def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user) when is_binary(email) do if Config.get([:instance, :account_activation_required]) do send_confirmation_email(user) @@ -834,7 +899,7 @@ defmodule Pleroma.User do end end - def try_send_confirmation_email(_), do: {:ok, :noop} + def maybe_send_confirmation_email(_), do: {:ok, :noop} @spec send_confirmation_email(Uset.t()) :: User.t() def send_confirmation_email(%User{} = user) do @@ -845,6 +910,24 @@ defmodule Pleroma.User do user end + @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop} + defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do + with false <- User.WelcomeEmail.enabled?(), + false <- Config.get([:instance, :account_activation_required], false), + false <- Config.get([:instance, :account_approval_required], false) do + user + |> Pleroma.Emails.UserEmail.successful_registration_email() + |> Pleroma.Emails.Mailer.deliver_async() + + {:ok, :enqueued} + else + _ -> + {:ok, :noop} + end + end + + defp maybe_send_registration_email(_), do: {:ok, :noop} + def needs_update?(%User{local: true}), do: false def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true @@ -870,7 +953,7 @@ defmodule Pleroma.User do if not ap_enabled?(followed) do follow(follower, followed) else - {:ok, follower} + {:ok, follower, followed} end end @@ -888,7 +971,7 @@ defmodule Pleroma.User do deny_follow_blocked = Config.get([:user, :deny_follow_blocked]) cond do - followed.deactivated -> + not followed.is_active -> {:error, "Could not follow user: #{followed.nickname} is deactivated."} deny_follow_blocked and blocks?(followed, follower) -> @@ -896,11 +979,6 @@ defmodule Pleroma.User do true -> FollowingRelationship.follow(follower, followed, state) - - {:ok, _} = update_follower_count(followed) - - follower - |> update_following_count() end end @@ -924,11 +1002,6 @@ defmodule Pleroma.User do case get_follow_state(follower, followed) do state when state in [:follow_pending, :follow_accept] -> FollowingRelationship.unfollow(follower, followed) - {:ok, followed} = update_follower_count(followed) - - {:ok, follower} = update_following_count(follower) - - {:ok, follower, followed} nil -> {:error, "Not subscribed!"} @@ -1002,9 +1075,9 @@ defmodule Pleroma.User do def set_cache({:error, err}), do: {:error, err} def set_cache(%User{} = user) do - Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) - Cachex.put(:user_cache, "nickname:#{user.nickname}", user) - Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user)) + @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) + @cachex.put(:user_cache, "nickname:#{user.nickname}", user) + @cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user)) {:ok, user} end @@ -1027,24 +1100,26 @@ defmodule Pleroma.User do @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()] def get_cached_user_friends_ap_ids(user) do - Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ -> + @cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ -> get_user_friends_ap_ids(user) end) end def invalidate_cache(user) do - Cachex.del(:user_cache, "ap_id:#{user.ap_id}") - Cachex.del(:user_cache, "nickname:#{user.nickname}") - Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}") + @cachex.del(:user_cache, "ap_id:#{user.ap_id}") + @cachex.del(:user_cache, "nickname:#{user.nickname}") + @cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}") + @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}") + @cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}") end @spec get_cached_by_ap_id(String.t()) :: User.t() | nil def get_cached_by_ap_id(ap_id) do key = "ap_id:#{ap_id}" - with {:ok, nil} <- Cachex.get(:user_cache, key), + with {:ok, nil} <- @cachex.get(:user_cache, key), user when not is_nil(user) <- get_by_ap_id(ap_id), - {:ok, true} <- Cachex.put(:user_cache, key, user) do + {:ok, true} <- @cachex.put(:user_cache, key, user) do user else {:ok, user} -> user @@ -1056,11 +1131,11 @@ defmodule Pleroma.User do key = "id:#{id}" ap_id = - Cachex.fetch!(:user_cache, key, fn _ -> + @cachex.fetch!(:user_cache, key, fn _ -> user = get_by_id(id) if user do - Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) + @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) {:commit, user.ap_id} else {:ignore, ""} @@ -1073,7 +1148,7 @@ defmodule Pleroma.User do def get_cached_by_nickname(nickname) do key = "nickname:#{nickname}" - Cachex.fetch!(:user_cache, key, fn -> + @cachex.fetch!(:user_cache, key, fn _ -> case get_or_fetch_by_nickname(nickname) do {:ok, user} -> {:commit, user} {:error, _error} -> {:ignore, nil} @@ -1131,7 +1206,7 @@ defmodule Pleroma.User do @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, deactivated: false}) + User.Query.build(%{followers: user, is_active: true}) end def get_followers_query(%User{} = user, page) do @@ -1307,7 +1382,7 @@ defmodule Pleroma.User do @spec get_users_from_set([String.t()], keyword()) :: [User.t()] def get_users_from_set(ap_ids, opts \\ []) do local_only = Keyword.get(opts, :local_only, true) - criteria = %{ap_id: ap_ids, deactivated: false} + criteria = %{ap_id: ap_ids, is_active: true} criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria User.Query.build(criteria) @@ -1318,7 +1393,7 @@ defmodule Pleroma.User do def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do to = [actor | to] - query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false}) + query = User.Query.build(%{recipients_from_activity: to, local: true, is_active: true}) query |> Repo.all() @@ -1342,6 +1417,8 @@ defmodule Pleroma.User do ) end + @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}") + {:ok, Enum.filter([user_mute, user_notification_mute], & &1)} end end @@ -1350,6 +1427,7 @@ defmodule Pleroma.User do with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee), {:ok, user_notification_mute} <- UserRelationship.delete_notification_mute(muter, mutee) do + @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}") {:ok, [user_mute, user_notification_mute]} end end @@ -1534,19 +1612,19 @@ defmodule Pleroma.User do defp maybe_filter_on_ap_id(query, _ap_ids), do: query - def deactivate_async(user, status \\ true) do - BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status}) + def set_activation_async(user, status \\ true) do + BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status}) end - def deactivate(user, status \\ true) - - def deactivate(users, status) when is_list(users) do + @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} + def set_activation(users, status) when is_list(users) do Repo.transaction(fn -> - for user <- users, do: deactivate(user, status) + for user <- users, do: set_activation(user, status) end) end - def deactivate(%User{} = user, status) do + @spec set_activation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} + def set_activation(%User{} = user, status) do with {:ok, user} <- set_activation_status(user, status) do user |> get_followers() @@ -1571,8 +1649,47 @@ defmodule Pleroma.User do end) end - def approve(%User{} = user) do - change(user, approval_pending: false) + def approve(%User{is_approved: false} = user) do + with chg <- change(user, is_approved: true), + {:ok, user} <- update_and_set_cache(chg) do + post_register_action(user) + {:ok, user} + end + end + + def approve(%User{} = user), do: {:ok, user} + + def confirm(users) when is_list(users) do + Repo.transaction(fn -> + Enum.map(users, fn user -> + with {:ok, user} <- confirm(user), do: user + end) + end) + end + + def confirm(%User{is_confirmed: false} = user) do + with chg <- confirmation_changeset(user, set_confirmation: true), + {:ok, user} <- update_and_set_cache(chg) do + post_register_action(user) + {:ok, user} + end + end + + def confirm(%User{} = user), do: {:ok, user} + + def set_suggestion(users, is_suggested) when is_list(users) do + Repo.transaction(fn -> + Enum.map(users, fn user -> + with {:ok, user} <- set_suggestion(user, is_suggested), do: user + end) + end) + end + + def set_suggestion(%User{is_suggested: is_suggested} = user, is_suggested), do: {:ok, user} + + def set_suggestion(%User{} = user, is_suggested) when is_boolean(is_suggested) do + user + |> change(is_suggested: is_suggested) |> update_and_set_cache() end @@ -1594,8 +1711,6 @@ defmodule Pleroma.User do email: nil, name: nil, password_hash: nil, - keys: nil, - public_key: nil, avatar: %{}, tags: [], last_refreshed_at: nil, @@ -1606,17 +1721,14 @@ defmodule Pleroma.User do follower_count: 0, following_count: 0, is_locked: false, - confirmation_pending: false, password_reset_pending: false, - approval_pending: false, registration_reason: nil, confirmation_token: nil, domain_blocks: [], - deactivated: true, + is_active: false, ap_enabled: false, is_moderator: false, is_admin: false, - mastofe_settings: nil, mascot: nil, emoji: %{}, pleroma_settings_store: %{}, @@ -1624,45 +1736,53 @@ defmodule Pleroma.User do raw_fields: [], is_discoverable: false, also_known_as: [] + # id: preserved + # ap_id: preserved + # nickname: preserved }) end + # Purge doesn't delete the user from the database. + # It just nulls all its fields and deactivates it. + # See `User.purge_user_changeset/1` above. + defp purge(%User{} = user) do + user + |> purge_user_changeset() + |> 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 + # Purge the user immediately + purge(user) BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) end - defp delete_and_invalidate_cache(%User{} = user) do + # *Actually* delete the user from the DB + defp delete_from_db(%User{} = user) do invalidate_cache(user) Repo.delete(user) end - defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user) - - defp delete_or_deactivate(%User{local: true} = user) do - status = account_status(user) + # If the user never finalized their account, it's safe to delete them. + defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user), + do: delete_from_db(user) - case status do - :confirmation_pending -> - delete_and_invalidate_cache(user) + defp maybe_delete_from_db(%User{local: true, is_approved: false} = user), + do: delete_from_db(user) - :approval_pending -> - delete_and_invalidate_cache(user) - - _ -> - user - |> purge_user_changeset() - |> update_and_set_cache() - end - end + defp maybe_delete_from_db(user), do: {:ok, user} def perform(:force_password_reset, user), do: force_password_reset(user) @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:delete, %User{} = user) do + # Purge the user again, in case perform/2 is called directly + purge(user) + # Remove all relationships user |> get_followers() @@ -1680,13 +1800,12 @@ defmodule Pleroma.User do delete_user_activities(user) delete_notifications_from_user_activities(user) - delete_outgoing_pending_follow_requests(user) - delete_or_deactivate(user) + maybe_delete_from_db(user) end - def perform(:deactivate_async, user, status), do: deactivate(user, status) + def perform(:set_activation_async, user, status), do: set_activation(user, status) @spec external_users_query() :: Ecto.Query.t() def external_users_query do @@ -1966,6 +2085,15 @@ defmodule Pleroma.User do |> hd() end + def full_nickname(%User{} = user) do + if String.contains?(user.nickname, "@") do + user.nickname + else + %{host: host} = URI.parse(user.ap_id) + user.nickname <> "@" <> host + end + end + def full_nickname(nickname_or_mention), do: String.trim_leading(nickname_or_mention, "@") @@ -1980,7 +2108,7 @@ defmodule Pleroma.User do @spec all_superusers() :: [User.t()] def all_superusers do - User.Query.build(%{super_users: true, local: true, deactivated: false}) + User.Query.build(%{super_users: true, local: true, is_active: true}) |> Repo.all() end @@ -2021,7 +2149,7 @@ defmodule Pleroma.User do left_join: a in Pleroma.Activity, on: u.ap_id == a.actor, where: not is_nil(u.nickname), - where: u.deactivated != ^true, + where: u.is_active == ^true, where: u.id not in ^has_read_notifications, group_by: u.id, having: @@ -2062,22 +2190,10 @@ defmodule Pleroma.User do updated_user end - @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()} - def toggle_confirmation(%User{} = user) do - user - |> confirmation_changeset(need_confirmation: !user.confirmation_pending) - |> update_and_set_cache() - end - - @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}] - def toggle_confirmation(users) do - Enum.map(users, &toggle_confirmation/1) - end - - @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} - def need_confirmation(%User{} = user, bool) do + @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} + def set_confirmation(%User{} = user, bool) do user - |> confirmation_changeset(need_confirmation: bool) + |> confirmation_changeset(set_confirmation: bool) |> update_and_set_cache() end @@ -2123,7 +2239,7 @@ defmodule Pleroma.User do defp put_password_hash( %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset ) do - change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password)) + change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password)) end defp put_password_hash(changeset), do: changeset @@ -2147,16 +2263,16 @@ defmodule Pleroma.User do def change_email(user, email) do user |> cast(%{email: email}, [:email]) - |> validate_required([:email]) + |> maybe_validate_required_email(false) |> unique_constraint(:email) |> validate_format(:email, @email_regex) |> update_and_set_cache() end # Internal function; public one is `deactivate/2` - defp set_activation_status(user, deactivated) do + defp set_activation_status(user, status) do user - |> cast(%{deactivated: deactivated}, [:deactivated]) + |> cast(%{is_active: status}, [:is_active]) |> update_and_set_cache() end @@ -2172,13 +2288,6 @@ defmodule Pleroma.User do |> update_and_set_cache() end - def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do - %{ - admin: is_admin, - moderator: is_moderator - } - end - def validate_fields(changeset, remote? \\ false) do limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields limit = Config.get([:instance, limit_name], 0) @@ -2237,76 +2346,58 @@ defmodule Pleroma.User do |> update_and_set_cache() end - def mastodon_settings_update(user, settings) do - user - |> cast(%{mastofe_settings: settings}, [:mastofe_settings]) - |> validate_required([:mastofe_settings]) - |> update_and_set_cache() - end - @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t() - def confirmation_changeset(user, need_confirmation: need_confirmation?) do + def confirmation_changeset(user, set_confirmation: confirmed?) do params = - if need_confirmation? do + if confirmed? do %{ - confirmation_pending: true, - confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64() + is_confirmed: true, + confirmation_token: nil } else %{ - confirmation_pending: false, - confirmation_token: nil + is_confirmed: false, + confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64() } end - cast(user, params, [:confirmation_pending, :confirmation_token]) + cast(user, params, [:is_confirmed, :confirmation_token]) end @spec approval_changeset(User.t(), keyword()) :: Changeset.t() - def approval_changeset(user, need_approval: need_approval?) do - params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false} - cast(user, params, [:approval_pending]) + def approval_changeset(user, set_approval: approved?) do + cast(user, %{is_approved: approved?}, [:is_approved]) end - def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do - if id not in user.pinned_activities do - max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0) - params = %{pinned_activities: user.pinned_activities ++ [id]} - - # if pinned activity was scheduled for deletion, we remove job - if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do - Oban.cancel_job(expiration.id) - end + @spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()} + def add_pinned_object_id(%User{} = user, object_id) do + if !user.pinned_objects[object_id] do + params = %{pinned_objects: Map.put(user.pinned_objects, object_id, NaiveDateTime.utc_now())} user - |> cast(params, [:pinned_activities]) - |> validate_length(:pinned_activities, - max: max_pinned_statuses, - message: "You have already pinned the maximum number of statuses" - ) + |> cast(params, [:pinned_objects]) + |> validate_change(:pinned_objects, fn :pinned_objects, pinned_objects -> + max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0) + + if Enum.count(pinned_objects) <= max_pinned_statuses do + [] + else + [pinned_objects: "You have already pinned the maximum number of statuses"] + end + end) else change(user) end |> update_and_set_cache() end - def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do - params = %{pinned_activities: List.delete(user.pinned_activities, id)} - - # if pinned activity was scheduled for deletion, we reschedule it for deletion - if data["expires_at"] do - # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation - {:ok, expires_at} = - data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast() - - Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ - activity_id: id, - expires_at: expires_at - }) - end - + @spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()} + def remove_pinned_object_id(%User{} = user, object_id) do user - |> cast(params, [:pinned_activities]) + |> cast( + %{pinned_objects: Map.delete(user.pinned_objects, object_id)}, + [:pinned_objects] + ) |> update_and_set_cache() end @@ -2345,13 +2436,19 @@ defmodule Pleroma.User do @spec add_to_block(User.t(), User.t()) :: {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()} defp add_to_block(%User{} = user, %User{} = blocked) do - UserRelationship.create_block(user, blocked) + with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do + @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}") + {:ok, relationship} + end end @spec add_to_block(User.t(), User.t()) :: {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()} defp remove_from_block(%User{} = user, %User{} = blocked) do - UserRelationship.delete_block(user, blocked) + with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do + @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}") + {:ok, relationship} + end end def set_invisible(user, invisible) do @@ -2383,4 +2480,23 @@ defmodule Pleroma.User do |> Map.put(:bio, HTML.filter_tags(user.bio, filter)) |> Map.put(:fields, fields) end + + def get_host(%User{ap_id: ap_id} = _user) do + URI.parse(ap_id).host + end + + def update_last_active_at(%__MODULE__{local: true} = user) do + user + |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at]) + |> update_and_set_cache() + end + + def active_user_count(days \\ 30) do + active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days) + + __MODULE__ + |> where([u], u.last_active_at >= ^active_after) + |> where([u], u.local == true) + |> Repo.aggregate(:count) + end end