Add OpenAPI spec for PleromaAPI.PleromaAPIController

This commit is contained in:
Egor Kislitsyn 2020-05-19 21:52:26 +04:00
parent 94ba5a7802
commit 490a3a34b6
No known key found for this signature in database
GPG key ID: 1B49CB15B71E7805
6 changed files with 264 additions and 39 deletions

View 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. * `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) * 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. ### Marks all user's conversations as read.
* Method `POST` * Method `POST`
* Authentication: required * 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 /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` * Method: `GET`
* Authentication: optional * Authentication: optional
* Params: None * Params: None

View file

@ -145,7 +145,7 @@ def destroy_multiple_operation do
} }
end end
defp notification do def notification do
%Schema{ %Schema{
title: "Notification", title: "Notification",
description: "Response schema for a notification", description: "Response schema for a notification",

View file

@ -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

View file

@ -20,6 +20,8 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read:statuses"]} %{scopes: ["read:statuses"]}
@ -49,14 +51,16 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
%{scopes: ["write:notifications"]} when action == :mark_notifications_as_read %{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), with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
%Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <- %Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <-
Object.normalize(activity) do Object.normalize(activity) do
reactions = reactions =
emoji_reactions emoji_reactions
|> Enum.map(fn [emoji, user_ap_ids] -> |> Enum.map(fn [emoji, user_ap_ids] ->
if params["emoji"] && params["emoji"] != emoji do if params[:emoji] && params[:emoji] != emoji do
nil nil
else else
users = users =
@ -79,7 +83,7 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}
} }
end end
end) end)
|> Enum.filter(& &1) |> Enum.reject(&is_nil/1)
conn conn
|> json(reactions) |> json(reactions)
@ -90,7 +94,7 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}
end end
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), with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji),
activity <- Activity.get_by_id(activity_id) do activity <- Activity.get_by_id(activity_id) do
conn conn
@ -99,10 +103,7 @@ def react_with_emoji(%{assigns: %{user: user}} = conn, %{"id" => activity_id, "e
end end
end end
def unreact_with_emoji(%{assigns: %{user: user}} = conn, %{ def unreact_with_emoji(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
"id" => activity_id,
"emoji" => emoji
}) do
with {:ok, _activity} <- with {:ok, _activity} <-
CommonAPI.unreact_with_emoji(activity_id, user, emoji), CommonAPI.unreact_with_emoji(activity_id, user, emoji),
activity <- Activity.get_by_id(activity_id) do activity <- Activity.get_by_id(activity_id) do
@ -112,7 +113,7 @@ def unreact_with_emoji(%{assigns: %{user: user}} = conn, %{
end end
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), with %Participation{} = participation <- Participation.get(participation_id),
true <- user.id == participation.user_id do true <- user.id == participation.user_id do
conn conn
@ -128,12 +129,13 @@ def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id})
def conversation_statuses( def conversation_statuses(
%{assigns: %{user: %{id: user_id} = user}} = conn, %{assigns: %{user: %{id: user_id} = user}} = conn,
%{"id" => participation_id} = params %{id: participation_id} = params
) do ) do
with %Participation{user_id: ^user_id} = participation <- with %Participation{user_id: ^user_id} = participation <-
Participation.get(participation_id, preload: [:conversation]) do Participation.get(participation_id, preload: [:conversation]) do
params = params =
params params
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("muting_user", user) |> Map.put("muting_user", user)
|> Map.put("user", user) |> Map.put("user", user)
@ -162,7 +164,7 @@ def conversation_statuses(
def update_conversation( def update_conversation(
%{assigns: %{user: user}} = conn, %{assigns: %{user: user}} = conn,
%{"id" => participation_id, "recipients" => recipients} %{id: participation_id, recipients: recipients}
) do ) do
with %Participation{} = participation <- Participation.get(participation_id), with %Participation{} = participation <- Participation.get(participation_id),
true <- user.id == participation.user_id, true <- user.id == participation.user_id,
@ -192,7 +194,7 @@ def mark_conversations_as_read(%{assigns: %{user: user}} = conn, _params) do
end end
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 with {:ok, notification} <- Notification.read_one(user, notification_id) do
conn conn
|> put_view(NotificationView) |> put_view(NotificationView)
@ -205,7 +207,7 @@ def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"id" => notif
end end
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 with notifications <- Notification.set_read_up_to(user, max_id) do
notifications = Enum.take(notifications, 80) notifications = Enum.take(notifications, 80)

View file

@ -51,7 +51,7 @@ def api_operations do
|> Map.take([:delete, :get, :head, :options, :patch, :post, :put, :trace]) |> Map.take([:delete, :get, :head, :options, :patch, :post, :put, :trace])
|> Map.values() |> Map.values()
|> Enum.reject(&is_nil/1) |> Enum.reject(&is_nil/1)
|> Enum.uniq()
end) end)
|> Enum.uniq()
end end
end end

View file

@ -27,7 +27,7 @@ test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|> assign(:user, other_user) |> assign(:user, other_user)
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕") |> 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. # We return the status, but this our implementation detail.
assert %{"id" => id} = result assert %{"id" => id} = result
@ -53,7 +53,7 @@ test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕") |> 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 assert to_string(activity.id) == id
ObanHelpers.perform_all() ObanHelpers.perform_all()
@ -73,7 +73,7 @@ test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do
result = result =
conn conn
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions") |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions")
|> json_response(200) |> json_response_and_validate_schema(200)
assert result == [] assert result == []
@ -85,7 +85,7 @@ test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do
result = result =
conn conn
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions") |> 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 [%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = result
@ -96,7 +96,7 @@ test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do
|> assign(:user, other_user) |> assign(:user, other_user)
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:statuses"])) |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:statuses"]))
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions") |> 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}] = assert [%{"name" => "🎅", "count" => 1, "accounts" => [_represented_user], "me" => true}] =
result result
@ -111,7 +111,7 @@ test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
result = result =
conn conn
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅") |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅")
|> json_response(200) |> json_response_and_validate_schema(200)
assert result == [] assert result == []
@ -121,7 +121,7 @@ test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
result = result =
conn conn
|> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅") |> 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 [%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = result
@ -140,7 +140,7 @@ test "/api/v1/pleroma/conversations/:id" do
result = result =
conn conn
|> get("/api/v1/pleroma/conversations/#{participation.id}") |> get("/api/v1/pleroma/conversations/#{participation.id}")
|> json_response(200) |> json_response_and_validate_schema(200)
assert result["id"] == participation.id |> to_string() assert result["id"] == participation.id |> to_string()
end end
@ -168,7 +168,7 @@ test "/api/v1/pleroma/conversations/:id/statuses" do
result = result =
conn conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses") |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses")
|> json_response(200) |> json_response_and_validate_schema(200)
assert length(result) == 2 assert length(result) == 2
@ -186,12 +186,12 @@ test "/api/v1/pleroma/conversations/:id/statuses" do
assert [%{"id" => ^id_two}, %{"id" => ^id_three}] = assert [%{"id" => ^id_two}, %{"id" => ^id_three}] =
conn conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2") |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2")
|> json_response(:ok) |> json_response_and_validate_schema(:ok)
assert [%{"id" => ^id_three}] = assert [%{"id" => ^id_three}] =
conn conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}") |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}")
|> json_response(:ok) |> json_response_and_validate_schema(:ok)
end end
test "PATCH /api/v1/pleroma/conversations/:id" do test "PATCH /api/v1/pleroma/conversations/:id" do
@ -208,12 +208,12 @@ test "PATCH /api/v1/pleroma/conversations/:id" do
assert [user] == participation.recipients assert [user] == participation.recipients
assert other_user not in participation.recipients assert other_user not in participation.recipients
query = "recipients[]=#{user.id}&recipients[]=#{other_user.id}"
result = result =
conn conn
|> patch("/api/v1/pleroma/conversations/#{participation.id}", %{ |> patch("/api/v1/pleroma/conversations/#{participation.id}?#{query}")
"recipients" => [user.id, other_user.id] |> json_response_and_validate_schema(200)
})
|> json_response(200)
assert result["id"] == participation.id |> to_string assert result["id"] == participation.id |> to_string
@ -242,7 +242,7 @@ test "POST /api/v1/pleroma/conversations/read" do
[%{"unread" => false}, %{"unread" => false}] = [%{"unread" => false}, %{"unread" => false}] =
conn conn
|> post("/api/v1/pleroma/conversations/read", %{}) |> post("/api/v1/pleroma/conversations/read", %{})
|> json_response(200) |> json_response_and_validate_schema(200)
[participation2, participation1] = Participation.for_user(other_user) [participation2, participation1] = Participation.for_user(other_user)
assert Participation.get(participation2.id).read == true assert Participation.get(participation2.id).read == true
@ -262,8 +262,8 @@ test "it marks a single notification as read", %{user: user1, conn: conn} do
response = response =
conn conn
|> post("/api/v1/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) |> post("/api/v1/pleroma/notifications/read?id=#{notification1.id}")
|> json_response(:ok) |> json_response_and_validate_schema(:ok)
assert %{"pleroma" => %{"is_seen" => true}} = response assert %{"pleroma" => %{"is_seen" => true}} = response
assert Repo.get(Notification, notification1.id).seen assert Repo.get(Notification, notification1.id).seen
@ -280,8 +280,8 @@ test "it marks multiple notifications as read", %{user: user1, conn: conn} do
[response1, response2] = [response1, response2] =
conn conn
|> post("/api/v1/pleroma/notifications/read", %{"max_id" => "#{notification2.id}"}) |> post("/api/v1/pleroma/notifications/read?max_id=#{notification2.id}")
|> json_response(:ok) |> json_response_and_validate_schema(:ok)
assert %{"pleroma" => %{"is_seen" => true}} = response1 assert %{"pleroma" => %{"is_seen" => true}} = response1
assert %{"pleroma" => %{"is_seen" => true}} = response2 assert %{"pleroma" => %{"is_seen" => true}} = response2
@ -293,8 +293,8 @@ test "it marks multiple notifications as read", %{user: user1, conn: conn} do
test "it returns error when notification not found", %{conn: conn} do test "it returns error when notification not found", %{conn: conn} do
response = response =
conn conn
|> post("/api/v1/pleroma/notifications/read", %{"id" => "22222222222222"}) |> post("/api/v1/pleroma/notifications/read?id=22222222222222")
|> json_response(:bad_request) |> json_response_and_validate_schema(:bad_request)
assert response == %{"error" => "Cannot get notification"} assert response == %{"error" => "Cannot get notification"}
end end