Add OpenAPI spec for PleromaAPI.PleromaAPIController
authorEgor Kislitsyn <egor@kislitsyn.com>
Tue, 19 May 2020 17:52:26 +0000 (21:52 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Wed, 20 May 2020 11:15:13 +0000 (15:15 +0400)
docs/API/pleroma_api.md
lib/pleroma/web/api_spec/operations/notification_operation.ex
lib/pleroma/web/api_spec/operations/pleroma_operation.ex [new file with mode: 0644]
lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
test/support/api_spec_helpers.ex
test/web/pleroma_api/controllers/pleroma_api_controller_test.exs

index 867f599190568340a8b233fb8e61cfc69d3596f4..d6dbafc06fd3a944acb36623abe1c3154e63ebe1 100644 (file)
@@ -358,7 +358,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
     * `recipients`: A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though.
 * Response: JSON, statuses (200 - healthy, 503 unhealthy)
 
-## `GET /api/v1/pleroma/conversations/read`
+## `POST /api/v1/pleroma/conversations/read`
 ### Marks all user's conversations as read.
 * Method `POST`
 * Authentication: required
@@ -536,7 +536,7 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
 ```
 
 ## `GET /api/v1/pleroma/statuses/:id/reactions/:emoji`
-### Get an object of emoji to account mappings with accounts that reacted to the post for a specific emoji`
+### Get an object of emoji to account mappings with accounts that reacted to the post for a specific emoji
 * Method: `GET`
 * Authentication: optional
 * Params: None
index 64adc5319f7149b37274953ccab0397b5758a782..46e72f8bf0c82bc0065b999f11070ca157863ec1 100644 (file)
@@ -145,7 +145,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
     }
   end
 
