Uploading an avatar media exceeding max size returns a 413
[akkoma] / lib / pleroma / user.ex
index a9caec55269cd69f864b72b658c25c8b650b4253..ba30769bbda1b8661091170f7ad64d702cf73038 100644 (file)
@@ -18,6 +18,8 @@ defmodule Pleroma.User do
   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
@@ -145,13 +147,13 @@ defmodule Pleroma.User do
     field(:also_known_as, {:array, ObjectValidators.ObjectID}, 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: %{})
     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,
@@ -166,6 +168,14 @@ defmodule Pleroma.User do
     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},
@@ -455,7 +465,6 @@ defmodule Pleroma.User do
         :invisible,
         :actor_type,
         :also_known_as,
-        :accepts_chat_messages,
         :pinned_objects
       ]
     )
@@ -516,8 +525,8 @@ defmodule Pleroma.User do
         :pleroma_settings_store,
         :is_discoverable,
         :actor_type,
-        :accepts_chat_messages,
-        :disclose_client
+        :disclose_client,
+        :status_ttl_days
       ]
     )
     |> unique_constraint(:nickname)
@@ -525,6 +534,7 @@ defmodule Pleroma.User do
     |> 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)})
@@ -589,7 +599,13 @@ defmodule Pleroma.User do
          {:ok, new_value} <- value_function.(value) do
       put_change(changeset, map_field, new_value)
     else
-      _ -> changeset
+      {:error, :file_too_large} ->
+        Ecto.Changeset.validate_change(changeset, map_field, fn map_field, _value ->
+          [{map_field, "file is too large"}]
+        end)
+
+      _ ->
+        changeset
     end
   end
 
@@ -666,8 +682,6 @@ defmodule Pleroma.User do
   # 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 = Map.put_new(params, :accepts_chat_messages, true)
-
     params =
       if Map.has_key?(params, :email) do
         Map.put_new(params, :email, params[:email])
@@ -679,8 +693,7 @@ defmodule Pleroma.User do
     |> cast(params, [
       :name,
       :nickname,
-      :email,
-      :accepts_chat_messages
+      :email
     ])
     |> validate_required([:name, :nickname])
     |> unique_constraint(:nickname)
@@ -689,13 +702,13 @@ defmodule Pleroma.User do
     |> 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)
     reason_limit = Config.get([:instance, :registration_reason_length], 500)
-    params = Map.put_new(params, :accepts_chat_messages, true)
 
     confirmed? =
       if is_nil(opts[:confirmed]) do
@@ -723,7 +736,6 @@ defmodule Pleroma.User do
       :password,
       :password_confirmation,
       :emoji,
-      :accepts_chat_messages,
       :registration_reason,
       :language
     ])
@@ -751,6 +763,7 @@ defmodule Pleroma.User do
     |> 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
@@ -763,12 +776,12 @@ defmodule Pleroma.User do
     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)
@@ -780,6 +793,11 @@ defmodule Pleroma.User do
     |> 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])
 
@@ -826,8 +844,7 @@ defmodule Pleroma.User do
          {:ok, user} <- set_cache(user),
          {: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, _} <- maybe_send_welcome_message(user) do
       {:ok, user}
     end
   end
@@ -861,15 +878,6 @@ defmodule Pleroma.User do
     end
   end
 
-  defp maybe_send_welcome_chat_message(user) do
-    if User.WelcomeChatMessage.enabled?() do
-      User.WelcomeChatMessage.post_message(user)
-      {:ok, :enqueued}
-    else
-      {:ok, :noop}
-    end
-  end
-
   defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
     if User.WelcomeEmail.enabled?() do
       User.WelcomeEmail.send_email(user)
@@ -1491,13 +1499,19 @@ defmodule Pleroma.User do
         blocker
       end
 
-    # clear any requested follows as well
+    # clear any requested follows from both sides as well
     blocked =
       case CommonAPI.reject_follow_request(blocked, blocker) do
         {:ok, %User{} = updated_blocked} -> updated_blocked
         nil -> blocked
       end
 
+    blocker =
+      case CommonAPI.reject_follow_request(blocker, blocked) do
+        {:ok, %User{} = updated_blocker} -> updated_blocker
+        nil -> blocker
+      end
+
     unsubscribe(blocked, blocker)
 
     unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
@@ -1913,7 +1927,8 @@ defmodule Pleroma.User do
       {%User{} = user, _} ->
         {:ok, user}
 
-      _ ->
+      e ->
+        Logger.error("Could not fetch user #{ap_id}, #{inspect(e)}")
         {:error, :not_found}
     end
   end
@@ -1960,6 +1975,7 @@ defmodule Pleroma.User do
       follower_address: uri <> "/followers"
     }
     |> change
+    |> put_private_key()
     |> unique_constraint(:nickname)
     |> Repo.insert()
     |> set_cache()
@@ -1992,7 +2008,8 @@ defmodule Pleroma.User do
 
   @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
@@ -2078,7 +2095,7 @@ defmodule Pleroma.User do
     |> 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
@@ -2225,17 +2242,6 @@ defmodule Pleroma.User do
     }
   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,
@@ -2558,4 +2564,54 @@ defmodule Pleroma.User do
       _ -> {: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