Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/expire...
[akkoma] / lib / pleroma / web / mastodon_api / controllers / status_controller.ex
index 9eea2e9eb1b4fd65cec5ceb5787d4317ca00f800..4d9be5240c912fc03416501af38035c4261e0c46 100644 (file)
@@ -6,15 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   use Pleroma.Web, :controller
 
   import Pleroma.Web.ControllerHelper,
-    only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1]
+    only: [try_render: 3, add_link_headers: 2]
 
   require Ecto.Query
 
   alias Pleroma.Activity
   alias Pleroma.Bookmark
   alias Pleroma.Object
-  alias Pleroma.Plugs.OAuthScopesPlug
-  alias Pleroma.Plugs.RateLimiter
   alias Pleroma.Repo
   alias Pleroma.ScheduledActivity
   alias Pleroma.User
@@ -23,8 +21,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.MastodonAPI.ScheduledActivityView
+  alias Pleroma.Web.Plugs.OAuthScopesPlug
+  alias Pleroma.Web.Plugs.RateLimiter
 
-  plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show])
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+  plug(
+    :skip_plug,
+    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]
+  )
 
   @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
 
@@ -83,13 +88,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
   plug(
     RateLimiter,
-    [name: :status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]]
+    [name: :status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: [:id]]
     when action in ~w(reblog unreblog)a
   )
 
   plug(
     RateLimiter,
-    [name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]]
+    [name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: [:id]]
     when action in ~w(favourite unfavourite)a
   )
 
@@ -97,12 +102,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.StatusOperation
+
   @doc """
   GET `/api/v1/statuses?ids[]=1&ids[]=2`
 
   `ids` query param is required
   """