-  defp notification do
+  def notification do
     %Schema{
       title: "Notification",
       description: "Response schema for a notification",
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_operation.ex
new file mode 100644 (file)
index 0000000..c6df5c8
--- /dev/null
@@ -0,0 +1,223 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.PleromaOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.Account
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+  alias Pleroma.Web.ApiSpec.Schemas.Status
+  alias Pleroma.Web.ApiSpec.Schemas.Conversation
+  alias Pleroma.Web.ApiSpec.StatusOperation
+  alias Pleroma.Web.ApiSpec.NotificationOperation
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def emoji_reactions_by_operation do
+    %Operation{
+      tags: ["Emoji Reactions"],
+      summary:
+        "Get an object of emoji to account mappings with accounts that reacted to the post",
+      parameters: [
+        Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
+        Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji",
+          required: false
+        )
+      ],
+      security: [%{"oAuth" => ["read:statuses"]}],
+      operationId: "PleromaController.emoji_reactions_by",
+      responses: %{
+        200 => array_of_reactions_response()
+      }
+    }
+  end
+
+  def react_with_emoji_operation do
+    %Operation{
+      tags: ["Emoji Reactions"],
+      summary: "React to a post with a unicode emoji",
+      parameters: [
+        Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
+        Operation.parameter(:emoji, :path, :string, "A single character unicode emoji",
+          required: true
+        )
+      ],
+      security: [%{"oAuth" => ["write:statuses"]}],
+      operationId: "PleromaController.react_with_emoji",
+      responses: %{
+        200 => Operation.response("Status", "application/json", Status)
+      }
+    }
+  end
+
+  def unreact_with_emoji_operation do
+    %Operation{
+      tags: ["Emoji Reactions"],
+      summary: "Remove a reaction to a post with a unicode emoji",
+      parameters: [
+        Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
+        Operation.parameter(:emoji, :path, :string, "A single character unicode emoji",
+          required: true
+        )
+      ],
+      security: [%{"oAuth" => ["write:statuses"]}],
+      operationId: "PleromaController.unreact_with_emoji",
+      responses: %{
+        200 => Operation.response("Status", "application/json", Status)
+      }
+    }
+  end
+
+  defp array_of_reactions_response do
+    Operation.response("Array of Emoji Reactions", "application/json", %Schema{
+      type: :array,
+      items: emoji_reaction(),
+      example: [emoji_reaction().example]
+    })
+  end
+
+  defp emoji_reaction do
+    %Schema{
+      title: "EmojiReaction",
+      type: :object,
+      properties: %{
+        name: %Schema{type: :string, description: "Emoji"},
+        count: %Schema{type: :integer, description: "Count of reactions with this emoji"},
+        me: %Schema{type: :boolean, description: "Did I react with this emoji?"},
+        accounts: %Schema{
+          type: :array,
+          items: Account,
+          description: "Array of accounts reacted with this emoji"
+        }
+      },
+      example: %{
+        "name" => "😱",
+        "count" => 1,
+        "me" => false,
+        "accounts" => [Account.schema().example]
+      }
+    }
+  end
+
+  def conversation_operation do
+    %Operation{
+      tags: ["Conversations"],
+      summary: "The conversation with the given ID",
+      parameters: [
+        Operation.parameter(:id, :path, :string, "Conversation ID",
+          example: "123",
+          required: true
+        )
+      ],
+      security: [%{"oAuth" => ["read:statuses"]}],
+      operationId: "PleromaController.conversation",
+      responses: %{
+        200 => Operation.response("Conversation", "application/json", Conversation)
+      }
+    }
+  end
+
+  def conversation_statuses_operation do
+    %Operation{
+      tags: ["Conversations"],
+      summary: "Timeline for a given conversation",
+      parameters: [
+        Operation.parameter(:id, :path, :string, "Conversation ID",
+          example: "123",
+          required: true
+        )
+        | pagination_params()
+      ],
+      security: [%{"oAuth" => ["read:statuses"]}],
+      operationId: "PleromaController.conversation_statuses",
+      responses: %{
+        200 =>
+          Operation.response(
+            "Array of Statuses",
+            "application/json",
+            StatusOperation.array_of_statuses()
+          )
+      }
+    }
+  end
+
+  def update_conversation_operation do
+    %Operation{
+      tags: ["Conversations"],
+      summary: "Update a conversation. Used to change the set of recipients.",
+      parameters: [
+        Operation.parameter(:id, :path, :string, "Conversation ID",
+          example: "123",
+          required: true
+        ),
+        Operation.parameter(
+          :recipients,
+          :query,
+          %Schema{type: :array, items: FlakeID},
+          "A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though.",
+          required: true
+        )
+      ],
+      security: [%{"oAuth" => ["write:conversations"]}],
+      operationId: "PleromaController.update_conversation",
+      responses: %{
+        200 => Operation.response("Conversation", "application/json", Conversation)
+      }
+    }
+  end
+
+  def mark_conversations_as_read_operation do
+    %Operation{
+      tags: ["Conversations"],
+      summary: "Marks all user's conversations as read",
+      security: [%{"oAuth" => ["write:conversations"]}],
+      operationId: "PleromaController.mark_conversations_as_read",
+      responses: %{
+        200 =>
+          Operation.response(
+            "Array of Conversations that were marked as read",
+            "application/json",
+            %Schema{
+              type: :array,
+              items: Conversation,
+              example: [Conversation.schema().example]
+            }
+          )
+      }
+    }
+  end
+
+  def mark_notifications_as_read_operation do
+    %Operation{
+      tags: ["Notifications"],
+      summary: "Mark notifications as read. Query parameters are mutually exclusive.",
+      parameters: [
+        Operation.parameter(:id, :query, :string, "A single notification ID to read"),
+        Operation.parameter(:max_id, :query, :string, "Read all notifications up to this id")
+      ],
+      security: [%{"oAuth" => ["write:notifications"]}],
+      operationId: "PleromaController.mark_notifications_as_read",
+      responses: %{
+        200 =>
+          Operation.response(
+            "A Notification or array of Motifications",
+            "application/json",
+            %Schema{
+              anyOf: [
+                %Schema{type: :array, items: NotificationOperation.notification()},
+                NotificationOperation.notification()
+              ]
+            }
+          ),
+        400 => Operation.response("Bad Request", "application/json", ApiError)
+      }
+    }
+  end
+end
index e834133b27100724f1cf75c9202bb0f08f8946c0..8220d13bc5136898fa5133fd4bc3cda2dbece365 100644 (file)
@@ -20,6 +20,8 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
   alias Pleroma.Web.MastodonAPI.NotificationView
   alias Pleroma.Web.MastodonAPI.StatusView
 
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
   plug(
     OAuthScopesPlug,
     %{scopes: ["read:statuses"]}
@@ -49,14 +51,16 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
     %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
   )
 
