Merge branch 'refactor/user' into 'develop'

Simplify updating user's `info`

See merge request pleroma/pleroma!1712
This commit is contained in:
kaniini 2019-09-26 03:00:38 +00:00
commit 48a82c4609
13 changed files with 307 additions and 524 deletions

View File

@ -4,7 +4,6 @@
defmodule Mix.Tasks.Pleroma.User do defmodule Mix.Tasks.Pleroma.User do
use Mix.Task use Mix.Task
import Ecto.Changeset
import Mix.Pleroma import Mix.Pleroma
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserInviteToken alias Pleroma.UserInviteToken
@ -228,9 +227,9 @@ def run(["unsubscribe", nickname]) do
shell_info("Deactivating #{user.nickname}") shell_info("Deactivating #{user.nickname}")
User.deactivate(user) User.deactivate(user)
{:ok, friends} = User.get_friends(user) user
|> User.get_friends()
Enum.each(friends, fn friend -> |> Enum.each(fn friend ->
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}") shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}")
@ -405,7 +404,7 @@ def run(["delete_activities", nickname]) do
start_pleroma() start_pleroma()
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
{:ok, _} = User.delete_user_activities(user) User.delete_user_activities(user)
shell_info("User #{nickname} statuses deleted.") shell_info("User #{nickname} statuses deleted.")
else else
_ -> _ ->
@ -443,39 +442,21 @@ def run(["sign_out", nickname]) do
end end
defp set_moderator(user, value) do defp set_moderator(user, value) do
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value}) {:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_moderator: value}))
user_cng =
Ecto.Changeset.change(user)
|> put_embed(:info, info_cng)
{:ok, user} = User.update_and_set_cache(user_cng)
shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}") shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
user user
end end
defp set_admin(user, value) do defp set_admin(user, value) do
info_cng = User.Info.admin_api_update(user.info, %{is_admin: value}) {:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_admin: value}))
user_cng =
Ecto.Changeset.change(user)
|> put_embed(:info, info_cng)
{:ok, user} = User.update_and_set_cache(user_cng)
shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}") shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}")
user user
end end
defp set_locked(user, value) do defp set_locked(user, value) do
info_cng = User.Info.user_upgrade(user.info, %{locked: value}) {:ok, user} = User.update_info(user, &User.Info.user_upgrade(&1, %{locked: value}))
user_cng =
Ecto.Changeset.change(user)
|> put_embed(:info, info_cng)
{:ok, user} = User.update_and_set_cache(user_cng)
shell_info("Locked status of #{user.nickname}: #{user.info.locked}") shell_info("Locked status of #{user.nickname}: #{user.info.locked}")
user user

View File

