a0933ba52262ea638d5f8a49848eb4877e9c25b5
[akkoma] / lib / pleroma / web / api_spec / operations / account_operation.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
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
17
18 import Pleroma.Web.ApiSpec.Helpers
19
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, [])
24 end
25
26 @spec create_operation() :: Operation.t()
27 def create_operation do
28 %Operation{
29 tags: ["accounts"],
30 summary: "Register an account",
31 description:
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),
35 responses: %{
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)
40 }
41 }
42 end
43
44 def verify_credentials_operation do
45 %Operation{
46 tags: ["accounts"],
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"]}],
51 responses: %{
52 200 => Operation.response("Account", "application/json", Account)
53 }
54 }
55 end
56
57 def update_credentials_operation do
58 %Operation{
59 tags: ["accounts"],
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),
65 responses: %{
66 200 => Operation.response("Account", "application/json", Account),
67 403 => Operation.response("Error", "application/json", ApiError)
68 }
69 }
70 end
71
72 def relationships_operation do
73 %Operation{
74 tags: ["accounts"],
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"]}],
79 parameters: [
80 Operation.parameter(
81 :id,
82 :query,
83 %Schema{
84 oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
85 },
86 "Account IDs",
87 example: "123"
88 )
89 ],
90 responses: %{
91 200 => Operation.response("Account", "application/json", array_of_relationships())
92 }
93 }
94 end
95
96 def show_operation do
97 %Operation{
98 tags: ["accounts"],
99 summary: "Account",
100 operationId: "AccountController.show",
101 description: "View information about a profile.",
102 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
103 responses: %{
104 200 => Operation.response("Account", "application/json", Account),
105 401 => Operation.response("Error", "application/json", ApiError),
106 404 => Operation.response("Error", "application/json", ApiError)
107 }
108 }
109 end
110
111 def statuses_operation do
112 %Operation{
113 tags: ["accounts"],
114 summary: "Statuses",
115 operationId: "AccountController.statuses",
116 description:
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)",
118 parameters:
119 [
120 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
121 Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
122 Operation.parameter(:tagged, :query, :string, "With tag"),
123 Operation.parameter(
124 :only_media,
125 :query,
126 BooleanLike,
127 "Include only statuses with media attached"
128 ),
129 Operation.parameter(
130 :with_muted,
131 :query,
132 BooleanLike,
133 "Include statuses from muted acccounts."
134 ),
135 Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
136 Operation.parameter(:exclude_replies, :query, BooleanLike, "Exclude replies"),
137 Operation.parameter(
138 :exclude_visibilities,
139 :query,
140 %Schema{type: :array, items: VisibilityScope},
141 "Exclude visibilities"
142 )
143 ] ++ pagination_params(),
144 responses: %{
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)
148 }
149 }
150 end
151
152 def followers_operation do
153 %Operation{
154 tags: ["accounts"],
155 summary: "Followers",
156 operationId: "AccountController.followers",
157 security: [%{"oAuth" => ["read:accounts"]}],
158 description:
159 "Accounts which follow the given account, if network is not hidden by the account owner.",
160 parameters: [
161 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
162 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
163 with_relationships_param() | pagination_params()
164 ],
165 responses: %{
166 200 => Operation.response("Accounts", "application/json", array_of_accounts())
167 }
168 }
169 end
170
171 def following_operation do
172 %Operation{
173 tags: ["accounts"],
174 summary: "Following",
175 operationId: "AccountController.following",
176 security: [%{"oAuth" => ["read:accounts"]}],
177 description:
178 "Accounts which the given account is following, if network is not hidden by the account owner.",
179 parameters: [
180 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
181 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
182 with_relationships_param() | pagination_params()
183 ],
184 responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
185 }
186 end
187
188 def lists_operation do
189 %Operation{
190 tags: ["accounts"],
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())}
197 }
198 end
199
200 def follow_operation do
201 %Operation{
202 tags: ["accounts"],
203 summary: "Follow",
204 operationId: "AccountController.follow",
205 security: [%{"oAuth" => ["follow", "write:follows"]}],
206 description: "Follow the given account",
207 parameters: [
208 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
209 ],
210 requestBody:
211 request_body(
212 "Parameters",
213 %Schema{
214 type: :object,
215 properties: %{
216 reblogs: %Schema{
217 type: :boolean,
218 description: "Receive this account's reblogs in home timeline? Defaults to true.",
219 default: true
220 }
221 }
222 },
223 required: false
224 ),
225 responses: %{
226 200 => Operation.response("Relationship", "application/json", AccountRelationship),
227 400 => Operation.response("Error", "application/json", ApiError),
228 404 => Operation.response("Error", "application/json", ApiError)
229 }
230 }
231 end
232
233 def unfollow_operation do
234 %Operation{
235 tags: ["accounts"],
236 summary: "Unfollow",
237 operationId: "AccountController.unfollow",
238 security: [%{"oAuth" => ["follow", "write:follows"]}],
239 description: "Unfollow the given account",
240 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
241 responses: %{
242 200 => Operation.response("Relationship", "application/json", AccountRelationship),
243 400 => Operation.response("Error", "application/json", ApiError),
244 404 => Operation.response("Error", "application/json", ApiError)
245 }
246 }
247 end
248
249 def mute_operation do
250 %Operation{
251 tags: ["accounts"],
252 summary: "Mute",
253 operationId: "AccountController.mute",
254 security: [%{"oAuth" => ["follow", "write:mutes"]}],
255 requestBody: request_body("Parameters", mute_request()),
256 description:
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).",
258 parameters: [
259 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
260 Operation.parameter(
261 :notifications,
262 :query,
263 %Schema{allOf: [BooleanLike], default: true},
264 "Mute notifications in addition to statuses? Defaults to `true`."
265 ),
266 Operation.parameter(
267 :expires_in,
268 :query,
269 %Schema{type: :integer, default: 0},
270 "Expire the mute in `expires_in` seconds. Default 0 for infinity"
271 )
272 ],
273 responses: %{
274 200 => Operation.response("Relationship", "application/json", AccountRelationship)
275 }
276 }
277 end
278
279 def unmute_operation do
280 %Operation{
281 tags: ["accounts"],
282 summary: "Unmute",
283 operationId: "AccountController.unmute",
284 security: [%{"oAuth" => ["follow", "write:mutes"]}],
285 description: "Unmute the given account.",
286 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
287 responses: %{
288 200 => Operation.response("Relationship", "application/json", AccountRelationship)
289 }
290 }
291 end
292
293 def block_operation do
294 %Operation{
295 tags: ["accounts"],
296 summary: "Block",
297 operationId: "AccountController.block",
298 security: [%{"oAuth" => ["follow", "write:blocks"]}],
299 description:
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"}],
302 responses: %{
303 200 => Operation.response("Relationship", "application/json", AccountRelationship)
304 }
305 }
306 end
307
308 def unblock_operation do
309 %Operation{
310 tags: ["accounts"],
311 summary: "Unblock",
312 operationId: "AccountController.unblock",
313 security: [%{"oAuth" => ["follow", "write:blocks"]}],
314 description: "Unblock the given account.",
315 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
316 responses: %{
317 200 => Operation.response("Relationship", "application/json", AccountRelationship)
318 }
319 }
320 end
321
322 def follow_by_uri_operation do
323 %Operation{
324 tags: ["accounts"],
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),
329 responses: %{
330 200 => Operation.response("Account", "application/json", AccountRelationship),
331 400 => Operation.response("Error", "application/json", ApiError),
332 404 => Operation.response("Error", "application/json", ApiError)
333 }
334 }
335 end
336
337 def mutes_operation do
338 %Operation{
339 tags: ["accounts"],
340 summary: "Muted accounts",
341 operationId: "AccountController.mutes",
342 description: "Accounts the user has muted.",
343 security: [%{"oAuth" => ["follow", "read:mutes"]}],
344 responses: %{
345 200 => Operation.response("Accounts", "application/json", array_of_accounts())
346 }
347 }
348 end
349
350 def blocks_operation do
351 %Operation{
352 tags: ["accounts"],
353 summary: "Blocked users",
354 operationId: "AccountController.blocks",
355 description: "View your blocks. See also accounts/:id/{block,unblock}",
356 security: [%{"oAuth" => ["read:blocks"]}],
357 responses: %{
358 200 => Operation.response("Accounts", "application/json", array_of_accounts())
359 }
360 }
361 end
362
363 def endorsements_operation do
364 %Operation{
365 tags: ["accounts"],
366 summary: "Endorsements",
367 operationId: "AccountController.endorsements",
368 description: "Not implemented",
369 security: [%{"oAuth" => ["read:accounts"]}],
370 responses: %{
371 200 => empty_array_response()
372 }
373 }
374 end
375
376 def identity_proofs_operation do
377 %Operation{
378 tags: ["accounts"],
379 summary: "Identity proofs",
380 operationId: "AccountController.identity_proofs",
381 # Validators complains about unused path params otherwise
382 parameters: [
383 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
384 ],
385 description: "Not implemented",
386 responses: %{
387 200 => empty_array_response()
388 }
389 }
390 end
391
392 defp create_request do
393 %Schema{
394 title: "AccountCreateRequest",
395 description: "POST body for creating an account",
396 type: :object,
397 required: [:username, :password, :agreement],
398 properties: %{
399 reason: %Schema{
400 type: :string,
401 nullable: true,
402 description:
403 "Text that will be reviewed by moderators if registrations require manual approval"
404 },
405 username: %Schema{type: :string, description: "The desired username for the account"},
406 email: %Schema{
407 type: :string,
408 nullable: true,
409 description:
410 "The email address to be used for login. Required when `account_activation_required` is enabled.",
411 format: :email
412 },
413 password: %Schema{
414 type: :string,
415 description: "The password to be used for login",
416 format: :password
417 },
418 agreement: %Schema{
419 allOf: [BooleanLike],
420 description:
421 "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."
422 },
423 locale: %Schema{
424 type: :string,
425 nullable: true,
426 description: "The language of the confirmation email that will be sent"
427 },
428 # Pleroma-specific properties:
429 fullname: %Schema{type: :string, nullable: true, description: "Full name"},
430 bio: %Schema{type: :string, description: "Bio", nullable: true, default: ""},
431 captcha_solution: %Schema{
432 type: :string,
433 nullable: true,
434 description: "Provider-specific captcha solution"
435 },
436 captcha_token: %Schema{
437 type: :string,
438 nullable: true,
439 description: "Provider-specific captcha token"
440 },
441 captcha_answer_data: %Schema{
442 type: :string,
443 nullable: true,
444 description: "Provider-specific captcha data"
445 },
446 token: %Schema{
447 type: :string,
448 nullable: true,
449 description: "Invite token required when the registrations aren't public"
450 }
451 },
452 example: %{
453 "username" => "cofe",
454 "email" => "cofe@example.com",
455 "password" => "secret",
456 "agreement" => "true",
457 "bio" => "☕️"
458 }
459 }
460 end
461
462 # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
463 defp create_response do
464 %Schema{
465 title: "AccountCreateResponse",
466 description: "Response schema for an account",
467 type: :object,
468 properties: %{
469 # The response when auto-login on create succeeds (token is issued):
470 token_type: %Schema{type: :string},
471 access_token: %Schema{type: :string},
472 refresh_token: %Schema{type: :string},
473 scope: %Schema{type: :string},
474 created_at: %Schema{type: :integer, format: :"date-time"},
475 me: %Schema{type: :string},
476 expires_in: %Schema{type: :integer},
477 #
478 # The response when registration succeeds but auto-login fails (no token):
479 identifier: %Schema{type: :string},
480 message: %Schema{type: :string}
481 },
482 # Note: example of successful registration with failed login response:
483 # example: %{
484 # "identifier" => "missing_confirmed_email",
485 # "message" => "You have been registered. Please check your email for further instructions."
486 # },
487 example: %{
488 "token_type" => "Bearer",
489 "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
490 "refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
491 "created_at" => 1_585_918_714,
492 "expires_in" => 600,
493 "scope" => "read write follow push",
494 "me" => "https://gensokyo.2hu/users/raymoo"
495 }
496 }
497 end
498
499 defp update_credentials_request do
500 %Schema{
501 title: "AccountUpdateCredentialsRequest",
502 description: "POST body for creating an account",
503 type: :object,
504 properties: %{
505 bot: %Schema{
506 allOf: [BooleanLike],
507 nullable: true,
508 description: "Whether the account has a bot flag."
509 },
510 display_name: %Schema{
511 type: :string,
512 nullable: true,
513 description: "The display name to use for the profile."
514 },
515 note: %Schema{type: :string, description: "The account bio."},
516 avatar: %Schema{
517 type: :string,
518 nullable: true,
519 description: "Avatar image encoded using multipart/form-data",
520 format: :binary
521 },
522 header: %Schema{
523 type: :string,
524 nullable: true,
525 description: "Header image encoded using multipart/form-data",
526 format: :binary
527 },
528 locked: %Schema{
529 allOf: [BooleanLike],
530 nullable: true,
531 description: "Whether manual approval of follow requests is required."
532 },
533 accepts_chat_messages: %Schema{
534 allOf: [BooleanLike],
535 nullable: true,
536 description: "Whether the user accepts receiving chat messages."
537 },
538 fields_attributes: %Schema{
539 nullable: true,
540 oneOf: [
541 %Schema{type: :array, items: attribute_field()},
542 %Schema{type: :object, additionalProperties: attribute_field()}
543 ]
544 },
545 # NOTE: `source` field is not supported
546 #
547 # source: %Schema{
548 # type: :object,
549 # properties: %{
550 # privacy: %Schema{type: :string},
551 # sensitive: %Schema{type: :boolean},
552 # language: %Schema{type: :string}
553 # }
554 # },
555
556 # Pleroma-specific fields
557 no_rich_text: %Schema{
558 allOf: [BooleanLike],
559 nullable: true,
560 description: "html tags are stripped from all statuses requested from the API"
561 },
562 hide_followers: %Schema{
563 allOf: [BooleanLike],
564 nullable: true,
565 description: "user's followers will be hidden"
566 },
567 hide_follows: %Schema{
568 allOf: [BooleanLike],
569 nullable: true,
570 description: "user's follows will be hidden"
571 },
572 hide_followers_count: %Schema{
573 allOf: [BooleanLike],
574 nullable: true,
575 description: "user's follower count will be hidden"
576 },
577 hide_follows_count: %Schema{
578 allOf: [BooleanLike],
579 nullable: true,
580 description: "user's follow count will be hidden"
581 },
582 hide_favorites: %Schema{
583 allOf: [BooleanLike],
584 nullable: true,
585 description: "user's favorites timeline will be hidden"
586 },
587 show_role: %Schema{
588 allOf: [BooleanLike],
589 nullable: true,
590 description: "user's role (e.g admin, moderator) will be exposed to anyone in the
591 API"
592 },
593 default_scope: VisibilityScope,
594 pleroma_settings_store: %Schema{
595 type: :object,
596 nullable: true,
597 description: "Opaque user settings to be saved on the backend."
598 },
599 skip_thread_containment: %Schema{
600 allOf: [BooleanLike],
601 nullable: true,
602 description: "Skip filtering out broken threads"
603 },
604 allow_following_move: %Schema{
605 allOf: [BooleanLike],
606 nullable: true,
607 description: "Allows automatically follow moved following accounts"
608 },
609 pleroma_background_image: %Schema{
610 type: :string,
611 nullable: true,
612 description: "Sets the background image of the user.",
613 format: :binary
614 },
615 discoverable: %Schema{
616 allOf: [BooleanLike],
617 nullable: true,
618 description:
619 "Discovery of this account in search results and other services is allowed."
620 },
621 actor_type: ActorType
622 },
623 example: %{
624 bot: false,
625 display_name: "cofe",
626 note: "foobar",
627 fields_attributes: [%{name: "foo", value: "bar"}],
628 no_rich_text: false,
629 hide_followers: true,
630 hide_follows: false,
631 hide_followers_count: false,
632 hide_follows_count: false,
633 hide_favorites: false,
634 show_role: false,
635 default_scope: "private",
636 pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
637 skip_thread_containment: false,
638 allow_following_move: false,
639 discoverable: false,
640 actor_type: "Person"
641 }
642 }
643 end
644
645 def array_of_accounts do
646 %Schema{
647 title: "ArrayOfAccounts",
648 type: :array,
649 items: Account,
650 example: [Account.schema().example]
651 }
652 end
653
654 defp array_of_relationships do
655 %Schema{
656 title: "ArrayOfRelationships",
657 description: "Response schema for account relationships",
658 type: :array,
659 items: AccountRelationship,
660 example: [
661 %{
662 "id" => "1",
663 "following" => true,
664 "showing_reblogs" => true,
665 "followed_by" => true,
666 "blocking" => false,
667 "blocked_by" => true,
668 "muting" => false,
669 "muting_notifications" => false,
670 "requested" => false,
671 "domain_blocking" => false,
672 "subscribing" => false,
673 "endorsed" => true
674 },
675 %{
676 "id" => "2",
677 "following" => true,
678 "showing_reblogs" => true,
679 "followed_by" => true,
680 "blocking" => false,
681 "blocked_by" => true,
682 "muting" => true,
683 "muting_notifications" => false,
684 "requested" => true,
685 "domain_blocking" => false,
686 "subscribing" => false,
687 "endorsed" => false
688 },
689 %{
690 "id" => "3",
691 "following" => true,
692 "showing_reblogs" => true,
693 "followed_by" => true,
694 "blocking" => true,
695 "blocked_by" => false,
696 "muting" => true,
697 "muting_notifications" => false,
698 "requested" => false,
699 "domain_blocking" => true,
700 "subscribing" => true,
701 "endorsed" => false
702 }
703 ]
704 }
705 end
706
707 defp follow_by_uri_request do
708 %Schema{
709 title: "AccountFollowsRequest",
710 description: "POST body for muting an account",
711 type: :object,
712 properties: %{
713 uri: %Schema{type: :string, nullable: true, format: :uri}
714 },
715 required: [:uri]
716 }
717 end
718
719 defp mute_request do
720 %Schema{
721 title: "AccountMuteRequest",
722 description: "POST body for muting an account",
723 type: :object,
724 properties: %{
725 notifications: %Schema{
726 allOf: [BooleanLike],
727 nullable: true,
728 description: "Mute notifications in addition to statuses? Defaults to true.",
729 default: true
730 },
731 expires_in: %Schema{
732 type: :integer,
733 nullable: true,
734 description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
735 default: 0
736 }
737 },
738 example: %{
739 "notifications" => true,
740 "expires_in" => 86_400
741 }
742 }
743 end
744
745 defp array_of_lists do
746 %Schema{
747 title: "ArrayOfLists",
748 description: "Response schema for lists",
749 type: :array,
750 items: List,
751 example: [
752 %{"id" => "123", "title" => "my list"},
753 %{"id" => "1337", "title" => "anotehr list"}
754 ]
755 }
756 end
757
758 defp array_of_statuses do
759 %Schema{
760 title: "ArrayOfStatuses",
761 type: :array,
762 items: Status
763 }
764 end
765
766 defp attribute_field do
767 %Schema{
768 title: "AccountAttributeField",
769 description: "Request schema for account custom fields",
770 type: :object,
771 properties: %{
772 name: %Schema{type: :string},
773 value: %Schema{type: :string}
774 },
775 required: [:name, :value],
776 example: %{
777 "name" => "Website",
778 "value" => "https://pleroma.com"
779 }
780 }
781 end
782 end