forked from AkkomaGang/akkoma
Merge branch 'openapi/polls' into 'develop'
Add OpenAPI spec for PollController See merge request pleroma/pleroma!2476
This commit is contained in:
commit
4c92dfb73e
4 changed files with 162 additions and 22 deletions
76
lib/pleroma/web/api_spec/operations/poll_operation.ex
Normal file
76
lib/pleroma/web/api_spec/operations/poll_operation.ex
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# 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.PollOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.Poll
|
||||||
|
|
||||||
|
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: ["Polls"],
|
||||||
|
summary: "View a poll",
|
||||||
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
|
parameters: [id_param()],
|
||||||
|
operationId: "PollController.show",
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Poll", "application/json", Poll),
|
||||||
|
404 => Operation.response("Error", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def vote_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Polls"],
|
||||||
|
summary: "Vote on a poll",
|
||||||
|
parameters: [id_param()],
|
||||||
|
operationId: "PollController.vote",
|
||||||
|
requestBody: vote_request(),
|
||||||
|
security: [%{"oAuth" => ["write:statuses"]}],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Poll", "application/json", Poll),
|
||||||
|
422 => Operation.response("Error", "application/json", ApiError),
|
||||||
|
404 => Operation.response("Error", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp id_param do
|
||||||
|
Operation.parameter(:id, :path, FlakeID, "Poll ID",
|
||||||
|
example: "123",
|
||||||
|
required: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp vote_request do
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
choices: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :integer},
|
||||||
|
description: "Array of own votes containing index for each option (starting from 0)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: [:choices]
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
example: %{
|
||||||
|
"choices" => [0, 1, 2]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,26 +11,72 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
|
||||||
|
|
||||||
OpenApiSpex.schema(%{
|
OpenApiSpex.schema(%{
|
||||||
title: "Poll",
|
title: "Poll",
|
||||||
description: "Response schema for account custom fields",
|
description: "Represents a poll attached to a status",
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
id: FlakeID,
|
id: FlakeID,
|
||||||
expires_at: %Schema{type: :string, format: "date-time"},
|
expires_at: %Schema{
|
||||||
expired: %Schema{type: :boolean},
|
type: :string,
|
||||||
multiple: %Schema{type: :boolean},
|
format: :"date-time",
|
||||||
votes_count: %Schema{type: :integer},
|
nullable: true,
|
||||||
voted: %Schema{type: :boolean},
|
description: "When the poll ends"
|
||||||
emojis: %Schema{type: :array, items: Emoji},
|
},
|
||||||
|
expired: %Schema{type: :boolean, description: "Is the poll currently expired?"},
|
||||||
|
multiple: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "Does the poll allow multiple-choice answers?"
|
||||||
|
},
|
||||||
|
votes_count: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
nullable: true,
|
||||||
|
description: "How many votes have been received. Number, or null if `multiple` is false."
|
||||||
|
},
|
||||||
|
voted: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
nullable: true,
|
||||||
|
description:
|
||||||
|
"When called with a user token, has the authorized user voted? Boolean, or null if no current user."
|
||||||
|
},
|
||||||
|
emojis: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: Emoji,
|
||||||
|
description: "Custom emoji to be used for rendering poll options."
|
||||||
|
},
|
||||||
options: %Schema{
|
options: %Schema{
|
||||||
type: :array,
|
type: :array,
|
||||||
items: %Schema{
|
items: %Schema{
|
||||||
|
title: "PollOption",
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
title: %Schema{type: :string},
|
title: %Schema{type: :string},
|
||||||
votes_count: %Schema{type: :integer}
|
votes_count: %Schema{type: :integer}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
description: "Possible answers for the poll."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
id: "34830",
|
||||||
|
expires_at: "2019-12-05T04:05:08.302Z",
|
||||||
|
expired: true,
|
||||||
|
multiple: false,
|
||||||
|
votes_count: 10,
|
||||||
|
voters_count: nil,
|
||||||
|
voted: true,
|
||||||
|
own_votes: [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
options: [
|
||||||
|
%{
|
||||||
|
title: "accept",
|
||||||
|
votes_count: 6
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
title: "deny",
|
||||||
|
votes_count: 4
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
emojis: []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,8 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} when action == :show
|
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} when action == :show
|
||||||
|
@ -22,8 +24,10 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
|
||||||
|
|
||||||
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote)
|
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote)
|
||||||
|
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PollOperation
|
||||||
|
|
||||||
@doc "GET /api/v1/polls/:id"
|
@doc "GET /api/v1/polls/:id"
|
||||||
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||||
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
|
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
|
||||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
|
@ -35,7 +39,7 @@ def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "POST /api/v1/polls/:id/votes"
|
@doc "POST /api/v1/polls/:id/votes"
|
||||||
def vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
|
def vote(%{assigns: %{user: user}, body_params: %{choices: choices}} = conn, %{id: id}) do
|
||||||
with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id),
|
with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id),
|
||||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
|
|
|
@ -24,7 +24,7 @@ test "returns poll entity for object id", %{user: user, conn: conn} do
|
||||||
|
|
||||||
conn = get(conn, "/api/v1/polls/#{object.id}")
|
conn = get(conn, "/api/v1/polls/#{object.id}")
|
||||||
|
|
||||||
response = json_response(conn, 200)
|
response = json_response_and_validate_schema(conn, 200)
|
||||||
id = to_string(object.id)
|
id = to_string(object.id)
|
||||||
assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
|
assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
|
||||||
end
|
end
|
||||||
|
@ -43,7 +43,7 @@ test "does not expose polls for private statuses", %{conn: conn} do
|
||||||
|
|
||||||
conn = get(conn, "/api/v1/polls/#{object.id}")
|
conn = get(conn, "/api/v1/polls/#{object.id}")
|
||||||
|
|
||||||
assert json_response(conn, 404)
|
assert json_response_and_validate_schema(conn, 404)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -65,9 +65,12 @@ test "votes are added to the poll", %{conn: conn} do
|
||||||
|
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
|
||||||
|
|
||||||
assert json_response(conn, 200)
|
assert json_response_and_validate_schema(conn, 200)
|
||||||
object = Object.get_by_id(object.id)
|
object = Object.get_by_id(object.id)
|
||||||
|
|
||||||
assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
|
assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
|
||||||
|
@ -85,8 +88,9 @@ test "author can't vote", %{user: user, conn: conn} do
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
assert conn
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
|
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
|
||||||
|> json_response(422) == %{"error" => "Poll's author can't vote"}
|
|> json_response_and_validate_schema(422) == %{"error" => "Poll's author can't vote"}
|
||||||
|
|
||||||
object = Object.get_by_id(object.id)
|
object = Object.get_by_id(object.id)
|
||||||
|
|
||||||
|
@ -105,8 +109,9 @@ test "does not allow multiple choices on a single-choice question", %{conn: conn
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
assert conn
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
|
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
|
||||||
|> json_response(422) == %{"error" => "Too many choices"}
|
|> json_response_and_validate_schema(422) == %{"error" => "Too many choices"}
|
||||||
|
|
||||||
object = Object.get_by_id(object.id)
|
object = Object.get_by_id(object.id)
|
||||||
|
|
||||||
|
@ -126,15 +131,21 @@ test "does not allow choice index to be greater than options count", %{conn: con
|
||||||
|
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [2]})
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]})
|
||||||
|
|
||||||
assert json_response(conn, 422) == %{"error" => "Invalid indices"}
|
assert json_response_and_validate_schema(conn, 422) == %{"error" => "Invalid indices"}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns 404 error when object is not exist", %{conn: conn} do
|
test "returns 404 error when object is not exist", %{conn: conn} do
|
||||||
conn = post(conn, "/api/v1/polls/1/votes", %{"choices" => [0]})
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/polls/1/votes", %{"choices" => [0]})
|
||||||
|
|
||||||
assert json_response(conn, 404) == %{"error" => "Record not found"}
|
assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns 404 when poll is private and not available for user", %{conn: conn} do
|
test "returns 404 when poll is private and not available for user", %{conn: conn} do
|
||||||
|
@ -149,9 +160,12 @@ test "returns 404 when poll is private and not available for user", %{conn: conn
|
||||||
|
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0]})
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]})
|
||||||
|
|
||||||
assert json_response(conn, 404) == %{"error" => "Record not found"}
|
assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue