field(:search_type, :integer, virtual: true)
field(:tags, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime_usec)
+ field(:last_digest_emailed_at, :naive_datetime)
has_many(:notifications, Notification)
has_many(:registrations, Registration)
embeds_one(:info, User.Info)
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
+ @spec ap_following(User.t()) :: Sring.t()
+ def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
+ def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
+
def user_info(%User{} = user, args \\ %{}) do
following_count =
if args[:following_count], do: args[:following_count], else: following_count(user)
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
end
+ @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
def restrict_deactivated(query) do
from(u in query,
where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
|> Repo.all()
end
- def mute(muter, %User{ap_id: ap_id}) do
+ @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
+ def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
+ info = muter.info
+
info_cng =
- muter.info
- |> User.Info.add_to_mutes(ap_id)
+ User.Info.add_to_mutes(info, ap_id)
+ |> User.Info.add_to_muted_notifications(info, ap_id, notifications?)
cng =
change(muter)
end
def unmute(muter, %{ap_id: ap_id}) do
+ info = muter.info
+
info_cng =
- muter.info
- |> User.Info.remove_from_mutes(ap_id)
+ User.Info.remove_from_mutes(info, ap_id)
+ |> User.Info.remove_from_muted_notifications(info, ap_id)
cng =
change(muter)
def mutes?(nil, _), do: false
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
+ @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
+ def muted_notifications?(nil, _), do: false
+
+ def muted_notifications?(user, %{ap_id: ap_id}),
+ do: Enum.member?(user.info.muted_notifications, ap_id)
+
def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
blocks = info.blocks
domain_blocks = info.domain_blocks
)
end
- @spec sync_follow_counter() :: :ok
- def sync_follow_counter,
- do: PleromaJobQueue.enqueue(:background, __MODULE__, [:sync_follow_counters])
-
- @spec perform(:sync_follow_counters) :: :ok
- def perform(:sync_follow_counters) do
- {:ok, _pid} = Agent.start_link(fn -> %{} end, name: :domain_errors)
- config = Pleroma.Config.get([:instance, :external_user_synchronization])
-
- :ok = sync_follow_counters(config)
- Agent.stop(:domain_errors)
- end
-
- @spec sync_follow_counters(keyword()) :: :ok
- def sync_follow_counters(opts \\ []) do
- users = external_users(opts)
-
- if length(users) > 0 do
- errors = Agent.get(:domain_errors, fn state -> state end)
- {last, updated_errors} = User.Synchronization.call(users, errors, opts)
- Agent.update(:domain_errors, fn _state -> updated_errors end)
- sync_follow_counters(max_id: last.id, limit: opts[:limit])
- else
- :ok
- end
- end
-
@spec external_users_query() :: Ecto.Query.t()
def external_users_query do
User.Query.build(%{
end
# OStatus Magic Key
- def public_key_from_info(%{magic_key: magic_key}) do
+ def public_key_from_info(%{magic_key: magic_key}) when not is_nil(magic_key) do
{:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
end
+ def public_key_from_info(_), do: {:error, "not found key"}
+
def get_public_key_for_ap_id(ap_id) do
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
{:ok, public_key} <- public_key_from_info(user.info) do
target.ap_id not in user.info.muted_reblogs
end
+ @doc """
+ The function returns a query to get users with no activity for given interval of days.
+ Inactive users are those who didn't read any notification, or had any activity where
+ the user is the activity's actor, during `inactivity_threshold` days.
+ Deactivated users will not appear in this list.
+
+ ## Examples
+
+ iex> Pleroma.User.list_inactive_users()
+ %Ecto.Query{}
+ """
+ @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
+ def list_inactive_users_query(inactivity_threshold \\ 7) do
+ negative_inactivity_threshold = -inactivity_threshold
+ now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
+ # Subqueries are not supported in `where` clauses, join gets too complicated.
+ has_read_notifications =
+ from(n in Pleroma.Notification,
+ where: n.seen == true,
+ group_by: n.id,
+ having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
+ select: n.user_id
+ )
+ |> Pleroma.Repo.all()
+
+ from(u in Pleroma.User,
+ left_join: a in Pleroma.Activity,
+ on: u.ap_id == a.actor,
+ where: not is_nil(u.nickname),
+ where: fragment("not (?->'deactivated' @> 'true')", u.info),
+ where: u.id not in ^has_read_notifications,
+ group_by: u.id,
+ having:
+ max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
+ is_nil(max(a.inserted_at))
+ )
+ end
+
+ @doc """
+ Enable or disable email notifications for user
+
+ ## Examples
+
+ iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
+ Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
+
+ iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
+ Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
+ """
+ @spec switch_email_notifications(t(), String.t(), boolean()) ::
+ {:ok, t()} | {:error, Ecto.Changeset.t()}
+ def switch_email_notifications(user, type, status) do
+ info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status})
+
+ change(user)
+ |> put_embed(:info, info)
+ |> update_and_set_cache()
+ end
+
+ @doc """
+ Set `last_digest_emailed_at` value for the user to current time
+ """
+ @spec touch_last_digest_emailed_at(t()) :: t()
+ def touch_last_digest_emailed_at(user) do
+ now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
+
+ {:ok, updated_user} =
+ user
+ |> change(%{last_digest_emailed_at: now})
+ |> update_and_set_cache()
+
+ updated_user
+ end
+
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
def toggle_confirmation(%User{} = user) do
need_confirmation? = !user.info.confirmation_pending
}
end
- def ensure_keys_present(user) do
- info = user.info
-
+ def ensure_keys_present(%User{info: info} = user) do
if info.keys do
{:ok, user}
else
{:ok, pem} = Keys.generate_rsa_pem()
- info_cng =
- info
- |> User.Info.set_keys(pem)
-
- cng =
- Ecto.Changeset.change(user)
- |> Ecto.Changeset.put_embed(:info, info_cng)
-
- update_and_set_cache(cng)
+ user
+ |> Ecto.Changeset.change()
+ |> Ecto.Changeset.put_embed(:info, User.Info.set_keys(info, pem))
+ |> update_and_set_cache()
end
end