field(:local, :boolean, default: true)
field(:follower_address, :string)
field(:following_address, :string)
+ field(:featured_address, :string)
field(:search_rank, :float, virtual: true)
field(:search_type, :integer, virtual: true)
field(:tags, {:array, :string}, default: [])
field(:follower_count, :integer, default: 0)
field(:following_count, :integer, default: 0)
field(:is_locked, :boolean, default: false)
- field(:confirmation_pending, :boolean, default: false)
+ field(:is_confirmed, :boolean, default: true)
field(:password_reset_pending, :boolean, default: false)
- field(:approval_pending, :boolean, default: false)
+ field(:is_approved, :boolean, default: true)
field(:registration_reason, :string, default: nil)
field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public")
field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
- field(:pinned_activities, {:array, :string}, default: [])
field(:email_notifications, :map, default: %{"digest" => false})
field(:mascot, :map, default: nil)
field(:emoji, :map, default: %{})
field(:inbox, :string)
field(:shared_inbox, :string)
field(:accepts_chat_messages, :boolean, default: nil)
+ field(:last_active_at, :naive_datetime)
+ field(:disclose_client, :boolean, default: true)
+ field(:pinned_objects, :map, default: %{})
embeds_one(
:notification_settings,
target_users_query = assoc(user, unquote(outgoing_relation_target))
if restrict_deactivated? do
- restrict_deactivated(target_users_query)
+ target_users_query
+ |> User.Query.build(%{deactivated: false})
else
target_users_query
end
@spec account_status(User.t()) :: account_status()
def account_status(%User{is_active: false}), do: :deactivated
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
- def account_status(%User{local: true, approval_pending: true}), do: :approval_pending
-
- def account_status(%User{local: true, confirmation_pending: true}) do
- if Config.get([:instance, :account_activation_required]) do
- :confirmation_pending
- else
- :active
- end
- end
-
+ def account_status(%User{local: true, is_approved: false}), do: :approval_pending
+ def account_status(%User{local: true, is_confirmed: false}), do: :confirmation_pending
def account_status(%User{}), do: :active
@spec visible_for(User.t(), User.t() | nil) ::
end
# Should probably be renamed or removed
+ @spec ap_id(User.t()) :: String.t()
def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
+ @spec ap_followers(User.t()) :: String.t()
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
- @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
- def restrict_deactivated(query) do
- from(u in query, where: u.is_active == ^true)
- end
+ @spec ap_featured_collection(User.t()) :: String.t()
+ def ap_featured_collection(%User{featured_address: fa}) when is_binary(fa), do: fa
+
+ def ap_featured_collection(%User{} = user), do: "#{ap_id(user)}/collections/featured"
defp truncate_fields_param(params) do
if Map.has_key?(params, :fields) do
:uri,
:follower_address,
:following_address,
+ :featured_address,
:hide_followers,
:hide_follows,
:hide_followers_count,
:invisible,
:actor_type,
:also_known_as,
- :accepts_chat_messages
+ :accepts_chat_messages,
+ :pinned_objects
]
)
|> cast(params, [:name], empty_values: [])
:pleroma_settings_store,
:is_discoverable,
:actor_type,
- :accepts_chat_messages
+ :accepts_chat_messages,
+ :disclose_client
]
)
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> put_ap_id()
|> unique_constraint(:ap_id)
- |> put_following_and_follower_address()
+ |> put_following_and_follower_and_featured_address()
end
def register_changeset(struct, params \\ %{}, opts \\ []) do
reason_limit = Config.get([:instance, :registration_reason_length], 500)
params = Map.put_new(params, :accepts_chat_messages, true)
- need_confirmation? =
- if is_nil(opts[:need_confirmation]) do
- Config.get([:instance, :account_activation_required])
+ confirmed? =
+ if is_nil(opts[:confirmed]) do
+ !Config.get([:instance, :account_activation_required])
else
- opts[:need_confirmation]
+ opts[:confirmed]
end
- need_approval? =
- if is_nil(opts[:need_approval]) do
- Config.get([:instance, :account_approval_required])
+ approved? =
+ if is_nil(opts[:approved]) do
+ !Config.get([:instance, :account_approval_required])
else
- opts[:need_approval]
+ opts[:approved]
end
struct
- |> confirmation_changeset(need_confirmation: need_confirmation?)
- |> approval_changeset(need_approval: need_approval?)
+ |> confirmation_changeset(set_confirmation: confirmed?)
+ |> approval_changeset(set_approval: approved?)
|> cast(params, [
:bio,
:raw_bio,
|> put_password_hash
|> put_ap_id()
|> unique_constraint(:ap_id)
- |> put_following_and_follower_address()
+ |> put_following_and_follower_and_featured_address()
end
def maybe_validate_required_email(changeset, true), do: changeset
put_change(changeset, :ap_id, ap_id)
end
- defp put_following_and_follower_address(changeset) do
- followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
+ defp put_following_and_follower_and_featured_address(changeset) do
+ user = %User{nickname: get_field(changeset, :nickname)}
+ followers = ap_followers(user)
+ following = ap_following(user)
+ featured = ap_featured_collection(user)
changeset
|> put_change(:follower_address, followers)
+ |> put_change(:following_address, following)
+ |> put_change(:featured_address, featured)
end
defp autofollow_users(user) do
end
end
- def post_register_action(%User{confirmation_pending: true} = user) do
- with {:ok, _} <- try_send_confirmation_email(user) do
+ def post_register_action(%User{is_confirmed: false} = user) do
+ with {:ok, _} <- maybe_send_confirmation_email(user) do
{:ok, user}
end
end
- def post_register_action(%User{approval_pending: true} = user) do
+ def post_register_action(%User{is_approved: false} = user) do
with {:ok, _} <- send_user_approval_email(user),
{:ok, _} <- send_admin_approval_emails(user) do
{:ok, user}
end
end
- def post_register_action(%User{approval_pending: false, confirmation_pending: false} = user) do
+ def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
with {:ok, user} <- autofollow_users(user),
{:ok, _} <- autofollowing_users(user),
{:ok, user} <- set_cache(user),
- {:ok, _} <- send_welcome_email(user),
- {:ok, _} <- send_welcome_message(user),
- {:ok, _} <- send_welcome_chat_message(user) do
+ {:ok, _} <- maybe_send_registration_email(user),
+ {:ok, _} <- maybe_send_welcome_email(user),
+ {:ok, _} <- maybe_send_welcome_message(user),
+ {:ok, _} <- maybe_send_welcome_chat_message(user) do
{:ok, user}
end
end
{:ok, :enqueued}
end
- def send_welcome_message(user) do
+ defp maybe_send_welcome_message(user) do
if User.WelcomeMessage.enabled?() do
User.WelcomeMessage.post_message(user)
{:ok, :enqueued}
end
end
- def send_welcome_chat_message(user) do
+ defp maybe_send_welcome_chat_message(user) do
if User.WelcomeChatMessage.enabled?() do
User.WelcomeChatMessage.post_message(user)
{:ok, :enqueued}
end
end
- def send_welcome_email(%User{email: email} = user) when is_binary(email) do
+ defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
if User.WelcomeEmail.enabled?() do
User.WelcomeEmail.send_email(user)
{:ok, :enqueued}
end
end
- def send_welcome_email(_), do: {:ok, :noop}
+ defp maybe_send_welcome_email(_), do: {:ok, :noop}
- @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
- def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user)
+ @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
+ def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user)
when is_binary(email) do
if Config.get([:instance, :account_activation_required]) do
send_confirmation_email(user)
end
end
- def try_send_confirmation_email(_), do: {:ok, :noop}
+ def maybe_send_confirmation_email(_), do: {:ok, :noop}
@spec send_confirmation_email(Uset.t()) :: User.t()
def send_confirmation_email(%User{} = user) do
user
end
+ @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop}
+ defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do
+ with false <- User.WelcomeEmail.enabled?(),
+ false <- Config.get([:instance, :account_activation_required], false),
+ false <- Config.get([:instance, :account_approval_required], false) do
+ user
+ |> Pleroma.Emails.UserEmail.successful_registration_email()
+ |> Pleroma.Emails.Mailer.deliver_async()
+
+ {:ok, :enqueued}
+ else
+ _ ->
+ {:ok, :noop}
+ end
+ end
+
+ defp maybe_send_registration_email(_), do: {:ok, :noop}
+
def needs_update?(%User{local: true}), do: false
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
cond do
- followed.is_active == false ->
+ not followed.is_active ->
{:error, "Could not follow user: #{followed.nickname} is deactivated."}
deny_follow_blocked and blocks?(followed, follower) ->
defp maybe_filter_on_ap_id(query, _ap_ids), do: query
- def deactivate_async(user, status \\ true) do
- BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
+ def set_activation_async(user, status \\ true) do
+ BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status})
end
- def deactivate(user, status \\ true)
-
- def deactivate(users, status) when is_list(users) do
+ @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
+ def set_activation(users, status) when is_list(users) do
Repo.transaction(fn ->
- for user <- users, do: deactivate(user, status)
+ for user <- users, do: set_activation(user, status)
end)
end
- def deactivate(%User{} = user, status) do
- with {:ok, user} <- set_activation_status(user, !status) do
+ @spec set_activation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
+ def set_activation(%User{} = user, status) do
+ with {:ok, user} <- set_activation_status(user, status) do
user
|> get_followers()
|> Enum.filter(& &1.local)
end)
end
- def approve(%User{approval_pending: true} = user) do
- with chg <- change(user, approval_pending: false),
+ def approve(%User{is_approved: false} = user) do
+ with chg <- change(user, is_approved: true),
{:ok, user} <- update_and_set_cache(chg) do
post_register_action(user)
{:ok, user}
end)
end
- def confirm(%User{confirmation_pending: true} = user) do
- with chg <- confirmation_changeset(user, need_confirmation: false),
+ def confirm(%User{is_confirmed: false} = user) do
+ with chg <- confirmation_changeset(user, set_confirmation: true),
{:ok, user} <- update_and_set_cache(chg) do
post_register_action(user)
{:ok, user}
follower_count: 0,
following_count: 0,
is_locked: false,
- confirmation_pending: false,
+ is_confirmed: true,
password_reset_pending: false,
- approval_pending: false,
+ is_approved: true,
registration_reason: nil,
confirmation_token: nil,
domain_blocks: [],
delete_or_deactivate(user)
end
- def perform(:deactivate_async, user, status), do: deactivate(user, status)
+ def perform(:set_activation_async, user, status), do: set_activation(user, status)
@spec external_users_query() :: Ecto.Query.t()
def external_users_query do
|> hd()
end
+ def full_nickname(%User{} = user) do
+ if String.contains?(user.nickname, "@") do
+ user.nickname
+ else
+ %{host: host} = URI.parse(user.ap_id)
+ user.nickname <> "@" <> host
+ end
+ end
+
def full_nickname(nickname_or_mention),
do: String.trim_leading(nickname_or_mention, "@")
updated_user
end
- @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
- def need_confirmation(%User{} = user, bool) do
+ @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
+ def set_confirmation(%User{} = user, bool) do
user
- |> confirmation_changeset(need_confirmation: bool)
+ |> confirmation_changeset(set_confirmation: bool)
|> update_and_set_cache()
end
|> update_and_set_cache()
end
- def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
- %{
- admin: is_admin,
- moderator: is_moderator
- }
- end
-
def validate_fields(changeset, remote? \\ false) do
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
limit = Config.get([:instance, limit_name], 0)
end
@spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
- def confirmation_changeset(user, need_confirmation: need_confirmation?) do
+ def confirmation_changeset(user, set_confirmation: confirmed?) do
params =
- if need_confirmation? do
+ if confirmed? do
%{
- confirmation_pending: true,
- confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
+ is_confirmed: true,
+ confirmation_token: nil
}
else
%{
- confirmation_pending: false,
- confirmation_token: nil
+ is_confirmed: false,
+ confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
}
end
- cast(user, params, [:confirmation_pending, :confirmation_token])
+ cast(user, params, [:is_confirmed, :confirmation_token])
end
@spec approval_changeset(User.t(), keyword()) :: Changeset.t()
- def approval_changeset(user, need_approval: need_approval?) do
- params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
- cast(user, params, [:approval_pending])
+ def approval_changeset(user, set_approval: approved?) do
+ cast(user, %{is_approved: approved?}, [:is_approved])
end
- def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
- if id not in user.pinned_activities do
- max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
- params = %{pinned_activities: user.pinned_activities ++ [id]}
-
- # if pinned activity was scheduled for deletion, we remove job
- if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
- Oban.cancel_job(expiration.id)
- end
+ @spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()}
+ def add_pinned_object_id(%User{} = user, object_id) do
+ if !user.pinned_objects[object_id] do
+ params = %{pinned_objects: Map.put(user.pinned_objects, object_id, NaiveDateTime.utc_now())}
user
- |> cast(params, [:pinned_activities])
- |> validate_length(:pinned_activities,
- max: max_pinned_statuses,
- message: "You have already pinned the maximum number of statuses"
- )
+ |> cast(params, [:pinned_objects])
+ |> validate_change(:pinned_objects, fn :pinned_objects, pinned_objects ->
+ max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
+
+ if Enum.count(pinned_objects) <= max_pinned_statuses do
+ []
+ else
+ [pinned_objects: "You have already pinned the maximum number of statuses"]
+ end
+ end)
else
change(user)
end
|> update_and_set_cache()
end
- def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
- params = %{pinned_activities: List.delete(user.pinned_activities, id)}
-
- # if pinned activity was scheduled for deletion, we reschedule it for deletion
- if data["expires_at"] do
- # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
- {:ok, expires_at} =
- data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
-
- Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
- activity_id: id,
- expires_at: expires_at
- })
- end
-
+ @spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()}
+ def remove_pinned_object_id(%User{} = user, object_id) do
user
- |> cast(params, [:pinned_activities])
+ |> cast(
+ %{pinned_objects: Map.delete(user.pinned_objects, object_id)},
+ [:pinned_objects]
+ )
|> update_and_set_cache()
end
def get_host(%User{ap_id: ap_id} = _user) do
URI.parse(ap_id).host
end
+
+ def update_last_active_at(%__MODULE__{local: true} = user) do
+ user
+ |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at])
+ |> update_and_set_cache()
+ end
+
+ def active_user_count(weeks \\ 4) do
+ active_after = Timex.shift(NaiveDateTime.utc_now(), weeks: -weeks)
+
+ __MODULE__
+ |> where([u], u.last_active_at >= ^active_after)
+ |> where([u], u.local == true)
+ |> Repo.aggregate(:count)
+ end
end