-  def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} = params) do
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaOperation
+
+  def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do
     with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
          %Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <-
            Object.normalize(activity) do
       reactions =
         emoji_reactions
         |> Enum.map(fn [emoji, user_ap_ids] ->
-          if params["emoji"] && params["emoji"] != emoji do
+          if params[:emoji] && params[:emoji] != emoji do
             nil
           else
             users =
@@ -79,7 +83,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
             }
           end
         end)
-        |> Enum.filter(& &1)
+        |> Enum.reject(&is_nil/1)
 
       conn
       |> json(reactions)
@@ -90,7 +94,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
     end
   end
 
-  def react_with_emoji(%{assigns: %{user: user}} = conn, %{"id" => activity_id, "emoji" => emoji}) do
+  def react_with_emoji(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
     with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji),
          activity <- Activity.get_by_id(activity_id) do
       conn
@@ -99,10 +103,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
     end
   end
 
-  def unreact_with_emoji(%{assigns: %{user: user}} = conn, %{
-        "id" => activity_id,
-        "emoji" => emoji
-      }) do
+  def unreact_with_emoji(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
     with {:ok, _activity} <-
            CommonAPI.unreact_with_emoji(activity_id, user, emoji),
          activity <- Activity.get_by_id(activity_id) do
@@ -112,7 +113,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
     end
   end
 
-  def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
+  def conversation(%{assigns: %{user: user}} = conn, %{id: participation_id}) do
     with %Participation{} = participation <- Participation.get(participation_id),
          true <- user.id == participation.user_id do
       conn
@@ -128,12 +129,13 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
 
   def conversation_statuses(
         %{assigns: %{user: %{id: user_id} = user}} = conn,
-        %{"id" => participation_id} = params
+        %{id: participation_id} = params
       ) do
     with %Participation{user_id: ^user_id} = participation <-
            Participation.get(participation_id, preload: [:conversation]) do
       params =
         params
+        |> Map.new(fn {key, value} -> {to_string(key), value} end)
         |> Map.put("blocking_user", user)
         |> Map.put("muting_user", user)
         |> Map.put("user", user)
@@ -162,7 +164,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
 
   def update_conversation(
         %{assigns: %{user: user}} = conn,
-        %{"id" => participation_id, "recipients" => recipients}
+        %{id: participation_id, recipients: recipients}
       ) do
     with %Participation{} = participation <- Participation.get(participation_id),
          true <- user.id == participation.user_id,
@@ -192,7 +194,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
     end
   end
 
-  def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
+  def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{id: notification_id}) do
     with {:ok, notification} <- Notification.read_one(user, notification_id) do
       conn
       |> put_view(NotificationView)
@@ -205,7 +207,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
     end
   end
 
-  def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do
+  def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{max_id: max_id}) do
     with notifications <- Notification.set_read_up_to(user, max_id) do
       notifications = Enum.take(notifications, 80)
 
index 80c69c7883f42fee5815ed4bae504e1f51a5191e..46388f92c4392ce4999cb094aee78b7feb16dc47 100644 (file)
@@ -51,7 +51,7 @@ defmodule Pleroma.Tests.ApiSpecHelpers do
       |> Map.take([:delete, :get, :head, :options, :patch, :post, :put, :trace])
       |> Map.values()
       |> Enum.reject(&is_nil/1)
-      |> Enum.uniq()
     end)
+    |> Enum.uniq()
   end
 end
index cfd1dbd246b79cfe21771dbb75305516d0615551..f0cdc2f08e7d765985a3d8167d44ca7a165d89a7 100644 (file)
@@ -27,7 +27,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
       |> assign(:user, other_user)
       |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
       |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕")
-      |> json_response(200)
+      |> json_response_and_validate_schema(200)
 
     # We return the status, but this our implementation detail.
     assert %{"id" => id} = result
@@ -53,7 +53,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
       |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
       |> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕")
 
-    assert %{"id" => id} = json_response(result, 200)
+    assert %{"id" => id} = json_response_and_validate_schema(result, 200)
     assert to_string(activity.id) == id
 
     ObanHelpers.perform_all()
