1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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.",
103 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
104 with_relationships_param()
107 200 => Operation.response("Account", "application/json", Account),
108 401 => Operation.response("Error", "application/json", ApiError),
109 404 => Operation.response("Error", "application/json", ApiError)
114 def statuses_operation do
118 operationId: "AccountController.statuses",
120 "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)",
123 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
124 Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
125 Operation.parameter(:tagged, :query, :string, "With tag"),
130 "Include only statuses with media attached"
136 "Include statuses from muted accounts."
138 Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
139 Operation.parameter(:exclude_replies, :query, BooleanLike, "Exclude replies"),
141 :exclude_visibilities,
143 %Schema{type: :array, items: VisibilityScope},
144 "Exclude visibilities"
150 "Include reactions from muted accounts."
152 ] ++ pagination_params(),
154 200 => Operation.response("Statuses", "application/json", array_of_statuses()),
155 401 => Operation.response("Error", "application/json", ApiError),
156 404 => Operation.response("Error", "application/json", ApiError)
161 def followers_operation do
164 summary: "Followers",
165 operationId: "AccountController.followers",
166 security: [%{"oAuth" => ["read:accounts"]}],
168 "Accounts which follow the given account, if network is not hidden by the account owner.",
170 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
171 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
172 with_relationships_param() | pagination_params()
175 200 => Operation.response("Accounts", "application/json", array_of_accounts())
180 def following_operation do
183 summary: "Following",
184 operationId: "AccountController.following",
185 security: [%{"oAuth" => ["read:accounts"]}],
187 "Accounts which the given account is following, if network is not hidden by the account owner.",
189 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
190 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
191 with_relationships_param() | pagination_params()
193 responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
197 def lists_operation do
200 summary: "Lists containing this account",
201 operationId: "AccountController.lists",
202 security: [%{"oAuth" => ["read:lists"]}],
203 description: "User lists that you have added this account to.",
204 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
205 responses: %{200 => Operation.response("Lists", "application/json", array_of_lists())}
209 def follow_operation do
213 operationId: "AccountController.follow",
214 security: [%{"oAuth" => ["follow", "write:follows"]}],
215 description: "Follow the given account",
217 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
227 description: "Receive this account's reblogs in home timeline? Defaults to true.",
235 200 => Operation.response("Relationship", "application/json", AccountRelationship),
236 400 => Operation.response("Error", "application/json", ApiError),
237 404 => Operation.response("Error", "application/json", ApiError)
242 def unfollow_operation do
246 operationId: "AccountController.unfollow",
247 security: [%{"oAuth" => ["follow", "write:follows"]}],
248 description: "Unfollow the given account",
249 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
251 200 => Operation.response("Relationship", "application/json", AccountRelationship),
252 400 => Operation.response("Error", "application/json", ApiError),
253 404 => Operation.response("Error", "application/json", ApiError)
258 def mute_operation do
262 operationId: "AccountController.mute",
263 security: [%{"oAuth" => ["follow", "write:mutes"]}],
264 requestBody: request_body("Parameters", mute_request()),
266 "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).",
268 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
272 %Schema{allOf: [BooleanLike], default: true},
273 "Mute notifications in addition to statuses? Defaults to `true`."
278 %Schema{type: :integer, default: 0},
279 "Expire the mute in `expires_in` seconds. Default 0 for infinity"
283 200 => Operation.response("Relationship", "application/json", AccountRelationship)
288 def unmute_operation do
292 operationId: "AccountController.unmute",
293 security: [%{"oAuth" => ["follow", "write:mutes"]}],
294 description: "Unmute the given account.",
295 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
297 200 => Operation.response("Relationship", "application/json", AccountRelationship)
302 def block_operation do
306 operationId: "AccountController.block",
307 security: [%{"oAuth" => ["follow", "write:blocks"]}],
309 "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
310 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
312 200 => Operation.response("Relationship", "application/json", AccountRelationship)
317 def unblock_operation do
321 operationId: "AccountController.unblock",
322 security: [%{"oAuth" => ["follow", "write:blocks"]}],
323 description: "Unblock the given account.",
324 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
326 200 => Operation.response("Relationship", "application/json", AccountRelationship)
331 def follow_by_uri_operation do
334 summary: "Follow by URI",
335 operationId: "AccountController.follows",
336 security: [%{"oAuth" => ["follow", "write:follows"]}],
337 requestBody: request_body("Parameters", follow_by_uri_request(), required: true),
339 200 => Operation.response("Account", "application/json", AccountRelationship),
340 400 => Operation.response("Error", "application/json", ApiError),
341 404 => Operation.response("Error", "application/json", ApiError)
346 def mutes_operation do
349 summary: "Muted accounts",
350 operationId: "AccountController.mutes",
351 description: "Accounts the user has muted.",
352 security: [%{"oAuth" => ["follow", "read:mutes"]}],
353 parameters: [with_relationships_param() | pagination_params()],
355 200 => Operation.response("Accounts", "application/json", array_of_accounts())
360 def blocks_operation do
363 summary: "Blocked users",
364 operationId: "AccountController.blocks",
365 description: "View your blocks. See also accounts/:id/{block,unblock}",
366 security: [%{"oAuth" => ["read:blocks"]}],
367 parameters: pagination_params(),
369 200 => Operation.response("Accounts", "application/json", array_of_accounts())
374 def endorsements_operation do
377 summary: "Endorsements",
378 operationId: "AccountController.endorsements",
379 description: "Not implemented",
380 security: [%{"oAuth" => ["read:accounts"]}],
382 200 => empty_array_response()
387 def identity_proofs_operation do
390 summary: "Identity proofs",
391 operationId: "AccountController.identity_proofs",
392 # Validators complains about unused path params otherwise
394 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
396 description: "Not implemented",
398 200 => empty_array_response()
403 defp create_request do
405 title: "AccountCreateRequest",
406 description: "POST body for creating an account",
408 required: [:username, :password, :agreement],
414 "Text that will be reviewed by moderators if registrations require manual approval"
416 username: %Schema{type: :string, description: "The desired username for the account"},
421 "The email address to be used for login. Required when `account_activation_required` is enabled.",
426 description: "The password to be used for login",
430 allOf: [BooleanLike],
432 "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."
437 description: "The language of the confirmation email that will be sent"
439 # Pleroma-specific properties:
440 fullname: %Schema{type: :string, nullable: true, description: "Full name"},
441 bio: %Schema{type: :string, description: "Bio", nullable: true, default: ""},
442 captcha_solution: %Schema{
445 description: "Provider-specific captcha solution"
447 captcha_token: %Schema{
450 description: "Provider-specific captcha token"
452 captcha_answer_data: %Schema{
455 description: "Provider-specific captcha data"
460 description: "Invite token required when the registrations aren't public"
464 "username" => "cofe",
465 "email" => "cofe@example.com",
466 "password" => "secret",
467 "agreement" => "true",
473 # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
474 defp create_response do
476 title: "AccountCreateResponse",
477 description: "Response schema for an account",
480 # The response when auto-login on create succeeds (token is issued):
481 token_type: %Schema{type: :string},
482 access_token: %Schema{type: :string},
483 refresh_token: %Schema{type: :string},
484 scope: %Schema{type: :string},
485 created_at: %Schema{type: :integer, format: :"date-time"},
486 me: %Schema{type: :string},
487 expires_in: %Schema{type: :integer},
489 # The response when registration succeeds but auto-login fails (no token):
490 identifier: %Schema{type: :string},
491 message: %Schema{type: :string}
493 # Note: example of successful registration with failed login response:
495 # "identifier" => "missing_confirmed_email",
496 # "message" => "You have been registered. Please check your email for further instructions."
499 "token_type" => "Bearer",
500 "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
501 "refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
502 "created_at" => 1_585_918_714,
504 "scope" => "read write follow push",
505 "me" => "https://gensokyo.2hu/users/raymoo"
510 defp update_credentials_request do
512 title: "AccountUpdateCredentialsRequest",
513 description: "POST body for creating an account",
517 allOf: [BooleanLike],
519 description: "Whether the account has a bot flag."
521 display_name: %Schema{
524 description: "The display name to use for the profile."
526 note: %Schema{type: :string, description: "The account bio."},
530 description: "Avatar image encoded using multipart/form-data",
536 description: "Header image encoded using multipart/form-data",
540 allOf: [BooleanLike],
542 description: "Whether manual approval of follow requests is required."
544 accepts_chat_messages: %Schema{
545 allOf: [BooleanLike],
547 description: "Whether the user accepts receiving chat messages."
549 fields_attributes: %Schema{
552 %Schema{type: :array, items: attribute_field()},
553 %Schema{type: :object, additionalProperties: attribute_field()}
556 # NOTE: `source` field is not supported
561 # privacy: %Schema{type: :string},
562 # sensitive: %Schema{type: :boolean},
563 # language: %Schema{type: :string}
567 # Pleroma-specific fields
568 no_rich_text: %Schema{
569 allOf: [BooleanLike],
571 description: "html tags are stripped from all statuses requested from the API"
573 hide_followers: %Schema{
574 allOf: [BooleanLike],
576 description: "user's followers will be hidden"
578 hide_follows: %Schema{
579 allOf: [BooleanLike],
581 description: "user's follows will be hidden"
583 hide_followers_count: %Schema{
584 allOf: [BooleanLike],
586 description: "user's follower count will be hidden"
588 hide_follows_count: %Schema{
589 allOf: [BooleanLike],
591 description: "user's follow count will be hidden"
593 hide_favorites: %Schema{
594 allOf: [BooleanLike],
596 description: "user's favorites timeline will be hidden"
599 allOf: [BooleanLike],
601 description: "user's role (e.g admin, moderator) will be exposed to anyone in the
604 default_scope: VisibilityScope,
605 pleroma_settings_store: %Schema{
608 description: "Opaque user settings to be saved on the backend."
610 skip_thread_containment: %Schema{
611 allOf: [BooleanLike],
613 description: "Skip filtering out broken threads"
615 allow_following_move: %Schema{
616 allOf: [BooleanLike],
618 description: "Allows automatically follow moved following accounts"
620 also_known_as: %Schema{
622 items: %Schema{type: :string},
624 description: "List of alternate ActivityPub IDs"
626 pleroma_background_image: %Schema{
629 description: "Sets the background image of the user.",
632 discoverable: %Schema{
633 allOf: [BooleanLike],
636 "Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
638 actor_type: ActorType
642 display_name: "cofe",
644 fields_attributes: [%{name: "foo", value: "bar"}],
646 hide_followers: true,
648 hide_followers_count: false,
649 hide_follows_count: false,
650 hide_favorites: false,
652 default_scope: "private",
653 pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
654 skip_thread_containment: false,
655 allow_following_move: false,
656 also_known_as: ["https://foo.bar/users/foo"],
663 def array_of_accounts do
665 title: "ArrayOfAccounts",
668 example: [Account.schema().example]
672 defp array_of_relationships do
674 title: "ArrayOfRelationships",
675 description: "Response schema for account relationships",
677 items: AccountRelationship,
682 "showing_reblogs" => true,
683 "followed_by" => true,
685 "blocked_by" => true,
687 "muting_notifications" => false,
688 "requested" => false,
689 "domain_blocking" => false,
690 "subscribing" => false,
696 "showing_reblogs" => true,
697 "followed_by" => true,
699 "blocked_by" => true,
701 "muting_notifications" => false,
703 "domain_blocking" => false,
704 "subscribing" => false,
710 "showing_reblogs" => true,
711 "followed_by" => true,
713 "blocked_by" => false,
715 "muting_notifications" => false,
716 "requested" => false,
717 "domain_blocking" => true,
718 "subscribing" => true,
725 defp follow_by_uri_request do
727 title: "AccountFollowsRequest",
728 description: "POST body for muting an account",
731 uri: %Schema{type: :string, nullable: true, format: :uri}
739 title: "AccountMuteRequest",
740 description: "POST body for muting an account",
743 notifications: %Schema{
744 allOf: [BooleanLike],
746 description: "Mute notifications in addition to statuses? Defaults to true.",
752 description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
757 "notifications" => true,
758 "expires_in" => 86_400
763 defp array_of_lists do
765 title: "ArrayOfLists",
766 description: "Response schema for lists",
770 %{"id" => "123", "title" => "my list"},
771 %{"id" => "1337", "title" => "anotehr list"}
776 defp array_of_statuses do
778 title: "ArrayOfStatuses",
784 defp attribute_field do
786 title: "AccountAttributeField",
787 description: "Request schema for account custom fields",
790 name: %Schema{type: :string},
791 value: %Schema{type: :string}
793 required: [:name, :value],
796 "value" => "https://pleroma.com"