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`."
269 %Schema{type: :integer, default: 0},
270 "Expire the mute in `expires_in` seconds. Default 0 for infinity"
274 200 => Operation.response("Relationship", "application/json", AccountRelationship)
279 def unmute_operation do
283 operationId: "AccountController.unmute",
284 security: [%{"oAuth" => ["follow", "write:mutes"]}],
285 description: "Unmute the given account.",
286 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
288 200 => Operation.response("Relationship", "application/json", AccountRelationship)
293 def block_operation do
297 operationId: "AccountController.block",
298 security: [%{"oAuth" => ["follow", "write:blocks"]}],
300 "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
301 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
303 200 => Operation.response("Relationship", "application/json", AccountRelationship)
308 def unblock_operation do
312 operationId: "AccountController.unblock",
313 security: [%{"oAuth" => ["follow", "write:blocks"]}],
314 description: "Unblock the given account.",
315 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
317 200 => Operation.response("Relationship", "application/json", AccountRelationship)
322 def follow_by_uri_operation do
325 summary: "Follow by URI",
326 operationId: "AccountController.follows",
327 security: [%{"oAuth" => ["follow", "write:follows"]}],
328 requestBody: request_body("Parameters", follow_by_uri_request(), required: true),
330 200 => Operation.response("Account", "application/json", AccountRelationship),
331 400 => Operation.response("Error", "application/json", ApiError),
332 404 => Operation.response("Error", "application/json", ApiError)
337 def mutes_operation do
340 summary: "Muted accounts",
341 operationId: "AccountController.mutes",
342 description: "Accounts the user has muted.",
343 security: [%{"oAuth" => ["follow", "read:mutes"]}],
344 parameters: pagination_params(),
346 200 => Operation.response("Accounts", "application/json", array_of_accounts())
351 def blocks_operation do
354 summary: "Blocked users",
355 operationId: "AccountController.blocks",
356 description: "View your blocks. See also accounts/:id/{block,unblock}",
357 security: [%{"oAuth" => ["read:blocks"]}],
358 parameters: pagination_params(),
360 200 => Operation.response("Accounts", "application/json", array_of_accounts())
365 def endorsements_operation do
368 summary: "Endorsements",
369 operationId: "AccountController.endorsements",
370 description: "Not implemented",
371 security: [%{"oAuth" => ["read:accounts"]}],
373 200 => empty_array_response()
378 def identity_proofs_operation do
381 summary: "Identity proofs",
382 operationId: "AccountController.identity_proofs",
383 # Validators complains about unused path params otherwise
385 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
387 description: "Not implemented",
389 200 => empty_array_response()
394 defp create_request do
396 title: "AccountCreateRequest",
397 description: "POST body for creating an account",
399 required: [:username, :password, :agreement],
405 "Text that will be reviewed by moderators if registrations require manual approval"
407 username: %Schema{type: :string, description: "The desired username for the account"},
412 "The email address to be used for login. Required when `account_activation_required` is enabled.",
417 description: "The password to be used for login",
421 allOf: [BooleanLike],
423 "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."
428 description: "The language of the confirmation email that will be sent"
430 # Pleroma-specific properties:
431 fullname: %Schema{type: :string, nullable: true, description: "Full name"},
432 bio: %Schema{type: :string, description: "Bio", nullable: true, default: ""},
433 captcha_solution: %Schema{
436 description: "Provider-specific captcha solution"
438 captcha_token: %Schema{
441 description: "Provider-specific captcha token"
443 captcha_answer_data: %Schema{
446 description: "Provider-specific captcha data"
451 description: "Invite token required when the registrations aren't public"
455 "username" => "cofe",
456 "email" => "cofe@example.com",
457 "password" => "secret",
458 "agreement" => "true",
464 # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
465 defp create_response do
467 title: "AccountCreateResponse",
468 description: "Response schema for an account",
471 # The response when auto-login on create succeeds (token is issued):
472 token_type: %Schema{type: :string},
473 access_token: %Schema{type: :string},
474 refresh_token: %Schema{type: :string},
475 scope: %Schema{type: :string},
476 created_at: %Schema{type: :integer, format: :"date-time"},
477 me: %Schema{type: :string},
478 expires_in: %Schema{type: :integer},
480 # The response when registration succeeds but auto-login fails (no token):
481 identifier: %Schema{type: :string},
482 message: %Schema{type: :string}
484 # Note: example of successful registration with failed login response:
486 # "identifier" => "missing_confirmed_email",
487 # "message" => "You have been registered. Please check your email for further instructions."
490 "token_type" => "Bearer",
491 "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
492 "refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
493 "created_at" => 1_585_918_714,
495 "scope" => "read write follow push",
496 "me" => "https://gensokyo.2hu/users/raymoo"
501 defp update_credentials_request do
503 title: "AccountUpdateCredentialsRequest",
504 description: "POST body for creating an account",
508 allOf: [BooleanLike],
510 description: "Whether the account has a bot flag."
512 display_name: %Schema{
515 description: "The display name to use for the profile."
517 note: %Schema{type: :string, description: "The account bio."},
521 description: "Avatar image encoded using multipart/form-data",
527 description: "Header image encoded using multipart/form-data",
531 allOf: [BooleanLike],
533 description: "Whether manual approval of follow requests is required."
535 accepts_chat_messages: %Schema{
536 allOf: [BooleanLike],
538 description: "Whether the user accepts receiving chat messages."
540 fields_attributes: %Schema{
543 %Schema{type: :array, items: attribute_field()},
544 %Schema{type: :object, additionalProperties: attribute_field()}
547 # NOTE: `source` field is not supported
552 # privacy: %Schema{type: :string},
553 # sensitive: %Schema{type: :boolean},
554 # language: %Schema{type: :string}
558 # Pleroma-specific fields
559 no_rich_text: %Schema{
560 allOf: [BooleanLike],
562 description: "html tags are stripped from all statuses requested from the API"
564 hide_followers: %Schema{
565 allOf: [BooleanLike],
567 description: "user's followers will be hidden"
569 hide_follows: %Schema{
570 allOf: [BooleanLike],
572 description: "user's follows will be hidden"
574 hide_followers_count: %Schema{
575 allOf: [BooleanLike],
577 description: "user's follower count will be hidden"
579 hide_follows_count: %Schema{
580 allOf: [BooleanLike],
582 description: "user's follow count will be hidden"
584 hide_favorites: %Schema{
585 allOf: [BooleanLike],
587 description: "user's favorites timeline will be hidden"
590 allOf: [BooleanLike],
592 description: "user's role (e.g admin, moderator) will be exposed to anyone in the
595 default_scope: VisibilityScope,
596 pleroma_settings_store: %Schema{
599 description: "Opaque user settings to be saved on the backend."
601 skip_thread_containment: %Schema{
602 allOf: [BooleanLike],
604 description: "Skip filtering out broken threads"
606 allow_following_move: %Schema{
607 allOf: [BooleanLike],
609 description: "Allows automatically follow moved following accounts"
611 pleroma_background_image: %Schema{
614 description: "Sets the background image of the user.",
617 discoverable: %Schema{
618 allOf: [BooleanLike],
621 "Discovery of this account in search results and other services is allowed."
623 actor_type: ActorType
627 display_name: "cofe",
629 fields_attributes: [%{name: "foo", value: "bar"}],
631 hide_followers: true,
633 hide_followers_count: false,
634 hide_follows_count: false,
635 hide_favorites: false,
637 default_scope: "private",
638 pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
639 skip_thread_containment: false,
640 allow_following_move: false,
647 def array_of_accounts do
649 title: "ArrayOfAccounts",
652 example: [Account.schema().example]
656 defp array_of_relationships do
658 title: "ArrayOfRelationships",
659 description: "Response schema for account relationships",
661 items: AccountRelationship,
666 "showing_reblogs" => true,
667 "followed_by" => true,
669 "blocked_by" => true,
671 "muting_notifications" => false,
672 "requested" => false,
673 "domain_blocking" => false,
674 "subscribing" => false,
680 "showing_reblogs" => true,
681 "followed_by" => true,
683 "blocked_by" => true,
685 "muting_notifications" => false,
687 "domain_blocking" => false,
688 "subscribing" => false,
694 "showing_reblogs" => true,
695 "followed_by" => true,
697 "blocked_by" => false,
699 "muting_notifications" => false,
700 "requested" => false,
701 "domain_blocking" => true,
702 "subscribing" => true,
709 defp follow_by_uri_request do
711 title: "AccountFollowsRequest",
712 description: "POST body for muting an account",
715 uri: %Schema{type: :string, nullable: true, format: :uri}
723 title: "AccountMuteRequest",
724 description: "POST body for muting an account",
727 notifications: %Schema{
728 allOf: [BooleanLike],
730 description: "Mute notifications in addition to statuses? Defaults to true.",
736 description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
741 "notifications" => true,
742 "expires_in" => 86_400
747 defp array_of_lists do
749 title: "ArrayOfLists",
750 description: "Response schema for lists",
754 %{"id" => "123", "title" => "my list"},
755 %{"id" => "1337", "title" => "anotehr list"}
760 defp array_of_statuses do
762 title: "ArrayOfStatuses",
768 defp attribute_field do
770 title: "AccountAttributeField",
771 description: "Request schema for account custom fields",
774 name: %Schema{type: :string},
775 value: %Schema{type: :string}
777 required: [:name, :value],
780 "value" => "https://pleroma.com"