1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 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
29 tags: ["Account credentials"],
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
46 tags: ["Account credentials"],
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
59 tags: ["Account credentials"],
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),
68 413 => Operation.response("Error", "application/json", ApiError)
73 def relationships_operation do
75 tags: ["Retrieve account information"],
76 summary: "Relationship with current account",
77 operationId: "AccountController.relationships",
78 description: "Find out whether a given account is followed, blocked, muted, etc.",
79 security: [%{"oAuth" => ["read:follows"]}],
85 oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
92 200 => Operation.response("Account", "application/json", array_of_relationships())
99 tags: ["Retrieve account information"],
101 operationId: "AccountController.show",
102 description: "View information about a profile.",
104 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
105 with_relationships_param()
108 200 => Operation.response("Account", "application/json", Account),
109 401 => Operation.response("Error", "application/json", ApiError),
110 404 => Operation.response("Error", "application/json", ApiError)
115 def statuses_operation do
118 tags: ["Retrieve account information"],
119 operationId: "AccountController.statuses",
121 "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)",
124 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
128 BooleanLike.schema(),
129 "Include only pinned statuses"
131 Operation.parameter(:tagged, :query, :string, "With tag"),
135 BooleanLike.schema(),
136 "Include only statuses with media attached"
141 BooleanLike.schema(),
142 "Include statuses from muted accounts."
144 Operation.parameter(:exclude_reblogs, :query, BooleanLike.schema(), "Exclude reblogs"),
145 Operation.parameter(:exclude_replies, :query, BooleanLike.schema(), "Exclude replies"),
147 :exclude_visibilities,
149 %Schema{type: :array, items: VisibilityScope},
150 "Exclude visibilities"
155 BooleanLike.schema(),
156 "Include reactions from muted accounts."
158 ] ++ pagination_params(),
160 200 => Operation.response("Statuses", "application/json", array_of_statuses()),
161 401 => Operation.response("Error", "application/json", ApiError),
162 404 => Operation.response("Error", "application/json", ApiError)
167 def followers_operation do
169 tags: ["Retrieve account information"],
170 summary: "Followers",
171 operationId: "AccountController.followers",
172 security: [%{"oAuth" => ["read:accounts"]}],
174 "Accounts which follow the given account, if network is not hidden by the account owner.",
176 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
177 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
178 with_relationships_param() | pagination_params()
181 200 => Operation.response("Accounts", "application/json", array_of_accounts())
186 def following_operation do
188 tags: ["Retrieve account information"],
189 summary: "Following",
190 operationId: "AccountController.following",
191 security: [%{"oAuth" => ["read:accounts"]}],
193 "Accounts which the given account is following, if network is not hidden by the account owner.",
195 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
196 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
197 with_relationships_param() | pagination_params()
199 responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
203 def lists_operation do
205 tags: ["Retrieve account information"],
206 summary: "Lists containing this account",
207 operationId: "AccountController.lists",
208 security: [%{"oAuth" => ["read:lists"]}],
209 description: "User lists that you have added this account to.",
210 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
211 responses: %{200 => Operation.response("Lists", "application/json", array_of_lists())}
215 def follow_operation do
217 tags: ["Account actions"],
219 operationId: "AccountController.follow",
220 security: [%{"oAuth" => ["follow", "write:follows"]}],
221 description: "Follow the given account",
223 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
232 allOf: [BooleanLike],
233 description: "Receive this account's reblogs in home timeline? Defaults to true.",
237 allOf: [BooleanLike],
239 "Receive notifications for all statuses posted by the account? Defaults to false.",
247 200 => Operation.response("Relationship", "application/json", AccountRelationship),
248 400 => Operation.response("Error", "application/json", ApiError),
249 404 => Operation.response("Error", "application/json", ApiError)
254 def unfollow_operation do
256 tags: ["Account actions"],
258 operationId: "AccountController.unfollow",
259 security: [%{"oAuth" => ["follow", "write:follows"]}],
260 description: "Unfollow the given account",
261 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
263 200 => Operation.response("Relationship", "application/json", AccountRelationship),
264 400 => Operation.response("Error", "application/json", ApiError),
265 404 => Operation.response("Error", "application/json", ApiError)
270 def mute_operation do
272 tags: ["Account actions"],
274 operationId: "AccountController.mute",
275 security: [%{"oAuth" => ["follow", "write:mutes"]}],
276 requestBody: request_body("Parameters", mute_request()),
278 "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).",
280 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
284 %Schema{allOf: [BooleanLike], default: true},
285 "Mute notifications in addition to statuses? Defaults to `true`."
290 %Schema{type: :integer},
291 "Expire the mute in `duration` seconds. Default 0 for infinity"
296 %Schema{type: :integer, default: 0},
297 "Deprecated, use `duration` instead"
301 200 => Operation.response("Relationship", "application/json", AccountRelationship)
306 def unmute_operation do
308 tags: ["Account actions"],
310 operationId: "AccountController.unmute",
311 security: [%{"oAuth" => ["follow", "write:mutes"]}],
312 description: "Unmute the given account.",
313 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
315 200 => Operation.response("Relationship", "application/json", AccountRelationship)
320 def block_operation do
322 tags: ["Account actions"],
324 operationId: "AccountController.block",
325 security: [%{"oAuth" => ["follow", "write:blocks"]}],
327 "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
328 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
330 200 => Operation.response("Relationship", "application/json", AccountRelationship)
335 def unblock_operation do
337 tags: ["Account actions"],
339 operationId: "AccountController.unblock",
340 security: [%{"oAuth" => ["follow", "write:blocks"]}],
341 description: "Unblock the given account.",
342 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
344 200 => Operation.response("Relationship", "application/json", AccountRelationship)
349 def endorse_operation do
351 tags: ["Account actions"],
353 operationId: "AccountController.endorse",
354 security: [%{"oAuth" => ["follow", "write:accounts"]}],
355 description: "Adds the given account to endorsed accounts list.",
356 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
358 200 => Operation.response("Relationship", "application/json", AccountRelationship),
360 Operation.response("Bad Request", "application/json", %Schema{
362 title: "Unprocessable Entity",
364 "error" => "You have already pinned the maximum number of users"
371 def unendorse_operation do
373 tags: ["Account actions"],
374 summary: "Unendorse",
375 operationId: "AccountController.unendorse",
376 security: [%{"oAuth" => ["follow", "write:accounts"]}],
377 description: "Removes the given account from endorsed accounts list.",
378 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
380 200 => Operation.response("Relationship", "application/json", AccountRelationship)
385 def remove_from_followers_operation do
387 tags: ["Account actions"],
388 summary: "Remove from followers",
389 operationId: "AccountController.remove_from_followers",
390 security: [%{"oAuth" => ["follow", "write:follows"]}],
391 description: "Remove the given account from followers",
392 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
394 200 => Operation.response("Relationship", "application/json", AccountRelationship),
395 400 => Operation.response("Error", "application/json", ApiError),
396 404 => Operation.response("Error", "application/json", ApiError)
401 def note_operation do
403 tags: ["Account actions"],
404 summary: "Set a private note about a user.",
405 operationId: "AccountController.note",
406 security: [%{"oAuth" => ["follow", "write:accounts"]}],
407 requestBody: request_body("Parameters", note_request()),
408 description: "Create a note for the given account.",
410 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
414 %Schema{type: :string},
419 200 => Operation.response("Relationship", "application/json", AccountRelationship)
424 def follow_by_uri_operation do
426 tags: ["Account actions"],
427 summary: "Follow by URI",
428 operationId: "AccountController.follows",
429 security: [%{"oAuth" => ["follow", "write:follows"]}],
430 requestBody: request_body("Parameters", follow_by_uri_request(), required: true),
432 200 => Operation.response("Account", "application/json", AccountRelationship),
433 400 => Operation.response("Error", "application/json", ApiError),
434 404 => Operation.response("Error", "application/json", ApiError)
439 def mutes_operation do
441 tags: ["Blocks and mutes"],
442 summary: "Retrieve list of mutes",
443 operationId: "AccountController.mutes",
444 description: "Accounts the user has muted.",
445 security: [%{"oAuth" => ["follow", "read:mutes"]}],
446 parameters: [with_relationships_param() | pagination_params()],
448 200 => Operation.response("Accounts", "application/json", array_of_accounts())
453 def blocks_operation do
455 tags: ["Blocks and mutes"],
456 summary: "Retrieve list of blocks",
457 operationId: "AccountController.blocks",
458 description: "View your blocks. See also accounts/:id/{block,unblock}",
459 security: [%{"oAuth" => ["read:blocks"]}],
460 parameters: [with_relationships_param() | pagination_params()],
462 200 => Operation.response("Accounts", "application/json", array_of_accounts())
467 def lookup_operation do
469 tags: ["Retrieve account information"],
470 summary: "Find a user by nickname",
471 operationId: "AccountController.lookup",
481 200 => Operation.response("Account", "application/json", Account),
482 404 => Operation.response("Error", "application/json", ApiError)
487 def endorsements_operation do
489 tags: ["Retrieve account information"],
490 summary: "Endorsements",
491 operationId: "AccountController.endorsements",
492 description: "Returns endorsed accounts",
493 security: [%{"oAuth" => ["read:accounts"]}],
495 200 => Operation.response("Array of Accounts", "application/json", array_of_accounts())
500 def identity_proofs_operation do
502 tags: ["Retrieve account information"],
503 summary: "Identity proofs",
504 operationId: "AccountController.identity_proofs",
505 # Validators complains about unused path params otherwise
507 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
509 description: "Not implemented",
511 200 => empty_array_response()
516 defp create_request do
518 title: "AccountCreateRequest",
519 description: "POST body for creating an account",
521 required: [:username, :password, :agreement],
527 "Text that will be reviewed by moderators if registrations require manual approval"
529 username: %Schema{type: :string, description: "The desired username for the account"},
534 "The email address to be used for login. Required when `account_activation_required` is enabled.",
539 description: "The password to be used for login",
543 allOf: [BooleanLike],
545 "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."
550 description: "The language of the confirmation email that will be sent"
552 # Pleroma-specific properties:
553 fullname: %Schema{type: :string, nullable: true, description: "Full name"},
554 bio: %Schema{type: :string, description: "Bio", nullable: true, default: ""},
555 captcha_solution: %Schema{
558 description: "Provider-specific captcha solution"
560 captcha_token: %Schema{
563 description: "Provider-specific captcha token"
565 captcha_answer_data: %Schema{
568 description: "Provider-specific captcha data"
573 description: "Invite token required when the registrations aren't public"
577 description: "User's birthday",
592 description: "User's preferred language for emails"
596 "username" => "cofe",
597 "email" => "cofe@example.com",
598 "password" => "secret",
599 "agreement" => "true",
605 # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
606 defp create_response do
608 title: "AccountCreateResponse",
609 description: "Response schema for an account",
612 # The response when auto-login on create succeeds (token is issued):
613 token_type: %Schema{type: :string},
614 access_token: %Schema{type: :string},
615 refresh_token: %Schema{type: :string},
616 scope: %Schema{type: :string},
617 created_at: %Schema{type: :integer, format: :"date-time"},
618 me: %Schema{type: :string},
619 expires_in: %Schema{type: :integer},
621 # The response when registration succeeds but auto-login fails (no token):
622 identifier: %Schema{type: :string},
623 message: %Schema{type: :string}
625 # Note: example of successful registration with failed login response:
627 # "identifier" => "missing_confirmed_email",
628 # "message" => "You have been registered. Please check your email for further instructions."
631 "token_type" => "Bearer",
632 "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
633 "refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
634 "created_at" => 1_585_918_714,
636 "scope" => "read write follow push",
637 "me" => "https://gensokyo.2hu/users/raymoo"
642 defp update_credentials_request do
644 title: "AccountUpdateCredentialsRequest",
645 description: "POST body for creating an account",
649 allOf: [BooleanLike],
651 description: "Whether the account has a bot flag."
653 display_name: %Schema{
656 description: "The display name to use for the profile."
658 note: %Schema{type: :string, description: "The account bio."},
662 description: "Avatar image encoded using multipart/form-data",
668 description: "Header image encoded using multipart/form-data",
672 allOf: [BooleanLike],
674 description: "Whether manual approval of follow requests is required."
676 accepts_chat_messages: %Schema{
677 allOf: [BooleanLike],
679 description: "Whether the user accepts receiving chat messages."
681 fields_attributes: %Schema{
684 %Schema{type: :array, items: attribute_field()},
685 %Schema{type: :object, additionalProperties: attribute_field()}
688 # NOTE: `source` field is not supported
693 # privacy: %Schema{type: :string},
694 # sensitive: %Schema{type: :boolean},
695 # language: %Schema{type: :string}
699 # Pleroma-specific fields
700 no_rich_text: %Schema{
701 allOf: [BooleanLike],
703 description: "html tags are stripped from all statuses requested from the API"
705 hide_followers: %Schema{
706 allOf: [BooleanLike],
708 description: "user's followers will be hidden"
710 hide_follows: %Schema{
711 allOf: [BooleanLike],
713 description: "user's follows will be hidden"
715 hide_followers_count: %Schema{
716 allOf: [BooleanLike],
718 description: "user's follower count will be hidden"
720 hide_follows_count: %Schema{
721 allOf: [BooleanLike],
723 description: "user's follow count will be hidden"
725 hide_favorites: %Schema{
726 allOf: [BooleanLike],
728 description: "user's favorites timeline will be hidden"
731 allOf: [BooleanLike],
733 description: "user's role (e.g admin, moderator) will be exposed to anyone in the
736 default_scope: VisibilityScope,
737 pleroma_settings_store: %Schema{
740 description: "Opaque user settings to be saved on the backend."
742 skip_thread_containment: %Schema{
743 allOf: [BooleanLike],
745 description: "Skip filtering out broken threads"
747 allow_following_move: %Schema{
748 allOf: [BooleanLike],
750 description: "Allows automatically follow moved following accounts"
752 also_known_as: %Schema{
754 items: %Schema{type: :string},
756 description: "List of alternate ActivityPub IDs"
758 pleroma_background_image: %Schema{
761 description: "Sets the background image of the user.",
764 discoverable: %Schema{
765 allOf: [BooleanLike],
768 "Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
770 actor_type: ActorType,
773 description: "User's birthday",
785 show_birthday: %Schema{
786 allOf: [BooleanLike],
788 description: "User's birthday will be visible"
793 display_name: "cofe",
795 fields_attributes: [%{name: "foo", value: "bar"}],
797 hide_followers: true,
799 hide_followers_count: false,
800 hide_follows_count: false,
801 hide_favorites: false,
803 default_scope: "private",
804 pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
805 skip_thread_containment: false,
806 allow_following_move: false,
807 also_known_as: ["https://foo.bar/users/foo"],
809 actor_type: "Person",
810 show_birthday: false,
811 birthday: "2001-02-12"
816 def array_of_accounts do
818 title: "ArrayOfAccounts",
821 example: [Account.schema().example]
825 defp array_of_relationships do
827 title: "ArrayOfRelationships",
828 description: "Response schema for account relationships",
830 items: AccountRelationship,
835 "showing_reblogs" => true,
836 "followed_by" => true,
838 "blocked_by" => true,
840 "muting_notifications" => false,
842 "requested" => false,
843 "domain_blocking" => false,
844 "subscribing" => false,
845 "notifying" => false,
851 "showing_reblogs" => true,
852 "followed_by" => true,
854 "blocked_by" => true,
856 "muting_notifications" => false,
859 "domain_blocking" => false,
860 "subscribing" => false,
861 "notifying" => false,
867 "showing_reblogs" => true,
868 "followed_by" => true,
870 "blocked_by" => false,
872 "muting_notifications" => false,
874 "requested" => false,
875 "domain_blocking" => true,
876 "subscribing" => true,
884 defp follow_by_uri_request do
886 title: "AccountFollowsRequest",
887 description: "POST body for muting an account",
890 uri: %Schema{type: :string, nullable: true, format: :uri}
898 title: "AccountMuteRequest",
899 description: "POST body for muting an account",
902 notifications: %Schema{
903 allOf: [BooleanLike],
905 description: "Mute notifications in addition to statuses? Defaults to true.",
911 description: "Expire the mute in `expires_in` seconds. Default 0 for infinity"
916 description: "Deprecated, use `duration` instead",
921 "notifications" => true,
922 "expires_in" => 86_400
929 title: "AccountNoteRequest",
930 description: "POST body for adding a note for an account",
935 description: "Account note body"
939 "comment" => "Example note"
944 defp array_of_lists do
946 title: "ArrayOfLists",
947 description: "Response schema for lists",
951 %{"id" => "123", "title" => "my list"},
952 %{"id" => "1337", "title" => "anotehr list"}
957 defp array_of_statuses do
959 title: "ArrayOfStatuses",
965 defp attribute_field do
967 title: "AccountAttributeField",
968 description: "Request schema for account custom fields",
971 name: %Schema{type: :string},
972 value: %Schema{type: :string}
974 required: [:name, :value],
977 "value" => "https://pleroma.com"