-  def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do
+  def index(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do
     limit = 100
 
     activities =
@@ -114,45 +121,47 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
     render(conn, "index.json",
       activities: activities,
       for: user,
-      as: :activity,
-      skip_relationships: skip_relationships?(params)
+      as: :activity
     )
   end
 
   @doc """
   POST /api/v1/statuses
-
-  Creates a scheduled status when `scheduled_at` param is present and it's far enough
   """
+  # Creates a scheduled status when `scheduled_at` param is present and it's far enough
   def create(
-        %{assigns: %{user: user}} = conn,
-        %{"status" => _, "scheduled_at" => scheduled_at} = params
+        %{
+          assigns: %{user: user},
+          body_params: %{status: _, scheduled_at: scheduled_at} = params
+        } = conn,
+        _
       )
       when not is_nil(scheduled_at) do
-    params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
+    params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
+
+    attrs = %{
+      params: Map.new(params, fn {key, value} -> {to_string(key), value} end),
+      scheduled_at: scheduled_at
+    }
 
     with {:far_enough, true} <- {:far_enough, ScheduledActivity.far_enough?(scheduled_at)},
-         attrs <- %{"params" => params, "scheduled_at" => scheduled_at},
          {:ok, scheduled_activity} <- ScheduledActivity.create(user, attrs) do
       conn
       |> put_view(ScheduledActivityView)
       |> render("show.json", scheduled_activity: scheduled_activity)
     else
       {:far_enough, _} ->
-        create(conn, Map.drop(params, ["scheduled_at"]))
+        params = Map.drop(params, [:scheduled_at])
+        create(%Plug.Conn{conn | body_params: params}, %{})
 
       error ->
         error
     end
   end
 
-  @doc """
-  POST /api/v1/statuses
-
-  Creates a regular status
-  """
-  def create(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
-    params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
+  # Creates a regular status
+  def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do
+    params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
 
     with {:ok, activity} <- CommonAPI.post(user, params) do
       try_render(conn, "show.json",
@@ -162,6 +171,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
         with_direct_conversation_id: true
       )
     else
+      {:error, {:reject, message}} ->
+        conn
+        |> put_status(:unprocessable_entity)
+        |> json(%{error: message})
+
       {:error, message} ->
         conn
         |> put_status(:unprocessable_entity)
@@ -169,12 +183,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
     end
   end
 
-  def create(%{assigns: %{user: _user}} = conn, %{"media_ids" => _} = params) do
-    create(conn, Map.put(params, "status", ""))
+  def create(%{assigns: %{user: _user}, body_params: %{media_ids: _} = params} = conn, _) do
+    params = Map.put(params, :status, "")
+    create(%Plug.Conn{conn | body_params: params}, %{})
   end
 
   @doc "GET /api/v1/statuses/:id"
-  def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+  def show(%{assigns: %{user: user}} = conn, %{id: id}) do
     with %Activity{} = activity <- Activity.get_by_id_with_object(id),
          true <- Visibility.visible_for_user?(activity, user) do
       try_render(conn, "show.json",
@@ -188,33 +203,38 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "DELETE /api/v1/statuses/:id"
-  def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
-      json(conn, %{})
+  def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
+    with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+         {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
+      try_render(conn, "show.json",
+        activity: activity,
+        for: user,
+        with_direct_conversation_id: true,
+        with_source: true
+      )
     else
-      {:error, :not_found} = e -> e
-      _e -> render_error(conn, :forbidden, "Can't delete this post")
+      _e -> {:error, :not_found}
     end
   end
 
   @doc "POST /api/v1/statuses/:id/reblog"
-  def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do
-    with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params),
+  def reblog(%{assigns: %{user: user}, body_params: params} = conn, %{id: ap_id_or_id}) do
+    with {:ok, announce} <- CommonAPI.repeat(ap_id_or_id, user, params),
          %Activity{} = announce <- Activity.normalize(announce.data) do
       try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
     end
   end
 
   @doc "POST /api/v1/statuses/:id/unreblog"
-  def unreblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
-         %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
+  def unreblog(%{assigns: %{user: user}} = conn, %{id: activity_id}) do
+    with {:ok, _unannounce} <- CommonAPI.unrepeat(activity_id, user),
+         %Activity{} = activity <- Activity.get_by_id(activity_id) do
       try_render(conn, "show.json", %{activity: activity, for: user, as: :activity})
     end
   end
 
   @doc "POST /api/v1/statuses/:id/favourite"
-  def favourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
+  def favourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do
     with {:ok, _fav} <- CommonAPI.favorite(user, activity_id),
          %Activity{} = activity <- Activity.get_by_id(activity_id) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
@@ -222,29 +242,29 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "POST /api/v1/statuses/:id/unfavourite"
-  def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
-         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
+  def unfavourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do
+    with {:ok, _unfav} <- CommonAPI.unfavorite(activity_id, user),
+         %Activity{} = activity <- Activity.get_by_id(activity_id) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
     end
   end
 
   @doc "POST /api/v1/statuses/:id/pin"
-  def pin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+  def pin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do
     with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
     end
   end
 
   @doc "POST /api/v1/statuses/:id/unpin"
-  def unpin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+  def unpin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do
     with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
     end
   end
 
   @doc "POST /api/v1/statuses/:id/bookmark"
-  def bookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+  def bookmark(%{assigns: %{user: user}} = conn, %{id: id}) do
     with %Activity{} = activity <- Activity.get_by_id_with_object(id),
          %User{} = user <- User.get_cached_by_nickname(user.nickname),
          true <- Visibility.visible_for_user?(activity, user),
@@ -254,7 +274,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "POST /api/v1/statuses/:id/unbookmark"
-  def unbookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+  def unbookmark(%{assigns: %{user: user}} = conn, %{id: id}) do
     with %Activity{} = activity <- Activity.get_by_id_with_object(id),
          %User{} = user <- User.get_cached_by_nickname(user.nickname),
          true <- Visibility.visible_for_user?(activity, user),
@@ -264,15 +284,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "POST /api/v1/statuses/:id/mute"
-  def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+  def mute_conversation(%{assigns: %{user: user}, body_params: params} = conn, %{id: id}) do
     with %Activity{} = activity <- Activity.get_by_id(id),
-         {:ok, activity} <- CommonAPI.add_mute(user, activity) do
+         {:ok, activity} <- CommonAPI.add_mute(user, activity, params) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
     end
   end
 
   @doc "POST /api/v1/statuses/:id/unmute"
-  def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+  def unmute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do
     with %Activity{} = activity <- Activity.get_by_id(id),
          {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
@@ -281,7 +301,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
   @doc "GET /api/v1/statuses/:id/card"
   @deprecated "https://github.com/tootsuite/mastodon/pull/11213"
-  def card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
+  def card(%{assigns: %{user: user}} = conn, %{id: status_id}) do
     with %Activity{} = activity <- Activity.get_by_id(status_id),
          true <- Visibility.visible_for_user?(activity, user) do
       data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
@@ -292,8 +312,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "GET /api/v1/statuses/:id/favourited_by"
-  def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+  def favourited_by(%{assigns: %{user: user}} = conn, %{id: id}) do
+    with true <- Pleroma.Config.get([:instance, :show_reactions]),
+         %Activity{} = activity <- Activity.get_by_id_with_object(id),
          {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
          %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
       users =
@@ -312,7 +333,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "GET /api/v1/statuses/:id/reblogged_by"
-  def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+  def reblogged_by(%{assigns: %{user: user}} = conn, %{id: id}) do
     with %Activity{} = activity <- Activity.get_by_id_with_object(id),
          {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
          %Object{data: %{"announcements" => announces, "id" => ap_id}} <-
@@ -344,13 +365,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "GET /api/v1/statuses/:id/context"
-  def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+  def context(%{assigns: %{user: user}} = conn, %{id: id}) do
     with %Activity{} = activity <- Activity.get_by_id(id) do
       activities =
         ActivityPub.fetch_activities_for_context(activity.data["context"], %{
-          "blocking_user" => user,
-          "user" => user,
-          "exclude_id" => activity.id
+          blocking_user: user,
+          user: user,
+          exclude_id: activity.id
         })
 
       render(conn, "context.json", activity: activity, activities: activities, user: user)
@@ -359,19 +380,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
   @doc "GET /api/v1/favourites"
   def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do
-    activities =
-      ActivityPub.fetch_favourites(
-        user,
-        Map.take(params, Pleroma.Pagination.page_keys())
-      )
+    activities = ActivityPub.fetch_favourites(user, params)
 
     conn
     |> add_link_headers(activities)
     |> render("index.json",
       activities: activities,
       for: user,
-      as: :activity,
-      skip_relationships: skip_relationships?(params)
+      as: :activity
     )
   end
 
@@ -393,8 +409,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
     |> render("index.json",
       activities: activities,
       for: user,
-      as: :activity,
-      skip_relationships: skip_relationships?(params)
+      as: :activity
     )
   end
 end