1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ApiSpec.AccountOperation do
6 alias OpenApiSpex.Operation
7 alias OpenApiSpex.Reference
8 alias OpenApiSpex.Schema
9 alias Pleroma.Web.ApiSpec.Schemas.Account
10 alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
11 alias Pleroma.Web.ApiSpec.Schemas.ActorType
12 alias Pleroma.Web.ApiSpec.Schemas.ApiError
13 alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
14 alias Pleroma.Web.ApiSpec.Schemas.List
15 alias Pleroma.Web.ApiSpec.Schemas.Status
16 alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
18 import Pleroma.Web.ApiSpec.Helpers
20 @spec open_api_operation(atom) :: Operation.t()
21 def open_api_operation(action) do
22 operation = String.to_existing_atom("#{action}_operation")
23 apply(__MODULE__, operation, [])
26 @spec create_operation() :: Operation.t()
27 def create_operation do
30 summary: "Register an account",
32 "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
33 operationId: "AccountController.create",
34 requestBody: request_body("Parameters", create_request(), required: true),
36 200 => Operation.response("Account", "application/json", create_response()),
37 400 => Operation.response("Error", "application/json", ApiError),
38 403 => Operation.response("Error", "application/json", ApiError),
39 429 => Operation.response("Error", "application/json", ApiError)
44 def verify_credentials_operation do
47 description: "Test to make sure that the user token works.",
48 summary: "Verify account credentials",
49 operationId: "AccountController.verify_credentials",
50 security: [%{"oAuth" => ["read:accounts"]}],
52 200 => Operation.response("Account", "application/json", Account)
57 def update_credentials_operation do
60 summary: "Update account credentials",
61 description: "Update the user's display and preferences.",
62 operationId: "AccountController.update_credentials",
63 security: [%{"oAuth" => ["write:accounts"]}],
64 requestBody: request_body("Parameters", update_credentials_request(), required: true),
66 200 => Operation.response("Account", "application/json", Account),
67 403 => Operation.response("Error", "application/json", ApiError)
72 def relationships_operation do
75 summary: "Check relationships to other accounts",
76 operationId: "AccountController.relationships",
77 description: "Find out whether a given account is followed, blocked, muted, etc.",
78 security: [%{"oAuth" => ["read:follows"]}],
84 oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
91 200 => Operation.response("Account", "application/json", array_of_relationships())
100 operationId: "AccountController.show",
101 description: "View information about a profile.",
102 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
104 200 => Operation.response("Account", "application/json", Account),
105 401 => Operation.response("Error", "application/json", ApiError),
106 404 => Operation.response("Error", "application/json", ApiError)
111 def statuses_operation do
115 operationId: "AccountController.statuses",
117 "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
120 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
121 Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
122 Operation.parameter(:tagged, :query, :string, "With tag"),
127 "Include only statuses with media attached"
133 "Include statuses from muted acccounts."
135 Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
136 Operation.parameter(:exclude_replies, :query, BooleanLike, "Exclude replies"),
138 :exclude_visibilities,
140 %Schema{type: :array, items: VisibilityScope},
141 "Exclude visibilities"
143 ] ++ pagination_params(),
145 200 => Operation.response("Statuses", "application/json", array_of_statuses()),
146 401 => Operation.response("Error", "application/json", ApiError),
147 404 => Operation.response("Error", "application/json", ApiError)
152 def followers_operation do
155 summary: "Followers",
156 operationId: "AccountController.followers",
157 security: [%{"oAuth" => ["read:accounts"]}],
159 "Accounts which follow the given account, if network is not hidden by the account owner.",
161 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
162 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
163 with_relationships_param() | pagination_params()
166 200 => Operation.response("Accounts", "application/json", array_of_accounts())
171 def following_operation do
174 summary: "Following",
175 operationId: "AccountController.following",
176 security: [%{"oAuth" => ["read:accounts"]}],
178 "Accounts which the given account is following, if network is not hidden by the account owner.",
180 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
181 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
182 with_relationships_param() | pagination_params()
184 responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
188 def lists_operation do
191 summary: "Lists containing this account",
192 operationId: "AccountController.lists",
193 security: [%{"oAuth" => ["read:lists"]}],
194 description: "User lists that you have added this account to.",
195 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
196 responses: %{200 => Operation.response("Lists", "application/json", array_of_lists())}
200 def follow_operation do
204 operationId: "AccountController.follow",
205 security: [%{"oAuth" => ["follow", "write:follows"]}],
206 description: "Follow the given account",
208 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
218 description: "Receive this account's reblogs in home timeline? Defaults to true.",
226 200 => Operation.response("Relationship", "application/json", AccountRelationship),
227 400 => Operation.response("Error", "application/json", ApiError),
228 404 => Operation.response("Error", "application/json", ApiError)
233 def unfollow_operation do
237 operationId: "AccountController.unfollow",
238 security: [%{"oAuth" => ["follow", "write:follows"]}],
239 description: "Unfollow the given account",
240 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
242 200 => Operation.response("Relationship", "application/json", AccountRelationship),
243 400 => Operation.response("Error", "application/json", ApiError),
244 404 => Operation.response("Error", "application/json", ApiError)
249 def mute_operation do
253 operationId: "AccountController.mute",
254 security: [%{"oAuth" => ["follow", "write:mutes"]}],
255 requestBody: request_body("Parameters", mute_request()),
257 "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).",
259 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
263 %Schema{allOf: [BooleanLike], default: true},
264 "Mute notifications in addition to statuses? Defaults to `true`."
268 200 => Operation.response("Relationship", "application/json", AccountRelationship)
273 def unmute_operation do
277 operationId: "AccountController.unmute",
278 security: [%{"oAuth" => ["follow", "write:mutes"]}],
279 description: "Unmute the given account.",
280 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
282 200 => Operation.response("Relationship", "application/json", AccountRelationship)
287 def block_operation do
291 operationId: "AccountController.block",
292 security: [%{"oAuth" => ["follow", "write:blocks"]}],
294 "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
295 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
297 200 => Operation.response("Relationship", "application/json", AccountRelationship)
302 def unblock_operation do
306 operationId: "AccountController.unblock",
307 security: [%{"oAuth" => ["follow", "write:blocks"]}],
308 description: "Unblock the given account.",
309 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
311 200 => Operation.response("Relationship", "application/json", AccountRelationship)
316 def follow_by_uri_operation do
319 summary: "Follow by URI",
320 operationId: "AccountController.follows",
321 security: [%{"oAuth" => ["follow", "write:follows"]}],
322 requestBody: request_body("Parameters", follow_by_uri_request(), required: true),
324 200 => Operation.response("Account", "application/json", AccountRelationship),
325 400 => Operation.response("Error", "application/json", ApiError),
326 404 => Operation.response("Error", "application/json", ApiError)
331 def mutes_operation do
334 summary: "Muted accounts",
335 operationId: "AccountController.mutes",
336 description: "Accounts the user has muted.",
337 security: [%{"oAuth" => ["follow", "read:mutes"]}],
339 200 => Operation.response("Accounts", "application/json", array_of_accounts())
344 def blocks_operation do
347 summary: "Blocked users",
348 operationId: "AccountController.blocks",
349 description: "View your blocks. See also accounts/:id/{block,unblock}",
350 security: [%{"oAuth" => ["read:blocks"]}],
352 200 => Operation.response("Accounts", "application/json", array_of_accounts())
357 def endorsements_operation do
360 summary: "Endorsements",
361 operationId: "AccountController.endorsements",
362 description: "Not implemented",
363 security: [%{"oAuth" => ["read:accounts"]}],
365 200 => empty_array_response()
370 def identity_proofs_operation do
373 summary: "Identity proofs",
374 operationId: "AccountController.identity_proofs",
375 # Validators complains about unused path params otherwise
377 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
379 description: "Not implemented",
381 200 => empty_array_response()
386 defp create_request do
388 title: "AccountCreateRequest",
389 description: "POST body for creating an account",
391 required: [:username, :password, :agreement],
397 "Text that will be reviewed by moderators if registrations require manual approval"
399 username: %Schema{type: :string, description: "The desired username for the account"},
404 "The email address to be used for login. Required when `account_activation_required` is enabled.",
409 description: "The password to be used for login",
413 allOf: [BooleanLike],
415 "Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE."
420 description: "The language of the confirmation email that will be sent"
422 # Pleroma-specific properties:
423 fullname: %Schema{type: :string, nullable: true, description: "Full name"},
424 bio: %Schema{type: :string, description: "Bio", nullable: true, default: ""},
425 captcha_solution: %Schema{
428 description: "Provider-specific captcha solution"
430 captcha_token: %Schema{
433 description: "Provider-specific captcha token"
435 captcha_answer_data: %Schema{
438 description: "Provider-specific captcha data"
443 description: "Invite token required when the registrations aren't public"
447 "username" => "cofe",
448 "email" => "cofe@example.com",
449 "password" => "secret",
450 "agreement" => "true",
456 # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
457 defp create_response do
459 title: "AccountCreateResponse",
460 description: "Response schema for an account",
463 # The response when auto-login on create succeeds (token is issued):
464 token_type: %Schema{type: :string},
465 access_token: %Schema{type: :string},
466 refresh_token: %Schema{type: :string},
467 scope: %Schema{type: :string},
468 created_at: %Schema{type: :integer, format: :"date-time"},
469 me: %Schema{type: :string},
470 expires_in: %Schema{type: :integer},
472 # The response when registration succeeds but auto-login fails (no token):
473 identifier: %Schema{type: :string},
474 message: %Schema{type: :string}
476 # Note: example of successful registration with failed login response:
478 # "identifier" => "missing_confirmed_email",
479 # "message" => "You have been registered. Please check your email for further instructions."
482 "token_type" => "Bearer",
483 "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
484 "refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
485 "created_at" => 1_585_918_714,
487 "scope" => "read write follow push",
488 "me" => "https://gensokyo.2hu/users/raymoo"
493 defp update_credentials_request do
495 title: "AccountUpdateCredentialsRequest",
496 description: "POST body for creating an account",
500 allOf: [BooleanLike],
502 description: "Whether the account has a bot flag."
504 display_name: %Schema{
507 description: "The display name to use for the profile."
509 note: %Schema{type: :string, description: "The account bio."},
513 description: "Avatar image encoded using multipart/form-data",
519 description: "Header image encoded using multipart/form-data",
523 allOf: [BooleanLike],
525 description: "Whether manual approval of follow requests is required."
527 accepts_chat_messages: %Schema{
528 allOf: [BooleanLike],
530 description: "Whether the user accepts receiving chat messages."
532 fields_attributes: %Schema{
535 %Schema{type: :array, items: attribute_field()},
536 %Schema{type: :object, additionalProperties: attribute_field()}
539 # NOTE: `source` field is not supported
544 # privacy: %Schema{type: :string},
545 # sensitive: %Schema{type: :boolean},
546 # language: %Schema{type: :string}
550 # Pleroma-specific fields
551 no_rich_text: %Schema{
552 allOf: [BooleanLike],
554 description: "html tags are stripped from all statuses requested from the API"
556 hide_followers: %Schema{
557 allOf: [BooleanLike],
559 description: "user's followers will be hidden"
561 hide_follows: %Schema{
562 allOf: [BooleanLike],
564 description: "user's follows will be hidden"
566 hide_followers_count: %Schema{
567 allOf: [BooleanLike],
569 description: "user's follower count will be hidden"
571 hide_follows_count: %Schema{
572 allOf: [BooleanLike],
574 description: "user's follow count will be hidden"
576 hide_favorites: %Schema{
577 allOf: [BooleanLike],
579 description: "user's favorites timeline will be hidden"
582 allOf: [BooleanLike],
584 description: "user's role (e.g admin, moderator) will be exposed to anyone in the
587 default_scope: VisibilityScope,
588 pleroma_settings_store: %Schema{
591 description: "Opaque user settings to be saved on the backend."
593 skip_thread_containment: %Schema{
594 allOf: [BooleanLike],
596 description: "Skip filtering out broken threads"
598 allow_following_move: %Schema{
599 allOf: [BooleanLike],
601 description: "Allows automatically follow moved following accounts"
603 pleroma_background_image: %Schema{
606 description: "Sets the background image of the user.",
609 discoverable: %Schema{
610 allOf: [BooleanLike],
613 "Discovery of this account in search results and other services is allowed."
615 actor_type: ActorType
619 display_name: "cofe",
621 fields_attributes: [%{name: "foo", value: "bar"}],
623 hide_followers: true,
625 hide_followers_count: false,
626 hide_follows_count: false,
627 hide_favorites: false,
629 default_scope: "private",
630 pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
631 skip_thread_containment: false,
632 allow_following_move: false,
639 def array_of_accounts do
641 title: "ArrayOfAccounts",
644 example: [Account.schema().example]
648 defp array_of_relationships do
650 title: "ArrayOfRelationships",
651 description: "Response schema for account relationships",
653 items: AccountRelationship,
658 "showing_reblogs" => true,
659 "followed_by" => true,
661 "blocked_by" => true,
663 "muting_notifications" => false,
664 "requested" => false,
665 "domain_blocking" => false,
666 "subscribing" => false,
672 "showing_reblogs" => true,
673 "followed_by" => true,
675 "blocked_by" => true,
677 "muting_notifications" => false,
679 "domain_blocking" => false,
680 "subscribing" => false,
686 "showing_reblogs" => true,
687 "followed_by" => true,
689 "blocked_by" => false,
691 "muting_notifications" => false,
692 "requested" => false,
693 "domain_blocking" => true,
694 "subscribing" => true,
701 defp follow_by_uri_request do
703 title: "AccountFollowsRequest",
704 description: "POST body for muting an account",
707 uri: %Schema{type: :string, nullable: true, format: :uri}
715 title: "AccountMuteRequest",
716 description: "POST body for muting an account",
719 notifications: %Schema{
720 allOf: [BooleanLike],
722 description: "Mute notifications in addition to statuses? Defaults to true.",
727 "notifications" => true
732 defp array_of_lists do
734 title: "ArrayOfLists",
735 description: "Response schema for lists",
739 %{"id" => "123", "title" => "my list"},
740 %{"id" => "1337", "title" => "anotehr list"}
745 defp array_of_statuses do
747 title: "ArrayOfStatuses",
753 defp attribute_field do
755 title: "AccountAttributeField",
756 description: "Request schema for account custom fields",
759 name: %Schema{type: :string},
760 value: %Schema{type: :string}
762 required: [:name, :value],
765 "value" => "https://pleroma.com"