alias Pleroma.Emoji
alias Pleroma.FollowingRelationship
alias Pleroma.Formatter
+ alias Pleroma.Hashtag
+ alias Pleroma.User.HashtagFollow
alias Pleroma.HTML
alias Pleroma.Keys
alias Pleroma.MFA
field(:is_suggested, :boolean, default: false)
field(:last_status_at, :naive_datetime)
field(:language, :string)
+ field(:status_ttl_days, :integer, default: nil)
embeds_one(
:notification_settings,
has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
+ has_many(:frontend_profiles, Pleroma.Akkoma.FrontendSettingsProfile)
+
+ many_to_many(:followed_hashtags, Hashtag,
+ on_replace: :delete,
+ on_delete: :delete_all,
+ join_through: HashtagFollow
+ )
+
for {relationship_type,
[
{outgoing_relation, outgoing_relation_target},
:pleroma_settings_store,
:is_discoverable,
:actor_type,
- :disclose_client
+ :disclose_client,
+ :status_ttl_days
]
)
|> unique_constraint(:nickname)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
|> validate_inclusion(:actor_type, ["Person", "Service"])
+ |> validate_number(:status_ttl_days, greater_than: 0)
|> put_fields()
|> put_emoji()
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
@spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def force_password_reset(user), do: update_password_reset_pending(user, true)
+ # Used to auto-register LDAP accounts which won't have a password hash stored locally
+ def register_changeset_ldap(struct, params = %{password: password})
+ when is_nil(password) do
+ params =
+ if Map.has_key?(params, :email) do
+ Map.put_new(params, :email, params[:email])
+ else
+ params
+ end
+
+ struct
+ |> cast(params, [
+ :name,
+ :nickname,
+ :email
+ ])
+ |> validate_required([:name, :nickname])
+ |> unique_constraint(:nickname)
+ |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
+ |> validate_format(:nickname, local_nickname_regex())
+ |> put_ap_id()
+ |> unique_constraint(:ap_id)
+ |> put_following_and_follower_and_featured_address()
+ |> put_private_key()
+ end
+
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
|> put_ap_id()
|> unique_constraint(:ap_id)
|> put_following_and_follower_and_featured_address()
+ |> put_private_key()
end
def maybe_validate_required_email(changeset, true), do: changeset
end
end
- defp put_ap_id(changeset) do
+ def 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_and_featured_address(changeset) do
+ def put_following_and_follower_and_featured_address(changeset) do
user = %User{nickname: get_field(changeset, :nickname)}
followers = ap_followers(user)
following = ap_following(user)
|> put_change(:featured_address, featured)
end
+ defp put_private_key(changeset) do
+ {:ok, pem} = Keys.generate_rsa_pem()
+ put_change(changeset, :keys, pem)
+ end
+
defp autofollow_users(user) do
candidates = Config.get([:instance, :autofollowed_nicknames])
{%User{} = user, _} ->
{:ok, user}
- _ ->
+ e ->
+ Logger.error("Could not fetch user, #{inspect(e)}")
{:error, :not_found}
end
end
follower_address: uri <> "/followers"
}
|> change
+ |> put_private_key()
|> unique_constraint(:nickname)
|> Repo.insert()
|> set_cache()
@doc "Gets or fetch a user by uri or nickname."
@spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
- def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
+ def get_or_fetch("http://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
+ def get_or_fetch("https://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
# wait a period of time and return newest version of the User structs
|> Enum.map(&String.downcase/1)
end
- defp local_nickname_regex do
+ def local_nickname_regex do
if Config.get([:instance, :extended_nickname_format]) do
@extended_local_nickname_regex
else
}
end
- def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
-
- def ensure_keys_present(%User{} = user) do
- with {:ok, pem} <- Keys.generate_rsa_pem() do
- user
- |> cast(%{keys: pem}, [:keys])
- |> validate_required([:keys])
- |> update_and_set_cache()
- end
- end
-
def get_ap_ids_by_nicknames(nicknames) do
from(u in User,
where: u.nickname in ^nicknames,
_ -> {:error, user}
end
end
+
+ defp maybe_load_followed_hashtags(%User{followed_hashtags: follows} = user)
+ when is_list(follows),
+ do: user
+
+ defp maybe_load_followed_hashtags(%User{} = user) do
+ followed_hashtags = HashtagFollow.get_by_user(user)
+ %{user | followed_hashtags: followed_hashtags}
+ end
+
+ def followed_hashtags(%User{followed_hashtags: follows})
+ when is_list(follows),
+ do: follows
+
+ def followed_hashtags(%User{} = user) do
+ {:ok, user} =
+ user
+ |> maybe_load_followed_hashtags()
+ |> set_cache()
+
+ user.followed_hashtags
+ end
+
+ def follow_hashtag(%User{} = user, %Hashtag{} = hashtag) do
+ Logger.debug("Follow hashtag #{hashtag.name} for user #{user.nickname}")
+ user = maybe_load_followed_hashtags(user)
+
+ with {:ok, _} <- HashtagFollow.new(user, hashtag),
+ follows <- HashtagFollow.get_by_user(user),
+ %User{} = user <- user |> Map.put(:followed_hashtags, follows) do
+ user
+ |> set_cache()
+ end
+ end
+
+ def unfollow_hashtag(%User{} = user, %Hashtag{} = hashtag) do
+ Logger.debug("Unfollow hashtag #{hashtag.name} for user #{user.nickname}")
+ user = maybe_load_followed_hashtags(user)
+
+ with {:ok, _} <- HashtagFollow.delete(user, hashtag),
+ follows <- HashtagFollow.get_by_user(user),
+ %User{} = user <- user |> Map.put(:followed_hashtags, follows) do
+ user
+ |> set_cache()
+ end
+ end
+
+ def following_hashtag?(%User{} = user, %Hashtag{} = hashtag) do
+ not is_nil(HashtagFollow.get(user, hashtag))
+ end
end