Commit f166316f authored by lain's avatar lain

Merge branch 'openapi/pleroma-api/pleroma' into 'develop'

Add OpenAPI spec for PleromaAPI.PleromaAPIController

See merge request !2564
parents 283fb1e0 5ba6e1c3
Pipeline #26199 passed with stages
in 82 minutes and 45 seconds
......@@ -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
......
# 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.EmojiReactionOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.Schemas.Status
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_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: "EmojiReactionController.index",
responses: %{
200 => array_of_reactions_response()
}
}
end
def create_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: "EmojiReactionController.create",
responses: %{
200 => Operation.response("Status", "application/json", Status)
}
}
end
def delete_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: "EmojiReactionController.delete",
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
end
......@@ -145,7 +145,7 @@ def destroy_multiple_operation do
}
end
defp notification do
def notification do
%Schema{
title: "Notification",
description: "Response schema for a notification",
......
# 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.PleromaConversationOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Conversation
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.StatusOperation
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def show_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: "PleromaAPI.ConversationController.show",
responses: %{
200 => Operation.response("Conversation", "application/json", Conversation)
}
}
end
def 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: "PleromaAPI.ConversationController.statuses",
responses: %{
200 =>
Operation.response(
"Array of Statuses",
"application/json",
StatusOperation.array_of_statuses()
)
}
}
end
def update_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: "PleromaAPI.ConversationController.update",
responses: %{
200 => Operation.response("Conversation", "application/json", Conversation)
}
}
end
def mark_as_read_operation do
%Operation{
tags: ["Conversations"],
summary: "Marks all user's conversations as read",
security: [%{"oAuth" => ["write:conversations"]}],
operationId: "PleromaAPI.ConversationController.mark_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
end
# 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.PleromaNotificationOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.NotificationOperation
alias Pleroma.Web.ApiSpec.Schemas.ApiError
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def mark_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: "PleromaAPI.NotificationController.mark_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
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.ConversationController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Conversation.Participation
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.StatusView
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:put_view, Pleroma.Web.MastodonAPI.ConversationView)
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:show, :statuses])
plug(
OAuthScopesPlug,
%{scopes: ["write:conversations"]} when action in [:update, :mark_as_read]
)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaConversationOperation
def show(%{assigns: %{user: %{id: user_id} = user}} = conn, %{id: participation_id}) do
with %Participation{user_id: ^user_id} = participation <- Participation.get(participation_id) do
render(conn, "participation.json", participation: participation, for: user)
else
_error ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Unknown conversation id"})
end
end
def statuses(
%{assigns: %{user: %{id: user_id} = user}} = conn,
%{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)
activities =
participation.conversation.ap_id
|> ActivityPub.fetch_activities_for_context_query(params)
|> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
|> Enum.reverse()
conn
|> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", activities: activities, for: user, as: :activity)
else
_error ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Unknown conversation id"})
end
end
def update(
%{assigns: %{user: %{id: user_id} = user}} = conn,
%{id: participation_id, recipients: recipients}
) do
with %Participation{user_id: ^user_id} = participation <- Participation.get(participation_id),
{:ok, participation} <- Participation.set_recipients(participation, recipients) do
render(conn, "participation.json", participation: participation, for: user)
else
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(%{"error" => message})
_error ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Unknown conversation id"})
end
end
def mark_as_read(%{assigns: %{user: user}} = conn, _params) do
with {:ok, _, participations} <- Participation.mark_all_as_read(user) do
conn
|> add_link_headers(participations)
|> render("participations.json", participations: participations, for: user)
end
end
end
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
use Pleroma.Web, :controller
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.StatusView
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action in [:create, :delete])
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated}
when action == :index
)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.EmojiReactionOperation
def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
%Object{data: %{"reactions" => reactions}} when is_list(reactions) <-
Object.normalize(activity) do
reactions = filter(reactions, params)
render(conn, "index.json", emoji_reactions: reactions, user: user)
else
_e -> json(conn, [])
end
end
defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
Enum.filter(reactions, fn [e, _] -> e == emoji end)
end
defp filter(reactions, _), do: reactions
def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
activity = Activity.get_by_id(activity_id)
conn
|> put_view(StatusView)
|> render("show.json", activity: activity, for: user, as: :activity)
end
end
def delete(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do
activity = Activity.get_by_id(activity_id)
conn
|> put_view(StatusView)
|> render("show.json", activity: activity, for: user, as: :activity)
end
end
end
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.NotificationController do
use Pleroma.Web, :controller
alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :mark_as_read)
plug(:put_view, Pleroma.Web.MastodonAPI.NotificationView)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation
def mark_as_read(%{assigns: %{user: user}} = conn, %{id: notification_id}) do
with {:ok, notification} <- Notification.read_one(user, notification_id) do
render(conn, "show.json", notification: notification, for: user)
else
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(%{"error" => message})
end
end
def mark_as_read(%{assigns: %{user: user}} = conn, %{max_id: max_id}) do
notifications =
user
|> Notification.set_read_up_to(max_id)
|> Enum.take(80)
render(conn, "index.json", notifications: notifications, for: user)
end
end
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.ConversationView
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"]}
when action in [:conversation, :conversation_statuses]
)
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated}
when action == :emoji_reactions_by
)
plug(
OAuthScopesPlug,
%{scopes: ["write:statuses"]}
when action in [:react_with_emoji, :unreact_with_emoji]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:conversations"]}
when action in [:update_conversation, :mark_conversations_as_read]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
)
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
nil
else
users =
Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1)
|> Enum.filter(fn
%{deactivated: false} -> true
_ -> false
end)
%{
name: emoji,
count: length(users),
accounts:
AccountView.render("index.json", %{
users: users,
for: user,
as: :user
}),
me: !!(user && user.ap_id in user_ap_ids)
}
end
end)
|> Enum.filter(& &1)
conn
|> json(reactions)
else
_e ->
conn
|> json([])
end
end
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
|> put_view(StatusView)
|> render("show.json", %{activity: activity, for: user, as: :activity})
end
end
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
conn
|> put_view(StatusView)
|> render("show.json", %{activity: activity, for: user, as: :activity})
end
end
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
|> put_view(ConversationView)
|> render("participation.json", %{participation: participation, for: user})
else
_error ->
conn
|> put_status(404)