1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User do
13 alias Pleroma.Activity
14 alias Pleroma.Conversation.Participation
15 alias Pleroma.Delivery
16 alias Pleroma.FollowingRelationship
18 alias Pleroma.Notification
20 alias Pleroma.Registration
22 alias Pleroma.RepoStreamer
25 alias Pleroma.Web.ActivityPub.ActivityPub
26 alias Pleroma.Web.ActivityPub.Utils
27 alias Pleroma.Web.CommonAPI
28 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
29 alias Pleroma.Web.OAuth
30 alias Pleroma.Web.RelMe
31 alias Pleroma.Workers.BackgroundWorker
35 @type t :: %__MODULE__{}
37 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
39 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
40 @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
42 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
43 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
47 field(:email, :string)
49 field(:nickname, :string)
50 field(:password_hash, :string)
51 field(:password, :string, virtual: true)
52 field(:password_confirmation, :string, virtual: true)
54 field(:ap_id, :string)
56 field(:local, :boolean, default: true)
57 field(:follower_address, :string)
58 field(:following_address, :string)
59 field(:search_rank, :float, virtual: true)
60 field(:search_type, :integer, virtual: true)
61 field(:tags, {:array, :string}, default: [])
62 field(:last_refreshed_at, :naive_datetime_usec)
63 field(:last_digest_emailed_at, :naive_datetime)
65 field(:banner, :map, default: %{})
66 field(:background, :map, default: %{})
67 field(:source_data, :map, default: %{})
68 field(:note_count, :integer, default: 0)
69 field(:follower_count, :integer, default: 0)
70 # Should be filled in only for remote users
71 field(:following_count, :integer, default: nil)
72 field(:locked, :boolean, default: false)
73 field(:confirmation_pending, :boolean, default: false)
74 field(:password_reset_pending, :boolean, default: false)
75 field(:confirmation_token, :string, default: nil)
76 field(:default_scope, :string, default: "public")
77 field(:blocks, {:array, :string}, default: [])
78 field(:domain_blocks, {:array, :string}, default: [])
79 field(:mutes, {:array, :string}, default: [])
80 field(:muted_reblogs, {:array, :string}, default: [])
81 field(:muted_notifications, {:array, :string}, default: [])
82 field(:subscribers, {:array, :string}, default: [])
83 field(:deactivated, :boolean, default: false)
84 field(:no_rich_text, :boolean, default: false)
85 field(:ap_enabled, :boolean, default: false)
86 field(:is_moderator, :boolean, default: false)
87 field(:is_admin, :boolean, default: false)
88 field(:show_role, :boolean, default: true)
89 field(:settings, :map, default: nil)
90 field(:magic_key, :string, default: nil)
91 field(:uri, :string, default: nil)
92 field(:hide_followers_count, :boolean, default: false)
93 field(:hide_follows_count, :boolean, default: false)
94 field(:hide_followers, :boolean, default: false)
95 field(:hide_follows, :boolean, default: false)
96 field(:hide_favorites, :boolean, default: true)
97 field(:unread_conversation_count, :integer, default: 0)
98 field(:pinned_activities, {:array, :string}, default: [])
99 field(:email_notifications, :map, default: %{"digest" => false})
100 field(:mascot, :map, default: nil)
101 field(:emoji, {:array, :map}, default: [])
102 field(:pleroma_settings_store, :map, default: %{})
103 field(:fields, {:array, :map}, default: [])
104 field(:raw_fields, {:array, :map}, default: [])
105 field(:discoverable, :boolean, default: false)
106 field(:invisible, :boolean, default: false)
107 field(:allow_following_move, :boolean, default: true)
108 field(:skip_thread_containment, :boolean, default: false)
109 field(:also_known_as, {:array, :string}, default: [])
111 field(:notification_settings, :map,
115 "non_follows" => true,
116 "non_followers" => true
120 has_many(:notifications, Notification)
121 has_many(:registrations, Registration)
122 has_many(:deliveries, Delivery)
124 field(:info, :map, default: %{})
129 def auth_active?(%User{confirmation_pending: true}),
130 do: !Pleroma.Config.get([:instance, :account_activation_required])
132 def auth_active?(%User{}), do: true
134 def visible_for?(user, for_user \\ nil)
136 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
138 def visible_for?(%User{} = user, for_user) do
139 auth_active?(user) || superuser?(for_user)
142 def visible_for?(_, _), do: false
144 def superuser?(%User{local: true, is_admin: true}), do: true
145 def superuser?(%User{local: true, is_moderator: true}), do: true
146 def superuser?(_), do: false
148 def invisible?(%User{invisible: true}), do: true
149 def invisible?(_), do: false
151 def avatar_url(user, options \\ []) do
153 %{"url" => [%{"href" => href} | _]} -> href
154 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
158 def banner_url(user, options \\ []) do
160 %{"url" => [%{"href" => href} | _]} -> href
161 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
165 def profile_url(%User{source_data: %{"url" => url}}), do: url
166 def profile_url(%User{ap_id: ap_id}), do: ap_id
167 def profile_url(_), do: nil
169 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
171 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
172 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
174 @spec ap_following(User.t()) :: Sring.t()
175 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
176 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
178 def user_info(%User{} = user, args \\ %{}) do
180 Map.get(args, :following_count, user.following_count || following_count(user))
182 follower_count = Map.get(args, :follower_count, user.follower_count)
185 note_count: user.note_count,
187 confirmation_pending: user.confirmation_pending,
188 default_scope: user.default_scope
190 |> Map.put(:following_count, following_count)
191 |> Map.put(:follower_count, follower_count)
194 def follow_state(%User{} = user, %User{} = target) do
195 case Utils.fetch_latest_follow(user, target) do
196 %{data: %{"state" => state}} -> state
197 # Ideally this would be nil, but then Cachex does not commit the value
202 def get_cached_follow_state(user, target) do
203 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
204 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
207 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
208 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
209 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
212 def set_info_cache(user, args) do
213 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
216 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
217 def restrict_deactivated(query) do
218 from(u in query, where: u.deactivated != ^true)
221 defdelegate following_count(user), to: FollowingRelationship
223 defp truncate_fields_param(params) do
224 if Map.has_key?(params, :fields) do
225 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
231 defp truncate_if_exists(params, key, max_length) do
232 if Map.has_key?(params, key) and is_binary(params[key]) do
233 {value, _chopped} = String.split_at(params[key], max_length)
234 Map.put(params, key, value)
240 def remote_user_creation(params) do
241 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
242 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
246 |> Map.put(:info, params[:info] || %{})
247 |> truncate_if_exists(:name, name_limit)
248 |> truncate_if_exists(:bio, bio_limit)
249 |> truncate_fields_param()
269 :hide_followers_count,
279 |> validate_required([:name, :ap_id])
280 |> unique_constraint(:nickname)
281 |> validate_format(:nickname, @email_regex)
282 |> validate_length(:bio, max: bio_limit)
283 |> validate_length(:name, max: name_limit)
284 |> validate_fields(true)
286 case params[:source_data] do
287 %{"followers" => followers, "following" => following} ->
289 |> put_change(:follower_address, followers)
290 |> put_change(:following_address, following)
293 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
294 put_change(changeset, :follower_address, followers)
298 def update_changeset(struct, params \\ %{}) do
299 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
300 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
315 :hide_followers_count,
318 :allow_following_move,
321 :skip_thread_containment,
324 :pleroma_settings_store,
329 |> unique_constraint(:nickname)
330 |> validate_format(:nickname, local_nickname_regex())
331 |> validate_length(:bio, max: bio_limit)
332 |> validate_length(:name, min: 1, max: name_limit)
333 |> validate_fields(false)
336 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
337 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
338 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
340 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
342 params = if remote?, do: truncate_fields_param(params), else: params
364 :allow_following_move,
366 :hide_followers_count,
371 |> unique_constraint(:nickname)
372 |> validate_format(:nickname, local_nickname_regex())
373 |> validate_length(:bio, max: bio_limit)
374 |> validate_length(:name, max: name_limit)
375 |> validate_fields(remote?)
378 def password_update_changeset(struct, params) do
380 |> cast(params, [:password, :password_confirmation])
381 |> validate_required([:password, :password_confirmation])
382 |> validate_confirmation(:password)
383 |> put_password_hash()
384 |> put_change(:password_reset_pending, false)
387 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
388 def reset_password(%User{id: user_id} = user, data) do
391 |> Multi.update(:user, password_update_changeset(user, data))
392 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
393 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
395 case Repo.transaction(multi) do
396 {:ok, %{user: user} = _} -> set_cache(user)
397 {:error, _, changeset, _} -> {:error, changeset}
401 def update_password_reset_pending(user, value) do
404 |> put_change(:password_reset_pending, value)
405 |> update_and_set_cache()
408 def force_password_reset_async(user) do
409 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
412 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
413 def force_password_reset(user), do: update_password_reset_pending(user, true)
415 def register_changeset(struct, params \\ %{}, opts \\ []) do
416 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
417 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
420 if is_nil(opts[:need_confirmation]) do
421 Pleroma.Config.get([:instance, :account_activation_required])
423 opts[:need_confirmation]
427 |> confirmation_changeset(need_confirmation: need_confirmation?)
428 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
429 |> validate_required([:name, :nickname, :password, :password_confirmation])
430 |> validate_confirmation(:password)
431 |> unique_constraint(:email)
432 |> unique_constraint(:nickname)
433 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
434 |> validate_format(:nickname, local_nickname_regex())
435 |> validate_format(:email, @email_regex)
436 |> validate_length(:bio, max: bio_limit)
437 |> validate_length(:name, min: 1, max: name_limit)
438 |> maybe_validate_required_email(opts[:external])
441 |> unique_constraint(:ap_id)
442 |> put_following_and_follower_address()
445 def maybe_validate_required_email(changeset, true), do: changeset
446 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
448 defp put_ap_id(changeset) do
449 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
450 put_change(changeset, :ap_id, ap_id)
453 defp put_following_and_follower_address(changeset) do
454 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
457 |> put_change(:follower_address, followers)
460 defp autofollow_users(user) do
461 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
464 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
467 follow_all(user, autofollowed_users)
470 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
471 def register(%Ecto.Changeset{} = changeset) do
472 with {:ok, user} <- Repo.insert(changeset) do
473 post_register_action(user)
477 def post_register_action(%User{} = user) do
478 with {:ok, user} <- autofollow_users(user),
479 {:ok, user} <- set_cache(user),
480 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
481 {:ok, _} <- try_send_confirmation_email(user) do
486 def try_send_confirmation_email(%User{} = user) do
487 if user.confirmation_pending &&
488 Pleroma.Config.get([:instance, :account_activation_required]) do
490 |> Pleroma.Emails.UserEmail.account_confirmation_email()
491 |> Pleroma.Emails.Mailer.deliver_async()
499 def needs_update?(%User{local: true}), do: false
501 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
503 def needs_update?(%User{local: false} = user) do
504 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
507 def needs_update?(_), do: true
509 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
510 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
511 follow(follower, followed, "pending")
514 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
515 follow(follower, followed)
518 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
519 if not ap_enabled?(followed) do
520 follow(follower, followed)
526 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
527 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
528 def follow_all(follower, followeds) do
530 Enum.reject(followeds, fn followed ->
531 blocks?(follower, followed) || blocks?(followed, follower)
534 Enum.each(followeds, &follow(follower, &1, "accept"))
536 Enum.each(followeds, &update_follower_count/1)
541 defdelegate following(user), to: FollowingRelationship
543 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
544 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
547 followed.deactivated ->
548 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
550 deny_follow_blocked and blocks?(followed, follower) ->
551 {:error, "Could not follow user: #{followed.nickname} blocked you."}
554 FollowingRelationship.follow(follower, followed, state)
556 follower = maybe_update_following_count(follower)
558 {:ok, _} = update_follower_count(followed)
564 def unfollow(%User{} = follower, %User{} = followed) do
565 if following?(follower, followed) and follower.ap_id != followed.ap_id do
566 FollowingRelationship.unfollow(follower, followed)
568 follower = maybe_update_following_count(follower)
570 {:ok, followed} = update_follower_count(followed)
574 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
576 {:error, "Not subscribed!"}
580 defdelegate following?(follower, followed), to: FollowingRelationship
582 def locked?(%User{} = user) do
587 Repo.get_by(User, id: id)
590 def get_by_ap_id(ap_id) do
591 Repo.get_by(User, ap_id: ap_id)
594 def get_all_by_ap_id(ap_ids) do
595 from(u in __MODULE__,
596 where: u.ap_id in ^ap_ids
601 def get_all_by_ids(ids) do
602 from(u in __MODULE__, where: u.id in ^ids)
606 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
607 # of the ap_id and the domain and tries to get that user
608 def get_by_guessed_nickname(ap_id) do
609 domain = URI.parse(ap_id).host
610 name = List.last(String.split(ap_id, "/"))
611 nickname = "#{name}@#{domain}"
613 get_cached_by_nickname(nickname)
616 def set_cache({:ok, user}), do: set_cache(user)
617 def set_cache({:error, err}), do: {:error, err}
619 def set_cache(%User{} = user) do
620 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
621 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
622 Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
626 def update_and_set_cache(struct, params) do
628 |> update_changeset(params)
629 |> update_and_set_cache()
632 def update_and_set_cache(changeset) do
633 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
638 def invalidate_cache(user) do
639 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
640 Cachex.del(:user_cache, "nickname:#{user.nickname}")
641 Cachex.del(:user_cache, "user_info:#{user.id}")
644 def get_cached_by_ap_id(ap_id) do
645 key = "ap_id:#{ap_id}"
646 Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
649 def get_cached_by_id(id) do
653 Cachex.fetch!(:user_cache, key, fn _ ->
657 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
658 {:commit, user.ap_id}
664 get_cached_by_ap_id(ap_id)
667 def get_cached_by_nickname(nickname) do
668 key = "nickname:#{nickname}"
670 Cachex.fetch!(:user_cache, key, fn ->
671 case get_or_fetch_by_nickname(nickname) do
672 {:ok, user} -> {:commit, user}
673 {:error, _error} -> {:ignore, nil}
678 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
679 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
682 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
683 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
685 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
686 get_cached_by_nickname(nickname_or_id)
688 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
689 get_cached_by_nickname(nickname_or_id)
696 def get_by_nickname(nickname) do
697 Repo.get_by(User, nickname: nickname) ||
698 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
699 Repo.get_by(User, nickname: local_nickname(nickname))
703 def get_by_email(email), do: Repo.get_by(User, email: email)
705 def get_by_nickname_or_email(nickname_or_email) do
706 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
709 def get_cached_user_info(user) do
710 key = "user_info:#{user.id}"
711 Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
714 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
716 def get_or_fetch_by_nickname(nickname) do
717 with %User{} = user <- get_by_nickname(nickname) do
721 with [_nick, _domain] <- String.split(nickname, "@"),
722 {:ok, user} <- fetch_by_nickname(nickname) do
723 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
724 fetch_initial_posts(user)
729 _e -> {:error, "not found " <> nickname}
734 @doc "Fetch some posts when the user has just been federated with"
735 def fetch_initial_posts(user) do
736 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
739 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
740 def get_followers_query(%User{} = user, nil) do
741 User.Query.build(%{followers: user, deactivated: false})
744 def get_followers_query(user, page) do
746 |> get_followers_query(nil)
747 |> User.Query.paginate(page, 20)
750 @spec get_followers_query(User.t()) :: Ecto.Query.t()
751 def get_followers_query(user), do: get_followers_query(user, nil)
753 @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
754 def get_followers(user, page \\ nil) do
756 |> get_followers_query(page)
760 @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
761 def get_external_followers(user, page \\ nil) do
763 |> get_followers_query(page)
764 |> User.Query.build(%{external: true})
768 def get_followers_ids(user, page \\ nil) do
770 |> get_followers_query(page)
775 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
776 def get_friends_query(%User{} = user, nil) do
777 User.Query.build(%{friends: user, deactivated: false})
780 def get_friends_query(user, page) do
782 |> get_friends_query(nil)
783 |> User.Query.paginate(page, 20)
786 @spec get_friends_query(User.t()) :: Ecto.Query.t()
787 def get_friends_query(user), do: get_friends_query(user, nil)
789 def get_friends(user, page \\ nil) do
791 |> get_friends_query(page)
795 def get_friends_ids(user, page \\ nil) do
797 |> get_friends_query(page)
802 defdelegate get_follow_requests(user), to: FollowingRelationship
804 def increase_note_count(%User{} = user) do
806 |> where(id: ^user.id)
807 |> update([u], inc: [note_count: 1])
809 |> Repo.update_all([])
811 {1, [user]} -> set_cache(user)
816 def decrease_note_count(%User{} = user) do
818 |> where(id: ^user.id)
821 note_count: fragment("greatest(0, note_count - 1)")
825 |> Repo.update_all([])
827 {1, [user]} -> set_cache(user)
832 def update_note_count(%User{} = user, note_count \\ nil) do
837 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
843 |> cast(%{note_count: note_count}, [:note_count])
844 |> update_and_set_cache()
847 @spec maybe_fetch_follow_information(User.t()) :: User.t()
848 def maybe_fetch_follow_information(user) do
849 with {:ok, user} <- fetch_follow_information(user) do
853 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
859 def fetch_follow_information(user) do
860 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
862 |> follow_information_changeset(info)
863 |> update_and_set_cache()
867 defp follow_information_changeset(user, params) do
874 :hide_followers_count,
879 def update_follower_count(%User{} = user) do
880 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
881 follower_count_query =
882 User.Query.build(%{followers: user, deactivated: false})
883 |> select([u], %{count: count(u.id)})
886 |> where(id: ^user.id)
887 |> join(:inner, [u], s in subquery(follower_count_query))
889 set: [follower_count: s.count]
892 |> Repo.update_all([])
894 {1, [user]} -> set_cache(user)
898 {:ok, maybe_fetch_follow_information(user)}
902 @spec maybe_update_following_count(User.t()) :: User.t()
903 def maybe_update_following_count(%User{local: false} = user) do
904 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
905 maybe_fetch_follow_information(user)
911 def maybe_update_following_count(user), do: user
913 def set_unread_conversation_count(%User{local: true} = user) do
914 unread_query = Participation.unread_conversation_count_for_user(user)
917 |> join(:inner, [u], p in subquery(unread_query))
919 set: [unread_conversation_count: p.count]
921 |> where([u], u.id == ^user.id)
923 |> Repo.update_all([])
925 {1, [user]} -> set_cache(user)
930 def set_unread_conversation_count(user), do: {:ok, user}
932 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
934 Participation.unread_conversation_count_for_user(user)
935 |> where([p], p.conversation_id == ^conversation.id)
938 |> join(:inner, [u], p in subquery(unread_query))
940 inc: [unread_conversation_count: 1]
942 |> where([u], u.id == ^user.id)
943 |> where([u, p], p.count == 0)
945 |> Repo.update_all([])
947 {1, [user]} -> set_cache(user)
952 def increment_unread_conversation_count(_, user), do: {:ok, user}
954 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
955 def get_users_from_set(ap_ids, local_only \\ true) do
956 criteria = %{ap_id: ap_ids, deactivated: false}
957 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
959 User.Query.build(criteria)
963 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
964 def get_recipients_from_activity(%Activity{recipients: to}) do
965 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
969 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
970 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
971 add_to_mutes(muter, ap_id, notifications?)
974 def unmute(muter, %{ap_id: ap_id}) do
975 remove_from_mutes(muter, ap_id)
978 def subscribe(subscriber, %{ap_id: ap_id}) do
979 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
980 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
982 if blocks?(subscribed, subscriber) and deny_follow_blocked do
983 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
985 User.add_to_subscribers(subscribed, subscriber.ap_id)
990 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
991 with %User{} = user <- get_cached_by_ap_id(ap_id) do
992 User.remove_from_subscribers(user, unsubscriber.ap_id)
996 def block(blocker, %User{ap_id: ap_id} = blocked) do
997 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
999 if following?(blocker, blocked) do
1000 {:ok, blocker, _} = unfollow(blocker, blocked)
1006 # clear any requested follows as well
1008 case CommonAPI.reject_follow_request(blocked, blocker) do
1009 {:ok, %User{} = updated_blocked} -> updated_blocked
1014 if subscribed_to?(blocked, blocker) do
1015 {:ok, blocker} = unsubscribe(blocked, blocker)
1021 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1023 {:ok, blocker} = update_follower_count(blocker)
1024 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1025 add_to_block(blocker, ap_id)
1028 # helper to handle the block given only an actor's AP id
1029 def block(blocker, %{ap_id: ap_id}) do
1030 block(blocker, get_cached_by_ap_id(ap_id))
1033 def unblock(blocker, %{ap_id: ap_id}) do
1034 remove_from_block(blocker, ap_id)
1037 def mutes?(nil, _), do: false
1038 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1040 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1041 def muted_notifications?(nil, _), do: false
1043 def muted_notifications?(user, %{ap_id: ap_id}),
1044 do: Enum.member?(user.muted_notifications, ap_id)
1046 def blocks?(%User{} = user, %User{} = target) do
1047 blocks_ap_id?(user, target) || blocks_domain?(user, target)
1050 def blocks?(nil, _), do: false
1052 def blocks_ap_id?(%User{} = user, %User{} = target) do
1053 Enum.member?(user.blocks, target.ap_id)
1056 def blocks_ap_id?(_, _), do: false
1058 def blocks_domain?(%User{} = user, %User{} = target) do
1059 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1060 %{host: host} = URI.parse(target.ap_id)
1061 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1064 def blocks_domain?(_, _), do: false
1066 def subscribed_to?(user, %{ap_id: ap_id}) do
1067 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1068 Enum.member?(target.subscribers, user.ap_id)
1072 @spec muted_users(User.t()) :: [User.t()]
1073 def muted_users(user) do
1074 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1078 @spec blocked_users(User.t()) :: [User.t()]
1079 def blocked_users(user) do
1080 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1084 @spec subscribers(User.t()) :: [User.t()]
1085 def subscribers(user) do
1086 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1090 def deactivate_async(user, status \\ true) do
1091 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1094 def deactivate(user, status \\ true)
1096 def deactivate(users, status) when is_list(users) do
1097 Repo.transaction(fn ->
1098 for user <- users, do: deactivate(user, status)
1102 def deactivate(%User{} = user, status) do
1103 with {:ok, user} <- set_activation_status(user, status) do
1104 Enum.each(get_followers(user), &invalidate_cache/1)
1105 Enum.each(get_friends(user), &update_follower_count/1)
1111 def update_notification_settings(%User{} = user, settings) do
1114 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1117 notification_settings =
1118 user.notification_settings
1119 |> Map.merge(settings)
1120 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1122 params = %{notification_settings: notification_settings}
1125 |> cast(params, [:notification_settings])
1126 |> validate_required([:notification_settings])
1127 |> update_and_set_cache()
1130 def delete(users) when is_list(users) do
1131 for user <- users, do: delete(user)
1134 def delete(%User{} = user) do
1135 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1138 def perform(:force_password_reset, user), do: force_password_reset(user)
1140 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1141 def perform(:delete, %User{} = user) do
1142 {:ok, _user} = ActivityPub.delete(user)
1144 # Remove all relationships
1147 |> Enum.each(fn follower ->
1148 ActivityPub.unfollow(follower, user)
1149 unfollow(follower, user)
1154 |> Enum.each(fn followed ->
1155 ActivityPub.unfollow(user, followed)
1156 unfollow(user, followed)
1159 delete_user_activities(user)
1160 invalidate_cache(user)
1164 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1165 def perform(:fetch_initial_posts, %User{} = user) do
1166 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1168 # Insert all the posts in reverse order, so they're in the right order on the timeline
1169 user.source_data["outbox"]
1170 |> Utils.fetch_ordered_collection(pages)
1172 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1175 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1177 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1178 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1179 when is_list(blocked_identifiers) do
1181 blocked_identifiers,
1182 fn blocked_identifier ->
1183 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1184 {:ok, blocker} <- block(blocker, blocked),
1185 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1189 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1196 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1197 def perform(:follow_import, %User{} = follower, followed_identifiers)
1198 when is_list(followed_identifiers) do
1200 followed_identifiers,
1201 fn followed_identifier ->
1202 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1203 {:ok, follower} <- maybe_direct_follow(follower, followed),
1204 {:ok, _} <- ActivityPub.follow(follower, followed) do
1208 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1215 @spec external_users_query() :: Ecto.Query.t()
1216 def external_users_query do
1224 @spec external_users(keyword()) :: [User.t()]
1225 def external_users(opts \\ []) do
1227 external_users_query()
1228 |> select([u], struct(u, [:id, :ap_id, :info]))
1232 do: where(query, [u], u.id > ^opts[:max_id]),
1237 do: limit(query, ^opts[:limit]),
1243 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1244 BackgroundWorker.enqueue("blocks_import", %{
1245 "blocker_id" => blocker.id,
1246 "blocked_identifiers" => blocked_identifiers
1250 def follow_import(%User{} = follower, followed_identifiers)
1251 when is_list(followed_identifiers) do
1252 BackgroundWorker.enqueue("follow_import", %{
1253 "follower_id" => follower.id,
1254 "followed_identifiers" => followed_identifiers
1258 def delete_user_activities(%User{ap_id: ap_id}) do
1260 |> Activity.Queries.by_actor()
1261 |> RepoStreamer.chunk_stream(50)
1262 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1266 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1268 |> Object.normalize()
1269 |> ActivityPub.delete()
1272 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1273 object = Object.normalize(activity)
1276 |> get_cached_by_ap_id()
1277 |> ActivityPub.unlike(object)
1280 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1281 object = Object.normalize(activity)
1284 |> get_cached_by_ap_id()
1285 |> ActivityPub.unannounce(object)
1288 defp delete_activity(_activity), do: "Doing nothing"
1290 def html_filter_policy(%User{no_rich_text: true}) do
1291 Pleroma.HTML.Scrubber.TwitterText
1294 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1296 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1298 def get_or_fetch_by_ap_id(ap_id) do
1299 user = get_cached_by_ap_id(ap_id)
1301 if !is_nil(user) and !needs_update?(user) do
1304 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1305 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1307 resp = fetch_by_ap_id(ap_id)
1309 if should_fetch_initial do
1310 with {:ok, %User{} = user} <- resp do
1311 fetch_initial_posts(user)
1319 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1320 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1321 with %User{} = user <- get_cached_by_ap_id(uri) do
1327 |> cast(%{}, [:ap_id, :nickname, :local])
1328 |> put_change(:ap_id, uri)
1329 |> put_change(:nickname, nickname)
1330 |> put_change(:local, true)
1331 |> put_change(:follower_address, uri <> "/followers")
1339 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1342 |> :public_key.pem_decode()
1344 |> :public_key.pem_entry_decode()
1349 def public_key(_), do: {:error, "not found key"}
1351 def get_public_key_for_ap_id(ap_id) do
1352 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1353 {:ok, public_key} <- public_key(user) do
1360 defp blank?(""), do: nil
1361 defp blank?(n), do: n
1363 def insert_or_update_user(data) do
1365 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1366 |> remote_user_creation()
1367 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1371 def ap_enabled?(%User{local: true}), do: true
1372 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1373 def ap_enabled?(_), do: false
1375 @doc "Gets or fetch a user by uri or nickname."
1376 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1377 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1378 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1380 # wait a period of time and return newest version of the User structs
1381 # this is because we have synchronous follow APIs and need to simulate them
1382 # with an async handshake
1383 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1384 with %User{} = a <- get_cached_by_id(a.id),
1385 %User{} = b <- get_cached_by_id(b.id) do
1392 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1393 with :ok <- :timer.sleep(timeout),
1394 %User{} = a <- get_cached_by_id(a.id),
1395 %User{} = b <- get_cached_by_id(b.id) do
1402 def parse_bio(bio) when is_binary(bio) and bio != "" do
1404 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1408 def parse_bio(_), do: ""
1410 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1411 # TODO: get profile URLs other than user.ap_id
1412 profile_urls = [user.ap_id]
1415 |> CommonUtils.format_input("text/plain",
1416 mentions_format: :full,
1417 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1422 def parse_bio(_, _), do: ""
1424 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1425 Repo.transaction(fn ->
1426 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1430 def tag(nickname, tags) when is_binary(nickname),
1431 do: tag(get_by_nickname(nickname), tags)
1433 def tag(%User{} = user, tags),
1434 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1436 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1437 Repo.transaction(fn ->
1438 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1442 def untag(nickname, tags) when is_binary(nickname),
1443 do: untag(get_by_nickname(nickname), tags)
1445 def untag(%User{} = user, tags),
1446 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1448 defp update_tags(%User{} = user, new_tags) do
1449 {:ok, updated_user} =
1451 |> change(%{tags: new_tags})
1452 |> update_and_set_cache()
1457 defp normalize_tags(tags) do
1460 |> Enum.map(&String.downcase/1)
1463 defp local_nickname_regex do
1464 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1465 @extended_local_nickname_regex
1467 @strict_local_nickname_regex
1471 def local_nickname(nickname_or_mention) do
1474 |> String.split("@")
1478 def full_nickname(nickname_or_mention),
1479 do: String.trim_leading(nickname_or_mention, "@")
1481 def error_user(ap_id) do
1485 nickname: "erroruser@example.com",
1486 inserted_at: NaiveDateTime.utc_now()
1490 @spec all_superusers() :: [User.t()]
1491 def all_superusers do
1492 User.Query.build(%{super_users: true, local: true, deactivated: false})
1496 def showing_reblogs?(%User{} = user, %User{} = target) do
1497 target.ap_id not in user.muted_reblogs
1501 The function returns a query to get users with no activity for given interval of days.
1502 Inactive users are those who didn't read any notification, or had any activity where
1503 the user is the activity's actor, during `inactivity_threshold` days.
1504 Deactivated users will not appear in this list.
1508 iex> Pleroma.User.list_inactive_users()
1511 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1512 def list_inactive_users_query(inactivity_threshold \\ 7) do
1513 negative_inactivity_threshold = -inactivity_threshold
1514 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1515 # Subqueries are not supported in `where` clauses, join gets too complicated.
1516 has_read_notifications =
1517 from(n in Pleroma.Notification,
1518 where: n.seen == true,
1520 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1523 |> Pleroma.Repo.all()
1525 from(u in Pleroma.User,
1526 left_join: a in Pleroma.Activity,
1527 on: u.ap_id == a.actor,
1528 where: not is_nil(u.nickname),
1529 where: u.deactivated != ^true,
1530 where: u.id not in ^has_read_notifications,
1533 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1534 is_nil(max(a.inserted_at))
1539 Enable or disable email notifications for user
1543 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1544 Pleroma.User{email_notifications: %{"digest" => true}}
1546 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1547 Pleroma.User{email_notifications: %{"digest" => false}}
1549 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1550 {:ok, t()} | {:error, Ecto.Changeset.t()}
1551 def switch_email_notifications(user, type, status) do
1552 User.update_email_notifications(user, %{type => status})
1556 Set `last_digest_emailed_at` value for the user to current time
1558 @spec touch_last_digest_emailed_at(t()) :: t()
1559 def touch_last_digest_emailed_at(user) do
1560 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1562 {:ok, updated_user} =
1564 |> change(%{last_digest_emailed_at: now})
1565 |> update_and_set_cache()
1570 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1571 def toggle_confirmation(%User{} = user) do
1573 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1574 |> update_and_set_cache()
1577 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1581 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1582 # use instance-default
1583 config = Pleroma.Config.get([:assets, :mascots])
1584 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1585 mascot = Keyword.get(config, default_mascot)
1588 "id" => "default-mascot",
1589 "url" => mascot[:url],
1590 "preview_url" => mascot[:url],
1592 "mime_type" => mascot[:mime_type]
1597 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1599 def ensure_keys_present(%User{} = user) do
1600 with {:ok, pem} <- Keys.generate_rsa_pem() do
1602 |> cast(%{keys: pem}, [:keys])
1603 |> validate_required([:keys])
1604 |> update_and_set_cache()
1608 def get_ap_ids_by_nicknames(nicknames) do
1610 where: u.nickname in ^nicknames,
1616 defdelegate search(query, opts \\ []), to: User.Search
1618 defp put_password_hash(
1619 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1621 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1624 defp put_password_hash(changeset), do: changeset
1626 def is_internal_user?(%User{nickname: nil}), do: true
1627 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1628 def is_internal_user?(_), do: false
1630 # A hack because user delete activities have a fake id for whatever reason
1631 # TODO: Get rid of this
1632 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1634 def get_delivered_users_by_object_id(object_id) do
1636 inner_join: delivery in assoc(u, :deliveries),
1637 where: delivery.object_id == ^object_id
1642 def change_email(user, email) do
1644 |> cast(%{email: email}, [:email])
1645 |> validate_required([:email])
1646 |> unique_constraint(:email)
1647 |> validate_format(:email, @email_regex)
1648 |> update_and_set_cache()
1651 # Internal function; public one is `deactivate/2`
1652 defp set_activation_status(user, deactivated) do
1654 |> cast(%{deactivated: deactivated}, [:deactivated])
1655 |> update_and_set_cache()
1658 def update_banner(user, banner) do
1660 |> cast(%{banner: banner}, [:banner])
1661 |> update_and_set_cache()
1664 def update_background(user, background) do
1666 |> cast(%{background: background}, [:background])
1667 |> update_and_set_cache()
1670 def update_source_data(user, source_data) do
1672 |> cast(%{source_data: source_data}, [:source_data])
1673 |> update_and_set_cache()
1676 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1679 moderator: is_moderator
1683 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1684 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1685 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1686 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1689 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1690 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1694 def fields(%{fields: nil}), do: []
1696 def fields(%{fields: fields}), do: fields
1698 def validate_fields(changeset, remote? \\ false) do
1699 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1700 limit = Pleroma.Config.get([:instance, limit_name], 0)
1703 |> validate_length(:fields, max: limit)
1704 |> validate_change(:fields, fn :fields, fields ->
1705 if Enum.all?(fields, &valid_field?/1) do
1713 defp valid_field?(%{"name" => name, "value" => value}) do
1714 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1715 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1717 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1718 String.length(value) <= value_limit
1721 defp valid_field?(_), do: false
1723 defp truncate_field(%{"name" => name, "value" => value}) do
1725 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1728 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1730 %{"name" => name, "value" => value}
1733 def admin_api_update(user, params) do
1740 |> update_and_set_cache()
1743 def mascot_update(user, url) do
1745 |> cast(%{mascot: url}, [:mascot])
1746 |> validate_required([:mascot])
1747 |> update_and_set_cache()
1750 def mastodon_settings_update(user, settings) do
1752 |> cast(%{settings: settings}, [:settings])
1753 |> validate_required([:settings])
1754 |> update_and_set_cache()
1757 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1758 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1760 if need_confirmation? do
1762 confirmation_pending: true,
1763 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1767 confirmation_pending: false,
1768 confirmation_token: nil
1772 cast(user, params, [:confirmation_pending, :confirmation_token])
1775 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1776 if id not in user.pinned_activities do
1777 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1778 params = %{pinned_activities: user.pinned_activities ++ [id]}
1781 |> cast(params, [:pinned_activities])
1782 |> validate_length(:pinned_activities,
1783 max: max_pinned_statuses,
1784 message: "You have already pinned the maximum number of statuses"
1789 |> update_and_set_cache()
1792 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1793 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1796 |> cast(params, [:pinned_activities])
1797 |> update_and_set_cache()
1800 def update_email_notifications(user, settings) do
1801 email_notifications =
1802 user.email_notifications
1803 |> Map.merge(settings)
1804 |> Map.take(["digest"])
1806 params = %{email_notifications: email_notifications}
1807 fields = [:email_notifications]
1810 |> cast(params, fields)
1811 |> validate_required(fields)
1812 |> update_and_set_cache()
1815 defp set_subscribers(user, subscribers) do
1816 params = %{subscribers: subscribers}
1819 |> cast(params, [:subscribers])
1820 |> validate_required([:subscribers])
1821 |> update_and_set_cache()
1824 def add_to_subscribers(user, subscribed) do
1825 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1828 def remove_from_subscribers(user, subscribed) do
1829 set_subscribers(user, List.delete(user.subscribers, subscribed))
1832 defp set_domain_blocks(user, domain_blocks) do
1833 params = %{domain_blocks: domain_blocks}
1836 |> cast(params, [:domain_blocks])
1837 |> validate_required([:domain_blocks])
1838 |> update_and_set_cache()
1841 def block_domain(user, domain_blocked) do
1842 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1845 def unblock_domain(user, domain_blocked) do
1846 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1849 defp set_blocks(user, blocks) do
1850 params = %{blocks: blocks}
1853 |> cast(params, [:blocks])
1854 |> validate_required([:blocks])
1855 |> update_and_set_cache()
1858 def add_to_block(user, blocked) do
1859 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1862 def remove_from_block(user, blocked) do
1863 set_blocks(user, List.delete(user.blocks, blocked))
1866 defp set_mutes(user, mutes) do
1867 params = %{mutes: mutes}
1870 |> cast(params, [:mutes])
1871 |> validate_required([:mutes])
1872 |> update_and_set_cache()
1875 def add_to_mutes(user, muted, notifications?) do
1876 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1877 set_notification_mutes(
1879 Enum.uniq([muted | user.muted_notifications]),
1885 def remove_from_mutes(user, muted) do
1886 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1887 set_notification_mutes(
1889 List.delete(user.muted_notifications, muted),
1895 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1899 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1900 params = %{muted_notifications: muted_notifications}
1903 |> cast(params, [:muted_notifications])
1904 |> validate_required([:muted_notifications])
1905 |> update_and_set_cache()
1908 def add_reblog_mute(user, ap_id) do
1909 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1912 |> cast(params, [:muted_reblogs])
1913 |> update_and_set_cache()
1916 def remove_reblog_mute(user, ap_id) do
1917 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1920 |> cast(params, [:muted_reblogs])
1921 |> update_and_set_cache()
1924 def set_invisible(user, invisible) do
1925 params = %{invisible: invisible}
1928 |> cast(params, [:invisible])
1929 |> validate_required([:invisible])
1930 |> update_and_set_cache()