@ -106,9 +106,7 @@ def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url
def profile_url(%User{ap_id: ap_id}), do: ap_id def profile_url(%User{ap_id: ap_id}), do: ap_id
def profile_url(_), do: nil def profile_url(_), do: nil
def ap_id(%User{nickname: nickname}) do def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
"#{Web.base_url()}/users/#{nickname}"
end
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers" def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
@ -119,12 +117,9 @@ def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
def user_info(%User{} = user, args \\ %{}) do def user_info(%User{} = user, args \\ %{}) do
following_count = following_count =
if args[:following_count], Map.get(args, :following_count, user.info.following_count || following_count(user))
do: args[:following_count],
else: user.info.following_count || following_count(user)
follower_count = follower_count = Map.get(args, :follower_count, user.info.follower_count)
if args[:follower_count], do: args[:follower_count], else: user.info.follower_count
%{ %{
note_count: user.info.note_count, note_count: user.info.note_count,
@ -137,12 +132,11 @@ def user_info(%User{} = user, args \\ %{}) do
end end
def follow_state(%User{} = user, %User{} = target) do def follow_state(%User{} = user, %User{} = target) do
follow_activity = Utils.fetch_latest_follow(user, target) case Utils.fetch_latest_follow(user, target) do
%{data: %{"state" => state}} -> state
if follow_activity,
do: follow_activity.data["state"],
# Ideally this would be nil, but then Cachex does not commit the value # Ideally this would be nil, but then Cachex does not commit the value
else: false _ -> false
end
end end
def get_cached_follow_state(user, target) do def get_cached_follow_state(user, target) do
@ -152,11 +146,7 @@ def get_cached_follow_state(user, target) do
@spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()} @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
def set_follow_state_cache(user_ap_id, target_ap_id, state) do def set_follow_state_cache(user_ap_id, target_ap_id, state) do
Cachex.put( Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
:user_cache,
"follow_state:#{user_ap_id}|#{target_ap_id}",
state
)
end end
def set_info_cache(user, args) do def set_info_cache(user, args) do
@ -197,34 +187,25 @@ def remote_user_creation(params) do
|> truncate_if_exists(:name, name_limit) |> truncate_if_exists(:name, name_limit)
|> truncate_if_exists(:bio, bio_limit) |> truncate_if_exists(:bio, bio_limit)
info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info]) changeset =
%User{local: false}
changes =
%User{}
|> cast(params, [:bio, :name, :ap_id, :nickname, :avatar]) |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
|> validate_required([:name, :ap_id]) |> validate_required([:name, :ap_id])
|> unique_constraint(:nickname) |> unique_constraint(:nickname)
|> validate_format(:nickname, @email_regex) |> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: bio_limit) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit) |> validate_length(:name, max: name_limit)
|> put_change(:local, false) |> change_info(&User.Info.remote_user_creation(&1, params[:info]))
|> put_embed(:info, info_cng)
if changes.valid? do case params[:info][:source_data] do
case info_cng.changes[:source_data] do
%{"followers" => followers, "following" => following} -> %{"followers" => followers, "following" => following} ->
changes changeset
|> put_change(:follower_address, followers) |> put_change(:follower_address, followers)
|> put_change(:following_address, following) |> put_change(:following_address, following)
_ -> _ ->
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]}) followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
put_change(changeset, :follower_address, followers)
changes
|> put_change(:follower_address, followers)
end
else
changes
end end
end end
@ -245,7 +226,6 @@ def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now()) params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
info_cng = User.Info.user_upgrade(struct.info, params[:info], remote?)
struct struct
|> cast(params, [ |> cast(params, [
@ -260,7 +240,7 @@ def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
|> validate_format(:nickname, local_nickname_regex()) |> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit) |> validate_length(:name, max: name_limit)
|> put_embed(:info, info_cng) |> change_info(&User.Info.user_upgrade(&1, params[:info], remote?))
end end
def password_update_changeset(struct, params) do def password_update_changeset(struct, params) do
@ -311,10 +291,6 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
opts[:need_confirmation] opts[:need_confirmation]
end end
info_change =
User.Info.confirmation_changeset(%User.Info{}, need_confirmation: need_confirmation?)
changeset =
struct struct
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation]) |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|> validate_required([:name, :nickname, :password, :password_confirmation]) |> validate_required([:name, :nickname, :password, :password_confirmation])
@ -326,28 +302,28 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|> validate_format(:email, @email_regex) |> validate_format(:email, @email_regex)
|> validate_length(:bio, max: bio_limit) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit) |> validate_length(:name, min: 1, max: name_limit)
|> put_change(:info, info_change) |> change_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
|> maybe_validate_required_email(opts[:external])
changeset = |> put_password_hash
if opts[:external] do |> put_ap_id()
changeset |> unique_constraint(:ap_id)
else |> put_following_and_follower_address()
validate_required(changeset, [:email])
end end
if changeset.valid? do def maybe_validate_required_email(changeset, true), do: changeset
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]}) def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
defp put_ap_id(changeset) do
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
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)})
changeset changeset
|> put_password_hash
|> put_change(:ap_id, ap_id)
|> unique_constraint(:ap_id)
|> put_change(:following, [followers]) |> put_change(:following, [followers])
|> put_change(:follower_address, followers) |> put_change(:follower_address, followers)
else
changeset
end
end end
defp autofollow_users(user) do defp autofollow_users(user) do
@ -362,9 +338,8 @@ defp autofollow_users(user) do
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)" @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
def register(%Ecto.Changeset{} = changeset) do def register(%Ecto.Changeset{} = changeset) do
with {:ok, user} <- Repo.insert(changeset), with {:ok, user} <- Repo.insert(changeset) do
{:ok, user} <- post_register_action(user) do post_register_action(user)
{:ok, user}
end end
end end
@ -410,7 +385,7 @@ def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
end end
def maybe_direct_follow(%User{} = follower, %User{} = followed) do def maybe_direct_follow(%User{} = follower, %User{} = followed) do
if not User.ap_enabled?(followed) do if not ap_enabled?(followed) do
follow(follower, followed) follow(follower, followed)
else else
{:ok, follower} {:ok, follower}
@ -443,9 +418,7 @@ def follow_all(follower, followeds) do
{1, [follower]} = Repo.update_all(q, []) {1, [follower]} = Repo.update_all(q, [])
Enum.each(followeds, fn followed -> Enum.each(followeds, &update_follower_count/1)
update_follower_count(followed)
end)
set_cache(follower) set_cache(follower)
end end
@ -555,8 +528,6 @@ def set_cache(%User{} = user) do
def update_and_set_cache(changeset) do def update_and_set_cache(changeset) do
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
set_cache(user) set_cache(user)
else
e -> e
end end
end end
@ -593,9 +564,7 @@ def get_cached_by_nickname(nickname) do
key = "nickname:#{nickname}" key = "nickname:#{nickname}"
Cachex.fetch!(:user_cache, key, fn -> Cachex.fetch!(:user_cache, key, fn ->
user_result = get_or_fetch_by_nickname(nickname) case get_or_fetch_by_nickname(nickname) do
case user_result do
{:ok, user} -> {:commit, user} {:ok, user} -> {:commit, user}
{:error, _error} -> {:ignore, nil} {:error, _error} -> {:ignore, nil}
end end
@ -635,13 +604,11 @@ def get_by_nickname_or_email(nickname_or_email) do
def get_cached_user_info(user) do def get_cached_user_info(user) do
key = "user_info:#{user.id}" key = "user_info:#{user.id}"
Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end) Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
end end
def fetch_by_nickname(nickname) do def fetch_by_nickname(nickname) do
ap_try = ActivityPub.make_user_from_nickname(nickname) case ActivityPub.make_user_from_nickname(nickname) do
case ap_try do
{:ok, user} -> {:ok, user} {:ok, user} -> {:ok, user}
_ -> OStatus.make_user(nickname) _ -> OStatus.make_user(nickname)
end end
@ -676,7 +643,8 @@ def get_followers_query(%User{} = user, nil) do
end end
def get_followers_query(user, page) do def get_followers_query(user, page) do
from(u in get_followers_query(user, nil)) user
|> get_followers_query(nil)
|> User.Query.paginate(page, 20) |> User.Query.paginate(page, 20)
end end
@ -685,25 +653,24 @@ def get_followers_query(user), do: get_followers_query(user, nil)
@spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
def get_followers(user, page \\ nil) do def get_followers(user, page \\ nil) do
q = get_followers_query(user, page) user
|> get_followers_query(page)
{:ok, Repo.all(q)} |> Repo.all()
end end
@spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
def get_external_followers(user, page \\ nil) do def get_external_followers(user, page \\ nil) do
q =
user user
|> get_followers_query(page) |> get_followers_query(page)
|> User.Query.build(%{external: true}) |> User.Query.build(%{external: true})
|> Repo.all()
{:ok, Repo.all(q)}
end end
def get_followers_ids(user, page \\ nil) do def get_followers_ids(user, page \\ nil) do
q = get_followers_query(user, page) user
|> get_followers_query(page)
Repo.all(from(u in q, select: u.id)) |> select([u], u.id)
|> Repo.all()
end end
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t() @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
@ -712,7 +679,8 @@ def get_friends_query(%User{} = user, nil) do
end end
def get_friends_query(user, page) do def get_friends_query(user, page) do
from(u in get_friends_query(user, nil)) user
|> get_friends_query(nil)
|> User.Query.paginate(page, 20) |> User.Query.paginate(page, 20)
end end
@ -720,28 +688,27 @@ def get_friends_query(user, page) do
def get_friends_query(user), do: get_friends_query(user, nil) def get_friends_query(user), do: get_friends_query(user, nil)
def get_friends(user, page \\ nil) do def get_friends(user, page \\ nil) do
q = get_friends_query(user, page) user
|> get_friends_query(page)
{:ok, Repo.all(q)} |> Repo.all()
end end
def get_friends_ids(user, page \\ nil) do def get_friends_ids(user, page \\ nil) do
q = get_friends_query(user, page) user
|> get_friends_query(page)
Repo.all(from(u in q, select: u.id)) |> select([u], u.id)
|> Repo.all()
end end
@spec get_follow_requests(User.t()) :: {:ok, [User.t()]} @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
def get_follow_requests(%User{} = user) do def get_follow_requests(%User{} = user) do
users = user
Activity.follow_requests_for_actor(user) |> Activity.follow_requests_for_actor()
|> join(:inner, [a], u in User, on: a.actor == u.ap_id) |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
|> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address])) |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
|> group_by([a, u], u.id) |> group_by([a, u], u.id)
|> select([a, u], u) |> select([a, u], u)
|> Repo.all() |> Repo.all()
{:ok, users}
end end
def increase_note_count(%User{} = user) do def increase_note_count(%User{} = user) do
@ -787,21 +754,15 @@ def decrease_note_count(%User{} = user) do
end end
def update_note_count(%User{} = user) do def update_note_count(%User{} = user) do
note_count_query = note_count =
from( from(
a in Object, a in Object,
where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data), where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
select: count(a.id) select: count(a.id)
) )
|> Repo.one()
note_count = Repo.one(note_count_query) update_info(user, &User.Info.set_note_count(&1, note_count))
info_cng = User.Info.set_note_count(user.info, note_count)
user
|> change()
|> put_embed(:info, info_cng)
|> update_and_set_cache()
end end
@spec maybe_fetch_follow_information(User.t()) :: User.t() @spec maybe_fetch_follow_information(User.t()) :: User.t()
@ -818,17 +779,7 @@ def maybe_fetch_follow_information(user) do
def fetch_follow_information(user) do def fetch_follow_information(user) do
with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
info_cng = User.Info.follow_information_update(user.info, info) update_info(user, &User.Info.follow_information_update(&1, info))
changeset =
user
|> change()
|> put_embed(:info, info_cng)
update_and_set_cache(changeset)
else
{:error, _} = e -> e
e -> {:error, e}
end end
end end
@ -902,62 +853,28 @@ def get_recipients_from_activity(%Activity{recipients: to}) do
@spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()} @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
info = muter.info update_info(muter, &User.Info.add_to_mutes(&1, ap_id, notifications?))
info_cng =
User.Info.add_to_mutes(info, ap_id)
|> User.Info.add_to_muted_notifications(info, ap_id, notifications?)
cng =
change(muter)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
end end
def unmute(muter, %{ap_id: ap_id}) do def unmute(muter, %{ap_id: ap_id}) do
info = muter.info update_info(muter, &User.Info.remove_from_mutes(&1, ap_id))
info_cng =
User.Info.remove_from_mutes(info, ap_id)
|> User.Info.remove_from_muted_notifications(info, ap_id)
cng =
change(muter)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
end end
def subscribe(subscriber, %{ap_id: ap_id}) do def subscribe(subscriber, %{ap_id: ap_id}) do
with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do if blocks?(subscribed, subscriber) and deny_follow_blocked do
blocked = blocks?(subscribed, subscriber) and deny_follow_blocked
if blocked do
{:error, "Could not subscribe: #{subscribed.nickname} is blocking you"} {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
else else
info_cng = update_info(subscribed, &User.Info.add_to_subscribers(&1, subscriber.ap_id))
subscribed.info
|> User.Info.add_to_subscribers(subscriber.ap_id)
change(subscribed)
|> put_embed(:info, info_cng)
|> update_and_set_cache()
end end
end end
end end
def unsubscribe(unsubscriber, %{ap_id: ap_id}) do def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
with %User{} = user <- get_cached_by_ap_id(ap_id) do with %User{} = user <- get_cached_by_ap_id(ap_id) do
info_cng = update_info(user, &User.Info.remove_from_subscribers(&1, unsubscriber.ap_id))
user.info
|> User.Info.remove_from_subscribers(unsubscriber.ap_id)
change(user)
|> put_embed(:info, info_cng)
|> update_and_set_cache()
end end
end end
@ -986,21 +903,11 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do
blocker blocker
end end
if following?(blocked, blocker) do if following?(blocked, blocker), do: unfollow(blocked, blocker)
unfollow(blocked, blocker)
end
{:ok, blocker} = update_follower_count(blocker) {:ok, blocker} = update_follower_count(blocker)
info_cng = update_info(blocker, &User.Info.add_to_block(&1, ap_id))
blocker.info
|> User.Info.add_to_block(ap_id)
cng =
change(blocker)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
end end
# helper to handle the block given only an actor's AP id # helper to handle the block given only an actor's AP id
@ -1009,15 +916,7 @@ def block(blocker, %{ap_id: ap_id}) do
end end
def unblock(blocker, %{ap_id: ap_id}) do def unblock(blocker, %{ap_id: ap_id}) do
info_cng = update_info(blocker, &User.Info.remove_from_block(&1, ap_id))
blocker.info
|> User.Info.remove_from_block(ap_id)
cng =
change(blocker)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
end end
def mutes?(nil, _), do: false def mutes?(nil, _), do: false
@ -1074,27 +973,11 @@ def subscribers(user) do
end end
def block_domain(user, domain) do def block_domain(user, domain) do
info_cng = update_info(user, &User.Info.add_to_domain_block(&1, domain))
user.info
|> User.Info.add_to_domain_block(domain)
cng =
change(user)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
end end
def unblock_domain(user, domain) do def unblock_domain(user, domain) do
info_cng = update_info(user, &User.Info.remove_from_domain_block(&1, domain))
user.info
|> User.Info.remove_from_domain_block(domain)
cng =
change(user)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
end end
def deactivate_async(user, status \\ true) do def deactivate_async(user, status \\ true) do
@ -1102,28 +985,16 @@ def deactivate_async(user, status \\ true) do
end end
def deactivate(%User{} = user, status \\ true) do def deactivate(%User{} = user, status \\ true) do
info_cng = User.Info.set_activation_status(user.info, status) with {:ok, user} <- update_info(user, &User.Info.set_activation_status(&1, status)) do
Enum.each(get_followers(user), &invalidate_cache/1)
with {:ok, friends} <- User.get_friends(user), Enum.each(get_friends(user), &update_follower_count/1)
{: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))
{:ok, user} {:ok, user}
end end
end end
def update_notification_settings(%User{} = user, settings \\ %{}) do def update_notification_settings(%User{} = user, settings \\ %{}) do
info_changeset = User.Info.update_notification_settings(user.info, settings) update_info(user, &User.Info.update_notification_settings(&1, settings))
change(user)
|> put_embed(:info, info_changeset)
|> update_and_set_cache()
end end
def delete(%User{} = user) do def delete(%User{} = user) do
@ -1137,18 +1008,18 @@ def perform(:delete, %User{} = user) do
{:ok, _user} = ActivityPub.delete(user) {:ok, _user} = ActivityPub.delete(user)
# Remove all relationships # Remove all relationships
{:ok, followers} = User.get_followers(user) user
|> get_followers()
Enum.each(followers, fn follower -> |> Enum.each(fn follower ->
ActivityPub.unfollow(follower, user) ActivityPub.unfollow(follower, user)
User.unfollow(follower, user) unfollow(follower, user)
end) end)
{:ok, friends} = User.get_friends(user) user
|> get_friends()
Enum.each(friends, fn followed -> |> Enum.each(fn followed ->
ActivityPub.unfollow(user, followed) ActivityPub.unfollow(user, followed)
User.unfollow(user, followed) unfollow(user, followed)
end) end)
delete_user_activities(user) delete_user_activities(user)
@ -1160,13 +1031,11 @@ def perform(:delete, %User{} = user) do
def perform(:fetch_initial_posts, %User{} = user) do def perform(:fetch_initial_posts, %User{} = user) do
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages]) 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 # 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)), user.info.source_data["outbox"]
&Pleroma.Web.Federator.incoming_ap_doc/1 |> Utils.fetch_ordered_collection(pages)
) |> Enum.reverse()
|> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
{:ok, user}
end end
def perform(:deactivate_async, user, status), do: deactivate(user, status) def perform(:deactivate_async, user, status), do: deactivate(user, status)
@ -1252,16 +1121,12 @@ def follow_import(%User{} = follower, followed_identifiers)
}) })
end end
def delete_user_activities(%User{ap_id: ap_id} = user) do def delete_user_activities(%User{ap_id: ap_id}) do
ap_id ap_id
|> Activity.Queries.by_actor() |> Activity.Queries.by_actor()
|> RepoStreamer.chunk_stream(50) |> RepoStreamer.chunk_stream(50)
|> Stream.each(fn activities -> |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
Enum.each(activities, &delete_activity(&1))
end)
|> Stream.run() |> Stream.run()
{:ok, user}
end end
defp delete_activity(%{data: %{"type" => "Create"}} = activity) do defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
@ -1271,17 +1136,19 @@ defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
end end
defp delete_activity(%{data: %{"type" => "Like"}} = activity) do defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
user = get_cached_by_ap_id(activity.actor)
object = Object.normalize(activity) object = Object.normalize(activity)
ActivityPub.unlike(user, object) activity.actor
|> get_cached_by_ap_id()
|> ActivityPub.unlike(object)
end end
defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
user = get_cached_by_ap_id(activity.actor)
object = Object.normalize(activity) object = Object.normalize(activity)
ActivityPub.unannounce(user, object) activity.actor
|> get_cached_by_ap_id()
|> ActivityPub.unannounce(object)
end end
defp delete_activity(_activity), do: "Doing nothing" defp delete_activity(_activity), do: "Doing nothing"
@ -1293,9 +1160,7 @@ def html_filter_policy(%User{info: %{no_rich_text: true}}) do
def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy]) def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
def fetch_by_ap_id(ap_id) do def fetch_by_ap_id(ap_id) do
ap_try = ActivityPub.make_user_from_ap_id(ap_id) case ActivityPub.make_user_from_ap_id(ap_id) do
case ap_try do
{:ok, user} -> {:ok, user} ->
{:ok, user} {:ok, user}
@ -1310,7 +1175,7 @@ def fetch_by_ap_id(ap_id) do
def get_or_fetch_by_ap_id(ap_id) do def get_or_fetch_by_ap_id(ap_id) do
user = get_cached_by_ap_id(ap_id) user = get_cached_by_ap_id(ap_id)
if !is_nil(user) and !User.needs_update?(user) do if !is_nil(user) and !needs_update?(user) do
{:ok, user} {:ok, user}
else else
# Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled) # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
@ -1330,18 +1195,19 @@ def get_or_fetch_by_ap_id(ap_id) do
@doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing." @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
if user = get_cached_by_ap_id(uri) do with %User{} = user <- get_cached_by_ap_id(uri) do
user user
else else
changes = _ ->
{:ok, user} =
%User{info: %User.Info{}} %User{info: %User.Info{}}
|> cast(%{}, [:ap_id, :nickname, :local]) |> cast(%{}, [:ap_id, :nickname, :local])
|> put_change(:ap_id, uri) |> put_change(:ap_id, uri)
|> put_change(:nickname, nickname) |> put_change(:nickname, nickname)
|> put_change(:local, true) |> put_change(:local, true)
|> put_change(:follower_address, uri <> "/followers") |> put_change(:follower_address, uri <> "/followers")
|> Repo.insert()
{:ok, user} = Repo.insert(changes)
user user
end end
end end
@ -1399,23 +1265,21 @@ def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
# this is because we have synchronous follow APIs and need to simulate them # this is because we have synchronous follow APIs and need to simulate them
# with an async handshake # with an async handshake
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
with %User{} = a <- User.get_cached_by_id(a.id), with %User{} = a <- get_cached_by_id(a.id),
%User{} = b <- User.get_cached_by_id(b.id) do %User{} = b <- get_cached_by_id(b.id) do
{:ok, a, b} {:ok, a, b}
else else
_e -> nil -> :error
:error
end end
end end
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
with :ok <- :timer.sleep(timeout), with :ok <- :timer.sleep(timeout),
%User{} = a <- User.get_cached_by_id(a.id), %User{} = a <- get_cached_by_id(a.id),
%User{} = b <- User.get_cached_by_id(b.id) do %User{} = b <- get_cached_by_id(b.id) do
{:ok, a, b} {:ok, a, b}
else else
_e -> nil -> :error
:error
end end
end end
@ -1477,7 +1341,7 @@ defp update_tags(%User{} = user, new_tags) do
defp normalize_tags(tags) do defp normalize_tags(tags) do
[tags] [tags]
|> List.flatten() |> List.flatten()
|> Enum.map(&String.downcase(&1)) |> Enum.map(&String.downcase/1)
end end
defp local_nickname_regex do defp local_nickname_regex do
@ -1570,11 +1434,7 @@ def list_inactive_users_query(inactivity_threshold \\ 7) do
@spec switch_email_notifications(t(), String.t(), boolean()) :: @spec switch_email_notifications(t(), String.t(), boolean()) ::
{:ok, t()} | {:error, Ecto.Changeset.t()} {:ok, t()} | {:error, Ecto.Changeset.t()}
def switch_email_notifications(user, type, status) do def switch_email_notifications(user, type, status) do
info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status}) update_info(user, &User.Info.update_email_notifications(&1, %{type => status}))
change(user)
|> put_embed(:info, info)
|> update_and_set_cache()
end end
@doc """ @doc """
@ -1596,13 +1456,8 @@ def touch_last_digest_emailed_at(user) do
def toggle_confirmation(%User{} = user) do def toggle_confirmation(%User{} = user) do
need_confirmation? = !user.info.confirmation_pending need_confirmation? = !user.info.confirmation_pending
info_changeset =
User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
user user
|> change() |> update_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
|> put_embed(:info, info_changeset)
|> update_and_set_cache()
end end
def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
@ -1625,16 +1480,11 @@ def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
} }
end end
def ensure_keys_present(%User{info: info} = user) do def ensure_keys_present(%{info: %{keys: keys}} = user) when not is_nil(keys), do: {:ok, user}
if info.keys do
{:ok, user}
else
{:ok, pem} = Keys.generate_rsa_pem()
user def ensure_keys_present(%User{} = user) do
|> Ecto.Changeset.change() with {:ok, pem} <- Keys.generate_rsa_pem() do
|> Ecto.Changeset.put_embed(:info, User.Info.set_keys(info, pem)) update_info(user, &User.Info.set_keys(&1, pem))
|> update_and_set_cache()
end end
end end
@ -1680,4 +1530,26 @@ def change_email(user, email) do
|> validate_format(:email, @email_regex) |> validate_format(:email, @email_regex)
|> update_and_set_cache() |> update_and_set_cache()
end end
@doc """
Changes `user.info` and returns the user changeset.
`fun` is called with the `user.info`.
"""
def change_info(user, fun) do
changeset = change(user)
info = get_field(changeset, :info) || %User.Info{}
put_embed(changeset, :info, fun.(info))
end
@doc """
Updates `user.info` and sets cache.
`fun` is called with the `user.info`.
"""
def update_info(user, fun) do
user
|> change_info(fun)
|> update_and_set_cache()
end
end end

