Merge branch 'refactor/common_api' into 'develop'
[akkoma] / lib / pleroma / web / mastodon_api / controllers / mastodon_api_controller.ex
index 6421c2c534c35821be7faceeddf0f1252b0ecab6..e4ae632312a1da57d2312950d38d18c9b3afbbd0 100644 (file)
@@ -6,17 +6,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   use Pleroma.Web, :controller
 
   import Pleroma.Web.ControllerHelper,
-    only: [json_response: 3, add_link_headers: 2, add_link_headers: 3]
+    only: [json_response: 3, add_link_headers: 2, truthy_param?: 1]
 
   alias Ecto.Changeset
   alias Pleroma.Activity
   alias Pleroma.Bookmark
   alias Pleroma.Config
   alias Pleroma.Conversation.Participation
+  alias Pleroma.Emoji
   alias Pleroma.Filter
-  alias Pleroma.Formatter
   alias Pleroma.HTTP
-  alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Pagination
   alias Pleroma.Plugs.RateLimiter
@@ -35,7 +34,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   alias Pleroma.Web.MastodonAPI.ListView
   alias Pleroma.Web.MastodonAPI.MastodonAPI
   alias Pleroma.Web.MastodonAPI.MastodonView
-  alias Pleroma.Web.MastodonAPI.NotificationView
   alias Pleroma.Web.MastodonAPI.ReportView
   alias Pleroma.Web.MastodonAPI.ScheduledActivityView
   alias Pleroma.Web.MastodonAPI.StatusView
@@ -46,7 +44,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.TwitterAPI.TwitterAPI
 
-  alias Pleroma.Web.ControllerHelper
   import Ecto.Query
 
   require Logger
@@ -140,7 +137,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     user_info_emojis =
       user.info
       |> Map.get(:emoji, [])
-      |> Enum.concat(Formatter.get_emoji_map(emojis_text))
+      |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
       |> Enum.dedup()
 
     info_params =
@@ -153,11 +150,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         :hide_follows,
         :hide_favorites,
         :show_role,
-        :skip_thread_containment
+        :skip_thread_containment,
+        :discoverable
       ]
       |> Enum.reduce(%{}, fn key, acc ->
         add_if_present(acc, params, to_string(key), key, fn value ->
-          {:ok, ControllerHelper.truthy_param?(value)}
+          {:ok, truthy_param?(value)}
         end)
       end)
       |> add_if_present(params, "default_scope", :default_scope)
@@ -188,14 +186,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       end)
       |> 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),
-         changeset <- Changeset.put_embed(changeset, :info, info_cng),
-         {:ok, user} <- User.update_and_set_cache(changeset) do
-      if original_user != user do
-        CommonAPI.update(user)
-      end
+    with {:ok, user} <- User.update_and_set_cache(changeset) do
+      if original_user != user, do: CommonAPI.update(user)
 
       json(
         conn,
@@ -225,12 +222,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
-    with 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)
+    new_info = %{"banner" => %{}}
 
+    with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+      CommonAPI.update(user)
       json(conn, %{url: nil})
     end
   end
@@ -238,9 +233,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   def update_banner(%{assigns: %{user: user}} = conn, params) do
     with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
          new_info <- %{"banner" => object.data},
