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"
147 "Include reactions from muted acccounts."
149 ] ++ pagination_params(),
151 200 => Operation.response("Statuses", "application/json", array_of_statuses()),
152 401 => Operation.response("Error", "application/json", ApiError),
153 404 => Operation.response("Error", "application/json", ApiError)
158 def followers_operation do
161 summary: "Followers",
162 operationId: "AccountController.followers",
163 security: [%{"oAuth" => ["read:accounts"]}],
165 "Accounts which follow the given account, if network is not hidden by the account owner.",
167 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
168 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
169 with_relationships_param() | pagination_params()
172 200 => Operation.response("Accounts", "application/json", array_of_accounts())
177 def following_operation do
180 summary: "Following",
181 operationId: "AccountController.following",
182 security: [%{"oAuth" => ["read:accounts"]}],
184 "Accounts which the given account is following, if network is not hidden by the account owner.",
186 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
187 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
188 with_relationships_param() | pagination_params()
190 responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
194 def lists_operation do
197 summary: "Lists containing this account",
198 operationId: "AccountController.lists",
199 security: [%{"oAuth" => ["read:lists"]}],
200 description: "User lists that you have added this account to.",
201 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
202 responses: %{200 => Operation.response("Lists", "application/json", array_of_lists())}
206 def follow_operation do
210 operationId: "AccountController.follow",
211 security: [%{"oAuth" => ["follow", "write:follows"]}],
212 description: "Follow the given account",
214 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
224 description: "Receive this account's reblogs in home timeline? Defaults to true.",
232 200 => Operation.response("Relationship", "application/json", AccountRelationship),
233 400 => Operation.response("Error", "application/json", ApiError),
234 404 => Operation.response("Error", "application/json", ApiError)
239 def unfollow_operation do
243 operationId: "AccountController.unfollow",
244 security: [%{"oAuth" => ["follow", "write:follows"]}],
245 description: "Unfollow the given account",
246 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
248 200 => Operation.response("Relationship", "application/json", AccountRelationship),
249 400 => Operation.response("Error", "application/json", ApiError),
250 404 => Operation.response("Error", "application/json", ApiError)
255 def mute_operation do
259 operationId: "AccountController.mute",
260 security: [%{"oAuth" => ["follow", "write:mutes"]}],
261 requestBody: request_body("Parameters", mute_request()),
263 "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).",
265 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
269 %Schema{allOf: [BooleanLike], default: true},
270 "Mute notifications in addition to statuses? Defaults to `true`."
275 %Schema{type: :integer, default: 0},
276 "Expire the mute in `expires_in` seconds. Default 0 for infinity"
280 200 => Operation.response("Relationship", "application/json", AccountRelationship)
285 def unmute_operation do
289 operationId: "AccountController.unmute",
290 security: [%{"oAuth" => ["follow", "write:mutes"]}],
291 description: "Unmute the given account.",
292 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
294 200 => Operation.response("Relationship", "application/json", AccountRelationship)
299 def block_operation do
303 operationId: "AccountController.block",
304 security: [%{"oAuth" => ["follow", "write:blocks"]}],
306 "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
307 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
309 200 => Operation.response("Relationship", "application/json", AccountRelationship)
314 def unblock_operation do
318 operationId: "AccountController.unblock",
319 security: [%{"oAuth" => ["follow", "write:blocks"]}],
320 description: "Unblock the given account.",
321 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
323 200 => Operation.response("Relationship", "application/json", AccountRelationship)
328 def follow_by_uri_operation do
331 summary: "Follow by URI",
332 operationId: "AccountController.follows",
333 security: [%{"oAuth" => ["follow", "write:follows"]}],
334 requestBody: request_body("Parameters", follow_by_uri_request(), required: true),
336 200 => Operation.response("Account", "application/json", AccountRelationship),
337 400 => Operation.response("Error", "application/json", ApiError),
338 404 => Operation.response("Error", "application/json", ApiError)
343 def mutes_operation do
346 summary: "Muted accounts",
347 operationId: "AccountController.mutes",
348 description: "Accounts the user has muted.",
349 security: [%{"oAuth" => ["follow", "read:mutes"]}],
350 parameters: pagination_params(),
352 200 => Operation.response("Accounts", "application/json", array_of_accounts())
357 def blocks_operation do
360 summary: "Blocked users",
361 operationId: "AccountController.blocks",
362 description: "View your blocks. See also accounts/:id/{block,unblock}",
363 security: [%{"oAuth" => ["read:blocks"]}],
364 parameters: pagination_params(),
366 200 => Operation.response("Accounts", "application/json", array_of_accounts())
371 def endorsements_operation do
374 summary: "Endorsements",
375 operationId: "AccountController.endorsements",
376 description: "Not implemented",
377 security: [%{"oAuth" => ["read:accounts"]}],
379 200 => empty_array_response()
384 def identity_proofs_operation do
387 summary: "Identity proofs",
388 operationId: "AccountController.identity_proofs",
389 # Validators complains about unused path params otherwise
391 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
393 description: "Not implemented",
395 200 => empty_array_response()
400 defp create_request do
402 title: "AccountCreateRequest",
403 description: "POST body for creating an account",
405 required: [:username, :password, :agreement],
411 "Text that will be reviewed by moderators if registrations require manual approval"
413 username: %Schema{type: :string, description: "The desired username for the account"},
418 "The email address to be used for login. Required when `account_activation_required` is enabled.",
423 description: "The password to be used for login",
427 allOf: [BooleanLike],
429 "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."
434 description: "The language of the confirmation email that will be sent"
436 # Pleroma-specific properties:
437 fullname: %Schema{type: :string, nullable: true, description: "Full name"},
438 bio: %Schema{type: :string, description: "Bio", nullable: true, default: ""},
439 captcha_solution: %Schema{
442 description: "Provider-specific captcha solution"
444 captcha_token: %Schema{
447 description: "Provider-specific captcha token"
449 captcha_answer_data: %Schema{
452 description: "Provider-specific captcha data"
457 description: "Invite token required when the registrations aren't public"
461 "username" => "cofe",
462 "email" => "cofe@example.com",
463 "password" => "secret",
464 "agreement" => "true",
470 # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
471 defp create_response do
473 title: "AccountCreateResponse",
474 description: "Response schema for an account",
477 # The response when auto-login on create succeeds (token is issued):
478 token_type: %Schema{type: :string},
479 access_token: %Schema{type: :string},
480 refresh_token: %Schema{type: :string},
481 scope: %Schema{type: :string},
482 created_at: %Schema{type: :integer, format: :"date-time"},
483 me: %Schema{type: :string},
484 expires_in: %Schema{type: :integer},
486 # The response when registration succeeds but auto-login fails (no token):
487 identifier: %Schema{type: :string},
488 message: %Schema{type: :string}
490 # Note: example of successful registration with failed login response:
492 # "identifier" => "missing_confirmed_email",
493 # "message" => "You have been registered. Please check your email for further instructions."
496 "token_type" => "Bearer",
497 "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
498 "refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
499 "created_at" => 1_585_918_714,
501 "scope" => "read write follow push",
502 "me" => "https://gensokyo.2hu/users/raymoo"
507 defp update_credentials_request do
509 title: "AccountUpdateCredentialsRequest",
510 description: "POST body for creating an account",
514 allOf: [BooleanLike],
516 description: "Whether the account has a bot flag."
518 display_name: %Schema{
521 description: "The display name to use for the profile."
523 note: %Schema{type: :string, description: "The account bio."},
527 description: "Avatar image encoded using multipart/form-data",
533 description: "Header image encoded using multipart/form-data",
537 allOf: [BooleanLike],
539 description: "Whether manual approval of follow requests is required."
541 accepts_chat_messages: %Schema{
542 allOf: [BooleanLike],
544 description: "Whether the user accepts receiving chat messages."
546 fields_attributes: %Schema{
549 %Schema{type: :array, items: attribute_field()},
550 %Schema{type: :object, additionalProperties: attribute_field()}
553 # NOTE: `source` field is not supported
558 # privacy: %Schema{type: :string},
559 # sensitive: %Schema{type: :boolean},
560 # language: %Schema{type: :string}
564 # Pleroma-specific fields
565 no_rich_text: %Schema{
566 allOf: [BooleanLike],
568 description: "html tags are stripped from all statuses requested from the API"
570 hide_followers: %Schema{
571 allOf: [BooleanLike],
573 description: "user's followers will be hidden"
575 hide_follows: %Schema{
576 allOf: [BooleanLike],
578 description: "user's follows will be hidden"
580 hide_followers_count: %Schema{
581 allOf: [BooleanLike],
583 description: "user's follower count will be hidden"
585 hide_follows_count: %Schema{
586 allOf: [BooleanLike],
588 description: "user's follow count will be hidden"
590 hide_favorites: %Schema{
591 allOf: [BooleanLike],
593 description: "user's favorites timeline will be hidden"
596 allOf: [BooleanLike],
598 description: "user's role (e.g admin, moderator) will be exposed to anyone in the
601 default_scope: VisibilityScope,
602 pleroma_settings_store: %Schema{
605 description: "Opaque user settings to be saved on the backend."
607 skip_thread_containment: %Schema{
608 allOf: [BooleanLike],
610 description: "Skip filtering out broken threads"
612 allow_following_move: %Schema{
613 allOf: [BooleanLike],
615 description: "Allows automatically follow moved following accounts"
617 also_known_as: %Schema{
619 items: %Schema{type: :string},
621 description: "List of alternate ActivityPub IDs"
623 pleroma_background_image: %Schema{
626 description: "Sets the background image of the user.",
629 discoverable: %Schema{
630 allOf: [BooleanLike],
633 "Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
635 actor_type: ActorType
639 display_name: "cofe",
641 fields_attributes: [%{name: "foo", value: "bar"}],
643 hide_followers: true,
645 hide_followers_count: false,
646 hide_follows_count: false,
647 hide_favorites: false,
649 default_scope: "private",
650 pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
651 skip_thread_containment: false,
652 allow_following_move: false,
653 also_known_as: ["https://foo.bar/users/foo"],
660 def array_of_accounts do
662 title: "ArrayOfAccounts",
665 example: [Account.schema().example]
669 defp array_of_relationships do
671 title: "ArrayOfRelationships",
672 description: "Response schema for account relationships",
674 items: AccountRelationship,
679 "showing_reblogs" => true,
680 "followed_by" => true,
682 "blocked_by" => true,
684 "muting_notifications" => false,
685 "requested" => false,
686 "domain_blocking" => false,
687 "subscribing" => false,
693 "showing_reblogs" => true,
694 "followed_by" => true,
696 "blocked_by" => true,
698 "muting_notifications" => false,
700 "domain_blocking" => false,
701 "subscribing" => false,
707 "showing_reblogs" => true,
708 "followed_by" => true,
710 "blocked_by" => false,
712 "muting_notifications" => false,
713 "requested" => false,
714 "domain_blocking" => true,
715 "subscribing" => true,
722 defp follow_by_uri_request do
724 title: "AccountFollowsRequest",
725 description: "POST body for muting an account",
728 uri: %Schema{type: :string, nullable: true, format: :uri}
736 title: "AccountMuteRequest",
737 description: "POST body for muting an account",
740 notifications: %Schema{
741 allOf: [BooleanLike],
743 description: "Mute notifications in addition to statuses? Defaults to true.",
749 description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
754 "notifications" => true,
755 "expires_in" => 86_400
760 defp array_of_lists do
762 title: "ArrayOfLists",
763 description: "Response schema for lists",
767 %{"id" => "123", "title" => "my list"},
768 %{"id" => "1337", "title" => "anotehr list"}
773 defp array_of_statuses do
775 title: "ArrayOfStatuses",
781 defp attribute_field do
783 title: "AccountAttributeField",
784 description: "Request schema for account custom fields",
787 name: %Schema{type: :string},
788 value: %Schema{type: :string}
790 required: [:name, :value],
793 "value" => "https://pleroma.com"