View File

@ -188,16 +188,11 @@ def set_subscribers(info, subscribers) do
|> validate_required([:subscribers]) |> validate_required([:subscribers])
end end
@spec add_to_mutes(Info.t(), String.t()) :: Changeset.t() @spec add_to_mutes(Info.t(), String.t(), boolean()) :: Changeset.t()
def add_to_mutes(info, muted) do def add_to_mutes(info, muted, notifications?) do
set_mutes(info, Enum.uniq([muted | info.mutes])) info
end |> set_mutes(Enum.uniq([muted | info.mutes]))
|> set_notification_mutes(
@spec add_to_muted_notifications(Changeset.t(), Info.t(), String.t(), boolean()) ::
Changeset.t()
def add_to_muted_notifications(changeset, info, muted, notifications?) do
set_notification_mutes(
changeset,
Enum.uniq([muted | info.muted_notifications]), Enum.uniq([muted | info.muted_notifications]),
notifications? notifications?
) )
@ -205,12 +200,9 @@ def add_to_muted_notifications(changeset, info, muted, notifications?) do
@spec remove_from_mutes(Info.t(), String.t()) :: Changeset.t() @spec remove_from_mutes(Info.t(), String.t()) :: Changeset.t()
def remove_from_mutes(info, muted) do def remove_from_mutes(info, muted) do
set_mutes(info, List.delete(info.mutes, muted)) info
end |> set_mutes(List.delete(info.mutes, muted))
|> set_notification_mutes(List.delete(info.muted_notifications, muted), true)
@spec remove_from_muted_notifications(Changeset.t(), Info.t(), String.t()) :: Changeset.t()
def remove_from_muted_notifications(changeset, info, muted) do
set_notification_mutes(changeset, List.delete(info.muted_notifications, muted), true)
end end
def add_to_block(info, blocked) do def add_to_block(info, blocked) do