-         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
+         {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
       CommonAPI.update(user)
       %{"url" => [%{"href" => href} | _]} = object.data
 
@@ -249,10 +242,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
-    with new_info <- %{"background" => %{}},
-         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
+    new_info = %{"background" => %{}}
+
+    with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
       json(conn, %{url: nil})
     end
   end
@@ -260,9 +252,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   def update_background(%{assigns: %{user: user}} = conn, params) do
     with {:ok, object} <- ActivityPub.upload(params, type: :background),
          new_info <- %{"background" => object.data},
-         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
+         {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
       %{"url" => [%{"href" => href} | _]} = object.data
 
       json(conn, %{url: href})
@@ -333,7 +323,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
 
   defp mastodonized_emoji do
     Pleroma.Emoji.get_all()
-    |> Enum.map(fn {shortcode, relative_url, tags} ->
+    |> Enum.map(fn {shortcode, %Pleroma.Emoji{file: relative_url, tags: tags}} ->
       url = to_string(URI.merge(Web.base_url(), relative_url))
 
       %{
@@ -353,43 +343,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     json(conn, mastodon_emoji)
   end
 
-  def home_timeline(%{assigns: %{user: user}} = conn, params) do
-    params =
-      params
-      |> Map.put("type", ["Create", "Announce"])
-      |> Map.put("blocking_user", user)
-      |> Map.put("muting_user", user)
-      |> Map.put("user", user)
-
-    activities =
-      [user.ap_id | user.following]
-      |> ActivityPub.fetch_activities(params)
-      |> Enum.reverse()
-
-    conn
-    |> add_link_headers(activities)
-    |> put_view(StatusView)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
-  end
-
-  def public_timeline(%{assigns: %{user: user}} = conn, params) do
-    local_only = params["local"] in [true, "True", "true", "1"]
-
-    activities =
-      params
-      |> Map.put("type", ["Create", "Announce"])
-      |> Map.put("local_only", local_only)
-      |> Map.put("blocking_user", user)
-      |> Map.put("muting_user", user)
-      |> ActivityPub.fetch_public_activities()
-      |> Enum.reverse()
-
-    conn
-    |> add_link_headers(activities, %{"local" => local_only})
-    |> put_view(StatusView)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
-  end
-
   def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
     with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
       params =
@@ -409,25 +362,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
-  def dm_timeline(%{assigns: %{user: user}} = conn, params) do
-    params =
-      params
-      |> Map.put("type", "Create")
-      |> Map.put("blocking_user", user)
-      |> Map.put("user", user)
-      |> Map.put(:visibility, "direct")
-
-    activities =
-      [user.ap_id]
-      |> ActivityPub.fetch_activities_query(params)
-      |> Pagination.fetch_paginated(params)
-
-    conn
-    |> add_link_headers(activities)
-    |> put_view(StatusView)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
-  end
-
   def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
     limit = 100
 
@@ -584,14 +518,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
-  def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
-    params =
-      params
-      |> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
-
-    scheduled_at = params["scheduled_at"]
-
-    if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
+  def post_status(
+        %{assigns: %{user: user}} = conn,
+        %{"status" => _, "scheduled_at" => scheduled_at} = params
+      ) do
+    if ScheduledActivity.far_enough?(scheduled_at) do
       with {:ok, scheduled_activity} <-
              ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
         conn
@@ -599,24 +530,26 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         |> render("show.json", %{scheduled_activity: scheduled_activity})
       end
     else
-      params = Map.drop(params, ["scheduled_at"])
-
-      case CommonAPI.post(user, params) do
-        {:error, message} ->
-          conn
-          |> put_status(:unprocessable_entity)
-          |> json(%{error: message})
-
-        {:ok, activity} ->
-          conn
-          |> put_view(StatusView)
-          |> try_render("status.json", %{
-            activity: activity,
-            for: user,
-            as: :activity,
-            with_direct_conversation_id: true
-          })
-      end
+      post_status(conn, Map.drop(params, ["scheduled_at"]))
+    end
+  end
+
+  def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
+    case CommonAPI.post(user, params) do
+      {:ok, activity} ->
+        conn
+        |> put_view(StatusView)
+        |> try_render("status.json", %{
+          activity: activity,
+          for: user,
+          as: :activity,
+          with_direct_conversation_id: true
+        })
+
+      {:error, message} ->
+        conn
+        |> put_status(:unprocessable_entity)
+        |> json(%{error: message})
     end
   end
 
@@ -722,49 +655,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
-  def notifications(%{assigns: %{user: user}} = conn, params) do
-    notifications = MastodonAPI.get_notifications(user, params)
-
-    conn
-    |> add_link_headers(notifications)
-    |> put_view(NotificationView)
-    |> render("index.json", %{notifications: notifications, for: user})
-  end
-
-  def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
-    with {:ok, notification} <- Notification.get(user, id) do
-      conn
-      |> put_view(NotificationView)
-      |> render("show.json", %{notification: notification, for: user})
-    else
-      {:error, reason} ->
-        conn
-        |> put_status(:forbidden)
-        |> json(%{"error" => reason})
-    end
-  end
-
-  def clear_notifications(%{assigns: %{user: user}} = conn, _params) do
-    Notification.clear(user)
-    json(conn, %{})
-  end
-
-  def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
-    with {:ok, _notif} <- Notification.dismiss(user, id) do
-      json(conn, %{})
-    else
-      {:error, reason} ->
-        conn
-        |> put_status(:forbidden)
-        |> json(%{"error" => reason})
-    end
-  end
-
-  def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do
-    Notification.destroy_multiple(user, ids)
-    json(conn, %{})
-  end
-
   def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
     id = List.wrap(id)
     q = from(u in User, where: u.id in ^id)
@@ -816,26 +706,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
     with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
          %{} = attachment_data <- Map.put(object.data, "id", object.id),
-         %{type: type} = rendered <-
+         # Reject if not an image
+         %{type: "image"} = rendered <-
            StatusView.render("attachment.json", %{attachment: attachment_data}) do
-      # Reject if not an image
-      if type == "image" do
-        # Sure!
-        # Save to the user's info
-        info_changeset = User.Info.mascot_update(user.info, rendered)
-
-        user_changeset =
-          user
-          |> Changeset.change()
-          |> Changeset.put_embed(:info, info_changeset)
-
-        {:ok, _user} = User.update_and_set_cache(user_changeset)
+      # Sure!
+      # Save to the user's info
+      {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, rendered))
 
-        conn
-        |> json(rendered)
-      else
-        render_error(conn, :unsupported_media_type, "mascots can only be images")
-      end
+      json(conn, rendered)
+    else
+      %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images")
     end
   end
 
@@ -884,45 +764,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
-  def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
-    local_only = params["local"] in [true, "True", "true", "1"]
-
-    tags =
-      [params["tag"], params["any"]]
-      |> List.flatten()
-      |> Enum.uniq()
-      |> Enum.filter(& &1)
-      |> Enum.map(&String.downcase(&1))
-
-    tag_all =
-      params["all"] ||
-        []
-        |> Enum.map(&String.downcase(&1))
-
-    tag_reject =
-      params["none"] ||
-        []
-        |> Enum.map(&String.downcase(&1))
-
-    activities =
-      params
-      |> Map.put("type", "Create")
-      |> Map.put("local_only", local_only)
-      |> Map.put("blocking_user", user)
-      |> Map.put("muting_user", user)
-      |> Map.put("user", user)
-      |> Map.put("tag", tags)
-      |> Map.put("tag_all", tag_all)
-      |> Map.put("tag_reject", tag_reject)
-      |> ActivityPub.fetch_public_activities()
-      |> Enum.reverse()
-
-    conn
-    |> add_link_headers(activities, %{"local" => local_only})
-    |> put_view(StatusView)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
-  end
-
   def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
     with %User{} = user <- User.get_cached_by_id(id),
          followers <- MastodonAPI.get_followers(user, params) do
@@ -958,11 +799,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
-    with {:ok, follow_requests} <- User.get_follow_requests(followed) do
-      conn
-      |> put_view(AccountView)
-      |> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
-    end
+    follow_requests = User.get_follow_requests(followed)
+
+    conn
+    |> put_view(AccountView)
+    |> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
   end
 
   def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
@@ -1235,31 +1076,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     json(conn, res)
   end
 
-  def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
-    with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
-      params =
-        params
-        |> Map.put("type", "Create")
-        |> Map.put("blocking_user", user)
-        |> Map.put("user", user)
-        |> Map.put("muting_user", user)
-
-      # we must filter the following list for the user to avoid leaking statuses the user
-      # does not actually have permission to see (for more info, peruse security issue #270).
-      activities =
-        following
-        |> Enum.filter(fn x -> x in user.following end)
-        |> ActivityPub.fetch_activities_bounded(following, params)
-        |> Enum.reverse()
-
-      conn
-      |> put_view(StatusView)
-      |> render("index.json", %{activities: activities, for: user, as: :activity})
-    else
-      _e -> render_error(conn, :forbidden, "Error.")
-    end
-  end
-
   def index(%{assigns: %{user: user}} = conn, _params) do
     token = get_session(conn, :oauth_token)
 
@@ -1366,11 +1182,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
-    info_cng = User.Info.mastodon_settings_update(user.info, settings)
-
-    with changeset <- Changeset.change(user),
-         changeset <- Changeset.put_embed(changeset, :info, info_cng),
-         {:ok, _user} <- User.update_and_set_cache(changeset) do
+    with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
       json(conn, %{})
     else
       e ->