X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fuser.ex;h=6abcb7288f5f3237a80cb212409656430cc153ef;hb=ad5263c647aea65dbeb4c329825671895e0a8863;hp=427400aa17764bc548233af00531af39786c1d7c;hpb=16cb9e5f1cae84322bd7953e58b438f3b4bd8b9c;p=akkoma diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 427400aa1..6abcb7288 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -10,6 +10,7 @@ defmodule Pleroma.User do alias Comeonin.Pbkdf2 alias Pleroma.Activity + alias Pleroma.Keys alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Registration @@ -55,7 +56,7 @@ defmodule Pleroma.User do field(:last_refreshed_at, :naive_datetime_usec) has_many(:notifications, Notification) has_many(:registrations, Registration) - embeds_one(:info, Pleroma.User.Info) + embeds_one(:info, User.Info) timestamps() end @@ -105,10 +106,8 @@ defmodule Pleroma.User do def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers" def user_info(%User{} = user) do - oneself = if user.local, do: 1, else: 0 - %{ - following_count: length(user.following) - oneself, + following_count: following_count(user), note_count: user.info.note_count, follower_count: user.info.follower_count, locked: user.info.locked, @@ -117,6 +116,20 @@ defmodule Pleroma.User do } end + def 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{} = user) do + user + |> get_friends_query() + |> Repo.aggregate(:count, :id) + end + def remote_user_creation(params) do params = params @@ -154,7 +167,7 @@ defmodule Pleroma.User do def update_changeset(struct, params \\ %{}) do struct - |> cast(params, [:bio, :name, :avatar]) + |> cast(params, [:bio, :name, :avatar, :following]) |> unique_constraint(:nickname) |> validate_format(:nickname, local_nickname_regex()) |> validate_length(:bio, max: 5000) @@ -204,14 +217,15 @@ defmodule Pleroma.User do end def register_changeset(struct, params \\ %{}, opts \\ []) do - confirmation_status = - if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do - :confirmed + need_confirmation? = + if is_nil(opts[:need_confirmation]) do + Pleroma.Config.get([:instance, :account_activation_required]) else - :unconfirmed + opts[:need_confirmation] end - info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status) + info_change = + User.Info.confirmation_changeset(%User.Info{}, need_confirmation: need_confirmation?) changeset = struct @@ -220,7 +234,7 @@ defmodule Pleroma.User do |> validate_confirmation(:password) |> unique_constraint(:email) |> unique_constraint(:nickname) - |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames])) + |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames])) |> validate_format(:nickname, local_nickname_regex()) |> validate_format(:email, @email_regex) |> validate_length(:bio, max: 1000) @@ -254,7 +268,7 @@ defmodule Pleroma.User do candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames]) autofollowed_users = - User.Query.build(%{nickname: candidates, local: true}) + User.Query.build(%{nickname: candidates, local: true, deactivated: false}) |> Repo.all() follow_all(user, autofollowed_users) @@ -263,9 +277,15 @@ defmodule Pleroma.User do @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)" def register(%Ecto.Changeset{} = changeset) do with {:ok, user} <- Repo.insert(changeset), - {:ok, user} <- autofollow_users(user), + {:ok, user} <- post_register_action(user) do + {:ok, user} + end + end + + def post_register_action(%User{} = user) do + with {:ok, user} <- autofollow_users(user), {:ok, user} <- set_cache(user), - {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user), + {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user), {:ok, _} <- try_send_confirmation_email(user) do {:ok, user} end @@ -352,9 +372,7 @@ defmodule Pleroma.User do 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) - + deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) ap_followers = followed.follower_address cond do @@ -412,24 +430,6 @@ defmodule Pleroma.User do Enum.member?(follower.following, followed.follower_address) end - def follow_import(%User{} = follower, followed_identifiers) - when is_list(followed_identifiers) do - Enum.map( - followed_identifiers, - fn followed_identifier -> - with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier), - {:ok, follower} <- maybe_direct_follow(follower, followed), - {:ok, _} <- ActivityPub.follow(follower, followed) do - followed - else - err -> - Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}") - err - end - end - ) - end - def locked?(%User{} = user) do user.info.locked || false end @@ -551,8 +551,7 @@ defmodule Pleroma.User do with [_nick, _domain] <- String.split(nickname, "@"), {:ok, user} <- fetch_by_nickname(nickname) do if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do - # TODO turn into job - {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user]) + fetch_initial_posts(user) end {:ok, user} @@ -563,19 +562,12 @@ defmodule Pleroma.User do end @doc "Fetch some posts when the user has just been federated with" - def fetch_initial_posts(user) do - pages = Pleroma.Config.get!([:fetch_initial_posts, :pages]) - - Enum.each( - # Insert all the posts in reverse order, so they're in the right order on the timeline - Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)), - &Pleroma.Web.Federator.incoming_ap_doc/1 - ) - end + def fetch_initial_posts(user), + do: PleromaJobQueue.enqueue(:background, __MODULE__, [:fetch_initial_posts, user]) @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 @@ -600,7 +592,7 @@ defmodule Pleroma.User do @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 @@ -690,16 +682,16 @@ defmodule Pleroma.User do info_cng = User.Info.set_note_count(user.info, note_count) - cng = - change(user) - |> put_embed(:info, info_cng) - - update_and_set_cache(cng) + user + |> change() + |> put_embed(:info, info_cng) + |> update_and_set_cache() end def update_follower_count(%User{} = user) do follower_count_query = - User.Query.build(%{followers: user}) |> select([u], %{count: count(u.id)}) + User.Query.build(%{followers: user, deactivated: false}) + |> select([u], %{count: count(u.id)}) User |> where(id: ^user.id) @@ -722,9 +714,21 @@ defmodule Pleroma.User do end end + 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 + @spec get_users_from_set([String.t()], boolean()) :: [User.t()] def get_users_from_set(ap_ids, local_only \\ true) do - 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) @@ -733,7 +737,7 @@ defmodule Pleroma.User do @spec get_recipients_from_activity(Activity.t()) :: [User.t()] def get_recipients_from_activity(%Activity{recipients: to}) do - User.Query.build(%{recipients_from_activity: to, local: true}) + User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false}) |> Repo.all() end @@ -760,7 +764,7 @@ defmodule Pleroma.User do from(s in subquery(boost_search_rank_query(distinct_query, for_user)), order_by: [desc: s.search_rank], - limit: 20 + limit: 40 ) end @@ -831,6 +835,7 @@ defmodule Pleroma.User do ^processed_query ) ) + |> restrict_deactivated() end defp trigram_search_subquery(term) do @@ -849,23 +854,7 @@ defmodule Pleroma.User do }, where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term) ) - end - - def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do - Enum.map( - blocked_identifiers, - fn blocked_identifier -> - with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier), - {:ok, blocker} <- block(blocker, blocked), - {:ok, _} <- ActivityPub.block(blocker, blocked) do - blocked - else - err -> - Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}") - err - end - end - ) + |> restrict_deactivated() end def mute(muter, %User{ap_id: ap_id}) do @@ -998,19 +987,19 @@ defmodule Pleroma.User do @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 @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 @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 @@ -1038,14 +1027,25 @@ defmodule Pleroma.User do update_and_set_cache(cng) end + def deactivate_async(user, status \\ true) do + PleromaJobQueue.enqueue(:background, __MODULE__, [:deactivate_async, user, status]) + end + def deactivate(%User{} = user, status \\ true) do info_cng = User.Info.set_activation_status(user.info, status) - cng = - change(user) - |> put_embed(:info, info_cng) + with {:ok, friends} <- User.get_friends(user), + {:ok, followers} <- User.get_followers(user), + {:ok, user} <- + user + |> change() + |> put_embed(:info, info_cng) + |> update_and_set_cache() do + Enum.each(followers, &invalidate_cache(&1)) + Enum.each(friends, &update_follower_count(&1)) - update_and_set_cache(cng) + {:ok, user} + end end def update_notification_settings(%User{} = user, settings \\ %{}) do @@ -1076,11 +1076,79 @@ defmodule Pleroma.User do delete_user_activities(user) end + @spec perform(atom(), User.t()) :: {:ok, User.t()} + def perform(:fetch_initial_posts, %User{} = user) do + pages = Pleroma.Config.get!([:fetch_initial_posts, :pages]) + + Enum.each( + # Insert all the posts in reverse order, so they're in the right order on the timeline + Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)), + &Pleroma.Web.Federator.incoming_ap_doc/1 + ) + + {:ok, user} + end + + def perform(:deactivate_async, user, status), do: deactivate(user, status) + + @spec perform(atom(), User.t(), list()) :: list() | {:error, any()} + def perform(:blocks_import, %User{} = blocker, blocked_identifiers) + when is_list(blocked_identifiers) do + Enum.map( + blocked_identifiers, + fn blocked_identifier -> + with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier), + {:ok, blocker} <- block(blocker, blocked), + {:ok, _} <- ActivityPub.block(blocker, blocked) do + blocked + else + err -> + Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}") + err + end + end + ) + end + + @spec perform(atom(), User.t(), list()) :: list() | {:error, any()} + def perform(:follow_import, %User{} = follower, followed_identifiers) + when is_list(followed_identifiers) do + Enum.map( + followed_identifiers, + fn followed_identifier -> + with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier), + {:ok, follower} <- maybe_direct_follow(follower, followed), + {:ok, _} <- ActivityPub.follow(follower, followed) do + followed + else + err -> + Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}") + err + end + end + ) + end + + def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers), + do: + PleromaJobQueue.enqueue(:background, __MODULE__, [ + :blocks_import, + blocker, + blocked_identifiers + ]) + + def follow_import(%User{} = follower, followed_identifiers) when is_list(followed_identifiers), + do: + PleromaJobQueue.enqueue(:background, __MODULE__, [ + :follow_import, + follower, + followed_identifiers + ]) + def delete_user_activities(%User{ap_id: ap_id} = user) do stream = ap_id |> Activity.query_by_actor() - |> Activity.with_preloaded_object() |> Repo.stream() Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity) @@ -1129,8 +1197,8 @@ defmodule Pleroma.User do resp = fetch_by_ap_id(ap_id) if should_fetch_initial do - with {:ok, %User{} = user} = resp do - {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user]) + with {:ok, %User{} = user} <- resp do + fetch_initial_posts(user) end end @@ -1319,11 +1387,64 @@ defmodule Pleroma.User do @spec all_superusers() :: [User.t()] def all_superusers do - User.Query.build(%{super_users: true, local: true}) + User.Query.build(%{super_users: true, local: true, deactivated: false}) |> Repo.all() end def showing_reblogs?(%User{} = user, %User{} = target) do target.ap_id not in user.info.muted_reblogs end + + @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()} + def toggle_confirmation(%User{} = user) do + need_confirmation? = !user.info.confirmation_pending + + info_changeset = + User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?) + + user + |> change() + |> put_embed(:info, info_changeset) + |> update_and_set_cache() + end + + def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do + mascot + end + + def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do + # use instance-default + config = Pleroma.Config.get([:assets, :mascots]) + default_mascot = Pleroma.Config.get([:assets, :default_mascot]) + mascot = Keyword.get(config, default_mascot) + + %{ + "id" => "default-mascot", + "url" => mascot[:url], + "preview_url" => mascot[:url], + "pleroma" => %{ + "mime_type" => mascot[:mime_type] + } + } + end + + def ensure_keys_present(user) do + info = user.info + + if info.keys do + {:ok, user} + else + {:ok, pem} = Keys.generate_rsa_pem() + + info_cng = + info + |> User.Info.set_keys(pem) + + cng = + Ecto.Changeset.change(user) + |> Ecto.Changeset.put_embed(:info, info_cng) + + update_and_set_cache(cng) + end + end end