View File

@ -111,11 +111,11 @@ defp should_federate?(inbox, public) do
@spec recipients(User.t(), Activity.t()) :: list(User.t()) | [] @spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
defp recipients(actor, activity) do defp recipients(actor, activity) do
{:ok, followers} = followers =
if actor.follower_address in activity.recipients do if actor.follower_address in activity.recipients do
User.get_external_followers(actor) User.get_external_followers(actor)
else else
{:ok, []} []
end end
fetchers = fetchers =

View File

@ -254,18 +254,12 @@ def right_add(%{assigns: %{user: admin}} = conn, %{
"nickname" => nickname "nickname" => nickname
}) })
when permission_group in ["moderator", "admin"] do when permission_group in ["moderator", "admin"] do
user = User.get_cached_by_nickname(nickname) info = Map.put(%{}, "is_" <> permission_group, true)
info = {:ok, user} =
%{} nickname
|> Map.put("is_" <> permission_group, true) |> User.get_cached_by_nickname()
|> User.update_info(&User.Info.admin_api_update(&1, info))
info_cng = User.Info.admin_api_update(user.info, info)
cng =
user
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_embed(:info, info_cng)
ModerationLog.insert_log(%{ ModerationLog.insert_log(%{
action: "grant", action: "grant",
@ -274,8 +268,6 @@ def right_add(%{assigns: %{user: admin}} = conn, %{
permission: permission_group permission: permission_group
}) })
{:ok, _user} = User.update_and_set_cache(cng)
json(conn, info) json(conn, info)
end end
@ -293,30 +285,24 @@ def right_get(conn, %{"nickname" => nickname}) do
}) })
end end
def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
render_error(conn, :forbidden, "You can't revoke your own admin status.")
end
def right_delete( def right_delete(
%{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn, %{assigns: %{user: admin}} = conn,
%{ %{
"permission_group" => permission_group, "permission_group" => permission_group,
"nickname" => nickname "nickname" => nickname
} }
) )
when permission_group in ["moderator", "admin"] do when permission_group in ["moderator", "admin"] do
if admin_nickname == nickname do info = Map.put(%{}, "is_" <> permission_group, false)
render_error(conn, :forbidden, "You can't revoke your own admin status.")
else
user = User.get_cached_by_nickname(nickname)
info = {:ok, user} =
%{} nickname
|> Map.put("is_" <> permission_group, false) |> User.get_cached_by_nickname()
|> User.update_info(&User.Info.admin_api_update(&1, info))
info_cng = User.Info.admin_api_update(user.info, info)
cng =
Ecto.Changeset.change(user)
|> Ecto.Changeset.put_embed(:info, info_cng)
{:ok, _user} = User.update_and_set_cache(cng)
ModerationLog.insert_log(%{ ModerationLog.insert_log(%{
action: "revoke", action: "revoke",
@ -327,7 +313,6 @@ def right_delete(
json(conn, info) json(conn, info)
end end
end
def right_delete(conn, _) do def right_delete(conn, _) do
render_error(conn, :not_found, "No such permission_group") render_error(conn, :not_found, "No such permission_group")

View File

@ -306,16 +306,14 @@ defp put_emoji(map, text, emojis) do
# Updates the emojis for a user based on their profile # Updates the emojis for a user based on their profile
def update(user) do def update(user) do
emoji = emoji_from_profile(user)
source_data = user.info |> Map.get(:source_data, {}) |> Map.put("tag", emoji)
user = user =
with emoji <- emoji_from_profile(user), with {:ok, user} <- User.update_info(user, &User.Info.set_source_data(&1, source_data)) do
source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
info_cng <- User.Info.set_source_data(user.info, source_data),
change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, user} <- User.update_and_set_cache(change) do
user user
else else
_e -> _e -> user
user
end end
ActivityPub.update(%{ ActivityPub.update(%{
@ -340,34 +338,21 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
} }
} = activity <- get_by_id_or_ap_id(id_or_ap_id), } = activity <- get_by_id_or_ap_id(id_or_ap_id),
true <- Visibility.is_public?(activity), true <- Visibility.is_public?(activity),
%{valid?: true} = info_changeset <- User.Info.add_pinnned_activity(user.info, activity), {:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do
changeset <-
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
{:ok, _user} <- User.update_and_set_cache(changeset) do
{:ok, activity} {:ok, activity}
else else
%{errors: [pinned_activities: {err, _}]} -> {:error, %{changes: %{info: %{errors: [pinned_activities: {err, _}]}}}} -> {:error, err}
{:error, err} _ -> {:error, dgettext("errors", "Could not pin")}
_ ->
{:error, dgettext("errors", "Could not pin")}
end end
end end
def unpin(id_or_ap_id, user) do def unpin(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
%{valid?: true} = info_changeset <- {:ok, _user} <- User.update_info(user, &User.Info.remove_pinnned_activity(&1, activity)) do
User.Info.remove_pinnned_activity(user.info, activity),
changeset <-
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
{:ok, _user} <- User.update_and_set_cache(changeset) do
{:ok, activity} {:ok, activity}
else else
%{errors: [pinned_activities: {err, _}]} -> %{errors: [pinned_activities: {err, _}]} -> {:error, err}
{:error, err} _ -> {:error, dgettext("errors", "Could not unpin")}
_ ->
{:error, dgettext("errors", "Could not unpin")}
end end
end end
@ -462,23 +447,15 @@ defp set_visibility(activity, %{"visibility" => visibility}) do
defp set_visibility(activity, _), do: {:ok, activity} defp set_visibility(activity, _), do: {:ok, activity}
def hide_reblogs(user, muted) do def hide_reblogs(user, %{ap_id: ap_id} = _muted) do
ap_id = muted.ap_id
if ap_id not in user.info.muted_reblogs do if ap_id not in user.info.muted_reblogs do
info_changeset = User.Info.add_reblog_mute(user.info, ap_id) User.update_info(user, &User.Info.add_reblog_mute(&1, ap_id))
changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
User.update_and_set_cache(changeset)
end end
end end
def show_reblogs(user, muted) do def show_reblogs(user, %{ap_id: ap_id} = _muted) do
ap_id = muted.ap_id
if ap_id in user.info.muted_reblogs do if ap_id in user.info.muted_reblogs do
info_changeset = User.Info.remove_reblog_mute(user.info, ap_id) User.update_info(user, &User.Info.remove_reblog_mute(&1, ap_id))
changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
User.update_and_set_cache(changeset)
end end
end end
end end

View File

@ -189,14 +189,13 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
end) end)
|> Map.put(:emoji, user_info_emojis) |> Map.put(:emoji, user_info_emojis)
info_cng = User.Info.profile_update(user.info, info_params) changeset =
user
|> User.update_changeset(user_params)
|> User.change_info(&User.Info.profile_update(&1, info_params))
with changeset <- User.update_changeset(user, user_params), with {:ok, user} <- User.update_and_set_cache(changeset) do
changeset <- Changeset.put_embed(changeset, :info, info_cng), if original_user != user, do: CommonAPI.update(user)
{:ok, user} <- User.update_and_set_cache(changeset) do
if original_user != user do
CommonAPI.update(user)
end
json( json(
conn, conn,
@ -226,12 +225,10 @@ def update_avatar(%{assigns: %{user: user}} = conn, params) do
end end
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
with new_info <- %{"banner" => %{}}, new_info = %{"banner" => %{}}
info_cng <- User.Info.profile_update(user.info, new_info),
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user)
with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
CommonAPI.update(user)
json(conn, %{url: nil}) json(conn, %{url: nil})
end end
end end
@ -239,9 +236,7 @@ def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
def update_banner(%{assigns: %{user: user}} = conn, params) do def update_banner(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner), with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
new_info <- %{"banner" => object.data}, new_info <- %{"banner" => object.data},
info_cng <- User.Info.profile_update(user.info, new_info), {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user) CommonAPI.update(user)
%{"url" => [%{"href" => href} | _]} = object.data %{"url" => [%{"href" => href} | _]} = object.data
@ -250,10 +245,9 @@ def update_banner(%{assigns: %{user: user}} = conn, params) do
end end
def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
with new_info <- %{"background" => %{}}, new_info = %{"background" => %{}}
info_cng <- User.Info.profile_update(user.info, new_info),
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng), with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
{:ok, _user} <- User.update_and_set_cache(changeset) do
json(conn, %{url: nil}) json(conn, %{url: nil})
end end
end end
@ -261,9 +255,7 @@ def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
def update_background(%{assigns: %{user: user}} = conn, params) do def update_background(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(params, type: :background), with {:ok, object} <- ActivityPub.upload(params, type: :background),
new_info <- %{"background" => object.data}, new_info <- %{"background" => object.data},
info_cng <- User.Info.profile_update(user.info, new_info), {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
{:ok, _user} <- User.update_and_set_cache(changeset) do
%{"url" => [%{"href" => href} | _]} = object.data %{"url" => [%{"href" => href} | _]} = object.data
json(conn, %{url: href}) json(conn, %{url: href})
@ -817,26 +809,16 @@ def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)), with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
%{} = attachment_data <- Map.put(object.data, "id", object.id), %{} = attachment_data <- Map.put(object.data, "id", object.id),
%{type: type} = rendered <-
StatusView.render("attachment.json", %{attachment: attachment_data}) do
# Reject if not an image # Reject if not an image
if type == "image" do %{type: "image"} = rendered <-
StatusView.render("attachment.json", %{attachment: attachment_data}) do
# Sure! # Sure!
# Save to the user's info # Save to the user's info
info_changeset = User.Info.mascot_update(user.info, rendered) {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, rendered))
user_changeset = json(conn, rendered)
user
|> Changeset.change()
|> Changeset.put_embed(:info, info_changeset)
{:ok, _user} = User.update_and_set_cache(user_changeset)
conn
|> json(rendered)
else else
render_error(conn, :unsupported_media_type, "mascots can only be images") %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images")
end
end end
end end
@ -959,12 +941,12 @@ def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
end end
def follow_requests(%{assigns: %{user: followed}} = conn, _params) do def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
with {:ok, follow_requests} <- User.get_follow_requests(followed) do follow_requests = User.get_follow_requests(followed)
conn conn
|> put_view(AccountView) |> put_view(AccountView)
|> render("accounts.json", %{for: followed, users: follow_requests, as: :user}) |> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
end end
end
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
with %User{} = follower <- User.get_cached_by_id(id), with %User{} = follower <- User.get_cached_by_id(id),
@ -1367,11 +1349,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do
end end
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
info_cng = User.Info.mastodon_settings_update(user.info, settings) with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
with changeset <- Changeset.change(user),
changeset <- Changeset.put_embed(changeset, :info, info_cng),
{:ok, _user} <- User.update_and_set_cache(changeset) do
json(conn, %{}) json(conn, %{})
else else
e -> e ->

View File

@ -5,7 +5,6 @@
defmodule Pleroma.Web.TwitterAPI.Controller do defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Ecto.Changeset
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
@ -16,15 +15,12 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
action_fallback(:errors) action_fallback(:errors)
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
with %User{} = user <- User.get_cached_by_id(uid), new_info = [need_confirmation: false]
true <- user.local,
true <- user.info.confirmation_pending, with %User{info: info} = user <- User.get_cached_by_id(uid),
true <- user.info.confirmation_token == token, true <- user.local and info.confirmation_pending and info.confirmation_token == token,
info_change <- User.Info.confirmation_changeset(user.info, need_confirmation: false), {:ok, _} <- User.update_info(user, &User.Info.confirmation_changeset(&1, new_info)) do
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change), redirect(conn, to: "/")
{:ok, _} <- User.update_and_set_cache(changeset) do
conn
|> redirect(to: "/")
end end
end end

View File

@ -77,12 +77,10 @@ test "following and followers count are updated" do
assert length(following) == 2 assert length(following) == 2
assert info.follower_count == 0 assert info.follower_count == 0
info_cng = Ecto.Changeset.change(info, %{follower_count: 3})
{:ok, user} = {:ok, user} =
user user
|> Ecto.Changeset.change(%{following: following ++ following}) |> Ecto.Changeset.change(%{following: following ++ following})
|> Ecto.Changeset.put_embed(:info, info_cng) |> User.change_info(&Ecto.Changeset.change(&1, %{follower_count: 3}))
|> Repo.update() |> Repo.update()
assert length(user.following) == 4 assert length(user.following) == 4

View File

@ -74,8 +74,8 @@ test "returns all pending follow requests" do
CommonAPI.follow(follower, unlocked) CommonAPI.follow(follower, unlocked)
CommonAPI.follow(follower, locked) CommonAPI.follow(follower, locked)
assert {:ok, []} = User.get_follow_requests(unlocked) assert [] = User.get_follow_requests(unlocked)
assert {:ok, [activity]} = User.get_follow_requests(locked) assert [activity] = User.get_follow_requests(locked)
assert activity assert activity
end end
@ -90,7 +90,7 @@ test "doesn't return already accepted or duplicate follow requests" do
CommonAPI.follow(accepted_follower, locked) CommonAPI.follow(accepted_follower, locked)
User.follow(accepted_follower, locked) User.follow(accepted_follower, locked)
assert {:ok, [activity]} = User.get_follow_requests(locked) assert [activity] = User.get_follow_requests(locked)
assert activity assert activity
end end
@ -99,10 +99,10 @@ test "clears follow requests when requester is blocked" do
follower = insert(:user) follower = insert(:user)
CommonAPI.follow(follower, followed) CommonAPI.follow(follower, followed)
assert {:ok, [_activity]} = User.get_follow_requests(followed) assert [_activity] = User.get_follow_requests(followed)
{:ok, _follower} = User.block(followed, follower) {:ok, _follower} = User.block(followed, follower)
assert {:ok, []} = User.get_follow_requests(followed) assert [] = User.get_follow_requests(followed)
end end
test "follow_all follows mutliple users" do test "follow_all follows mutliple users" do
@ -560,7 +560,7 @@ test "it sets the follower_adress" do
test "it enforces the fqn format for nicknames" do test "it enforces the fqn format for nicknames" do
cs = User.remote_user_creation(%{@valid_remote | nickname: "bla"}) cs = User.remote_user_creation(%{@valid_remote | nickname: "bla"})
assert cs.changes.local == false assert Ecto.Changeset.get_field(cs, :local) == false
assert cs.changes.avatar assert cs.changes.avatar
refute cs.valid? refute cs.valid?
end end
@ -584,7 +584,7 @@ test "gets all followers for a given user" do
{:ok, follower_one} = User.follow(follower_one, user) {:ok, follower_one} = User.follow(follower_one, user)
{:ok, follower_two} = User.follow(follower_two, user) {:ok, follower_two} = User.follow(follower_two, user)
{:ok, res} = User.get_followers(user) res = User.get_followers(user)
assert Enum.member?(res, follower_one) assert Enum.member?(res, follower_one)
assert Enum.member?(res, follower_two) assert Enum.member?(res, follower_two)
@ -600,7 +600,7 @@ test "gets all friends (followed users) for a given user" do
{:ok, user} = User.follow(user, followed_one) {:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two) {:ok, user} = User.follow(user, followed_two)
{:ok, res} = User.get_friends(user) res = User.get_friends(user)
followed_one = User.get_cached_by_ap_id(followed_one.ap_id) followed_one = User.get_cached_by_ap_id(followed_one.ap_id)
followed_two = User.get_cached_by_ap_id(followed_two.ap_id) followed_two = User.get_cached_by_ap_id(followed_two.ap_id)
@ -975,7 +975,7 @@ test "hide a user from followers " do
info = User.get_cached_user_info(user2) info = User.get_cached_user_info(user2)
assert info.follower_count == 0 assert info.follower_count == 0
assert {:ok, []} = User.get_followers(user2) assert [] = User.get_followers(user2)
end end
test "hide a user from friends" do test "hide a user from friends" do
@ -991,7 +991,7 @@ test "hide a user from friends" do
assert info.following_count == 0 assert info.following_count == 0
assert User.following_count(user2) == 0 assert User.following_count(user2) == 0
assert {:ok, []} = User.get_friends(user2) assert [] = User.get_friends(user2)
end end
test "hide a user's statuses from timelines and notifications" do test "hide a user's statuses from timelines and notifications" do
@ -1034,7 +1034,7 @@ test "hide a user's statuses from timelines and notifications" do
test ".delete_user_activities deletes all create activities", %{user: user} do test ".delete_user_activities deletes all create activities", %{user: user} do
{:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"}) {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
{:ok, _} = User.delete_user_activities(user) User.delete_user_activities(user)
# TODO: Remove favorites, repeats, delete activities. # TODO: Remove favorites, repeats, delete activities.
refute Activity.get_by_id(activity.id) refute Activity.get_by_id(activity.id)
@ -1707,4 +1707,22 @@ test "sets password_reset_pending to true", %{user: user} do
assert password_reset_pending assert password_reset_pending
end end
end end
test "change_info/2" do
user = insert(:user)
assert user.info.hide_follows == false
changeset = User.change_info(user, &User.Info.profile_update(&1, %{hide_follows: true}))
assert changeset.changes.info.changes.hide_follows == true
end
test "update_info/2" do
user = insert(:user)
assert user.info.hide_follows == false
assert {:ok, _} = User.update_info(user, &User.Info.profile_update(&1, %{hide_follows: true}))
assert %{info: %{hide_follows: true}} = Repo.get(User, user.id)
assert {:ok, %{info: %{hide_follows: true}}} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
end
end end

View File

@ -2613,14 +2613,11 @@ test "get instance stats", %{conn: conn} do
{:ok, _} = CommonAPI.post(user, %{"status" => "cofe"}) {:ok, _} = CommonAPI.post(user, %{"status" => "cofe"})
# Stats should count users with missing or nil `info.deactivated` value # Stats should count users with missing or nil `info.deactivated` value
user = User.get_cached_by_id(user.id)
info_change = Changeset.change(user.info, %{deactivated: nil})
{:ok, _user} = {:ok, _user} =
user user.id
|> Changeset.change() |> User.get_cached_by_id()
|> Changeset.put_embed(:info, info_change) |> User.update_info(&Changeset.change(&1, %{deactivated: nil}))
|> User.update_and_set_cache()
Pleroma.Stats.force_update() Pleroma.Stats.force_update()
@ -3953,13 +3950,9 @@ test "it returns 400 when user is not local", %{conn: conn, user: user} do
describe "POST /api/v1/pleroma/accounts/confirmation_resend" do describe "POST /api/v1/pleroma/accounts/confirmation_resend" do
setup do setup do
user = insert(:user)
info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true)
{:ok, user} = {:ok, user} =
user insert(:user)
|> Changeset.change() |> User.change_info(&User.Info.confirmation_changeset(&1, need_confirmation: true))
|> Changeset.put_embed(:info, info_change)
|> Repo.update() |> Repo.update()
assert user.info.confirmation_pending assert user.info.confirmation_pending

View File

@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
@ -775,15 +776,11 @@ test "rejects token exchange with invalid client credentials" do
test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do
Pleroma.Config.put([:instance, :account_activation_required], true) Pleroma.Config.put([:instance, :account_activation_required], true)
password = "testpassword" password = "testpassword"
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
info_change = Pleroma.User.Info.confirmation_changeset(user.info, need_confirmation: true)
{:ok, user} = {:ok, user} =
user insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
|> Ecto.Changeset.change() |> User.change_info(&User.Info.confirmation_changeset(&1, need_confirmation: true))
|> Ecto.Changeset.put_embed(:info, info_change)
|> Repo.update() |> Repo.update()
refute Pleroma.User.auth_active?(user) refute Pleroma.User.auth_active?(user)

View File

@ -50,20 +50,16 @@ test "decodes a salmon with a changed magic key", %{conn: conn} do
assert response(conn, 200) assert response(conn, 200)
end) =~ "[error]" end) =~ "[error]"
# Set a wrong magic-key for a user so it has to refetch
salmon_user = User.get_cached_by_ap_id("http://gs.example.org:4040/index.php/user/1")
# Wrong key # Wrong key
info_cng = info = %{
User.Info.remote_user_creation(salmon_user.info, %{
magic_key: magic_key:
"RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB" "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
}) }
salmon_user # Set a wrong magic-key for a user so it has to refetch
|> Ecto.Changeset.change() "http://gs.example.org:4040/index.php/user/1"
|> Ecto.Changeset.put_embed(:info, info_cng) |> User.get_cached_by_ap_id()
|> User.update_and_set_cache() |> User.update_info(&User.Info.remote_user_creation(&1, info))
assert capture_log(fn -> assert capture_log(fn ->
conn = conn =