@@ -73,7 +73,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     result =
       conn
       |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
-      |> json_response(200)
+      |> json_response_and_validate_schema(200)
 
     assert result == []
 
@@ -85,7 +85,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     result =
       conn
       |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
-      |> json_response(200)
+      |> json_response_and_validate_schema(200)
 
     [%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = result
 
@@ -96,7 +96,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
       |> assign(:user, other_user)
       |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:statuses"]))
       |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
-      |> json_response(200)
+      |> json_response_and_validate_schema(200)
 
     assert [%{"name" => "🎅", "count" => 1, "accounts" => [_represented_user], "me" => true}] =
              result
@@ -111,7 +111,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     result =
       conn
       |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅")
-      |> json_response(200)
+      |> json_response_and_validate_schema(200)
 
     assert result == []
 
@@ -121,7 +121,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     result =
       conn
       |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅")
-      |> json_response(200)
+      |> json_response_and_validate_schema(200)
 
     [%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = result
 
@@ -140,7 +140,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     result =
       conn
       |> get("/api/v1/pleroma/conversations/#{participation.id}")
-      |> json_response(200)
+      |> json_response_and_validate_schema(200)
 
     assert result["id"] == participation.id |> to_string()
   end
@@ -168,7 +168,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     result =
       conn
       |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses")
-      |> json_response(200)
+      |> json_response_and_validate_schema(200)
 
     assert length(result) == 2
 
@@ -186,12 +186,12 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     assert [%{"id" => ^id_two}, %{"id" => ^id_three}] =
              conn
              |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2")
-             |> json_response(:ok)
+             |> json_response_and_validate_schema(:ok)
 
     assert [%{"id" => ^id_three}] =
              conn
              |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}")
-             |> json_response(:ok)
+             |> json_response_and_validate_schema(:ok)
   end
 
   test "PATCH /api/v1/pleroma/conversations/:id" do
@@ -208,12 +208,12 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     assert [user] == participation.recipients
     assert other_user not in participation.recipients
 
+    query = "recipients[]=#{user.id}&recipients[]=#{other_user.id}"
+
     result =
       conn
-      |> patch("/api/v1/pleroma/conversations/#{participation.id}", %{
-        "recipients" => [user.id, other_user.id]
-      })
-      |> json_response(200)
+      |> patch("/api/v1/pleroma/conversations/#{participation.id}?#{query}")
+      |> json_response_and_validate_schema(200)
 
     assert result["id"] == participation.id |> to_string
 
@@ -242,7 +242,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     [%{"unread" => false}, %{"unread" => false}] =
       conn
       |> post("/api/v1/pleroma/conversations/read", %{})
-      |> json_response(200)
+      |> json_response_and_validate_schema(200)
 
     [participation2, participation1] = Participation.for_user(other_user)
     assert Participation.get(participation2.id).read == true
@@ -262,8 +262,8 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
 
       response =
         conn
-        |> post("/api/v1/pleroma/notifications/read", %{"id" => "#{notification1.id}"})
-        |> json_response(:ok)
+        |> post("/api/v1/pleroma/notifications/read?id=#{notification1.id}")
+        |> json_response_and_validate_schema(:ok)
 
       assert %{"pleroma" => %{"is_seen" => true}} = response
       assert Repo.get(Notification, notification1.id).seen
@@ -280,8 +280,8 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
 
       [response1, response2] =
         conn
-        |> post("/api/v1/pleroma/notifications/read", %{"max_id" => "#{notification2.id}"})
-        |> json_response(:ok)
+        |> post("/api/v1/pleroma/notifications/read?max_id=#{notification2.id}")
+        |> json_response_and_validate_schema(:ok)
 
       assert %{"pleroma" => %{"is_seen" => true}} = response1
       assert %{"pleroma" => %{"is_seen" => true}} = response2
@@ -293,8 +293,8 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     test "it returns error when notification not found", %{conn: conn} do
       response =
         conn
-        |> post("/api/v1/pleroma/notifications/read", %{"id" => "22222222222222"})
-        |> json_response(:bad_request)
+        |> post("/api/v1/pleroma/notifications/read?id=22222222222222")
+        |> json_response_and_validate_schema(:bad_request)
 
       assert response == %{"error" => "Cannot get notification"}
     end