Add pagination for Blocks
[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 ],
267 responses: %{
268 200 => Operation.response("Relationship", "application/json", AccountRelationship)
269 }
270 }
271 end
272
273 def unmute_operation do
274 %Operation{
275 tags: ["accounts"],
276 summary: "Unmute",
277 operationId: "AccountController.unmute",
278 security: [%{"oAuth" => ["follow", "write:mutes"]}],
279 description: "Unmute the given account.",
280 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
281 responses: %{
282 200 => Operation.response("Relationship", "application/json", AccountRelationship)
283 }
284 }
285 end
286
287 def block_operation do
288 %Operation{
289 tags: ["accounts"],
290 summary: "Block",
291 operationId: "AccountController.block",
292 security: [%{"oAuth" => ["follow", "write:blocks"]}],
293 description:
294 "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
295 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
296 responses: %{
297 200 => Operation.response("Relationship", "application/json", AccountRelationship)
298 }
299 }
300 end
301
302 def unblock_operation do
303 %Operation{
304 tags: ["accounts"],
305 summary: "Unblock",
306 operationId: "AccountController.unblock",
307 security: [%{"oAuth" => ["follow", "write:blocks"]}],
308 description: "Unblock the given account.",
309 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
310 responses: %{
311 200 => Operation.response("Relationship", "application/json", AccountRelationship)
312 }
313 }
314 end
315
316 def follow_by_uri_operation do
317 %Operation{
318 tags: ["accounts"],
319 summary: "Follow by URI",
320 operationId: "AccountController.follows",
321 security: [%{"oAuth" => ["follow", "write:follows"]}],
322 requestBody: request_body("Parameters", follow_by_uri_request(), required: true),
323 responses: %{
324 200 => Operation.response("Account", "application/json", AccountRelationship),
325 400 => Operation.response("Error", "application/json", ApiError),
326 404 => Operation.response("Error", "application/json", ApiError)
327 }
328 }
329 end
330
331 def mutes_operation do
332 %Operation{
333 tags: ["accounts"],
334 summary: "Muted accounts",
335 operationId: "AccountController.mutes",
336 description: "Accounts the user has muted.",
337 security: [%{"oAuth" => ["follow", "read:mutes"]}],
338 responses: %{
339 200 => Operation.response("Accounts", "application/json", array_of_accounts())
340 }
341 }
342 end
343
344 def blocks_operation do
345 %Operation{
346 tags: ["accounts"],
347 summary: "Blocked users",
348 operationId: "AccountController.blocks",
349 description: "View your blocks. See also accounts/:id/{block,unblock}",
350 security: [%{"oAuth" => ["read:blocks"]}],
351 parameters: pagination_params(),
352 responses: %{
353 200 => Operation.response("Accounts", "application/json", array_of_accounts())
354 }
355 }
356 end
357
358 def endorsements_operation do
359 %Operation{
360 tags: ["accounts"],
361 summary: "Endorsements",
362 operationId: "AccountController.endorsements",
363 description: "Not implemented",
364 security: [%{"oAuth" => ["read:accounts"]}],
365 responses: %{
366 200 => empty_array_response()
367 }
368 }
369 end
370
371 def identity_proofs_operation do
372 %Operation{
373 tags: ["accounts"],
374 summary: "Identity proofs",
375 operationId: "AccountController.identity_proofs",
376 # Validators complains about unused path params otherwise
377 parameters: [
378 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
379 ],
380 description: "Not implemented",
381 responses: %{
382 200 => empty_array_response()
383 }
384 }
385 end
386
387 defp create_request do
388 %Schema{
389 title: "AccountCreateRequest",
390 description: "POST body for creating an account",
391 type: :object,
392 required: [:username, :password, :agreement],
393 properties: %{
394 reason: %Schema{
395 type: :string,
396 nullable: true,
397 description:
398 "Text that will be reviewed by moderators if registrations require manual approval"
399 },
400 username: %Schema{type: :string, description: "The desired username for the account"},
401 email: %Schema{
402 type: :string,
403 nullable: true,
404 description:
405 "The email address to be used for login. Required when `account_activation_required` is enabled.",
406 format: :email
407 },
408 password: %Schema{
409 type: :string,
410 description: "The password to be used for login",
411 format: :password
412 },
413 agreement: %Schema{
414 allOf: [BooleanLike],
415 description:
416 "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."
417 },
418 locale: %Schema{
419 type: :string,
420 nullable: true,
421 description: "The language of the confirmation email that will be sent"
422 },
423 # Pleroma-specific properties:
424 fullname: %Schema{type: :string, nullable: true, description: "Full name"},
425 bio: %Schema{type: :string, description: "Bio", nullable: true, default: ""},
426 captcha_solution: %Schema{
427 type: :string,
428 nullable: true,
429 description: "Provider-specific captcha solution"
430 },
431 captcha_token: %Schema{
432 type: :string,
433 nullable: true,
434 description: "Provider-specific captcha token"
435 },
436 captcha_answer_data: %Schema{
437 type: :string,
438 nullable: true,
439 description: "Provider-specific captcha data"
440 },
441 token: %Schema{
442 type: :string,
443 nullable: true,
444 description: "Invite token required when the registrations aren't public"
445 }
446 },
447 example: %{
448 "username" => "cofe",
449 "email" => "cofe@example.com",
450 "password" => "secret",
451 "agreement" => "true",
452 "bio" => "☕️"
453 }
454 }
455 end
456
457 # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
458 defp create_response do
459 %Schema{
460 title: "AccountCreateResponse",
461 description: "Response schema for an account",
462 type: :object,
463 properties: %{
464 # The response when auto-login on create succeeds (token is issued):
465 token_type: %Schema{type: :string},
466 access_token: %Schema{type: :string},
467 refresh_token: %Schema{type: :string},
468 scope: %Schema{type: :string},
469 created_at: %Schema{type: :integer, format: :"date-time"},
470 me: %Schema{type: :string},
471 expires_in: %Schema{type: :integer},
472 #
473 # The response when registration succeeds but auto-login fails (no token):
474 identifier: %Schema{type: :string},
475 message: %Schema{type: :string}
476 },
477 # Note: example of successful registration with failed login response:
478 # example: %{
479 # "identifier" => "missing_confirmed_email",
480 # "message" => "You have been registered. Please check your email for further instructions."
481 # },
482 example: %{
483 "token_type" => "Bearer",
484 "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
485 "refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
486 "created_at" => 1_585_918_714,
487 "expires_in" => 600,
488 "scope" => "read write follow push",
489 "me" => "https://gensokyo.2hu/users/raymoo"
490 }
491 }
492 end
493
494 defp update_credentials_request do
495 %Schema{
496 title: "AccountUpdateCredentialsRequest",
497 description: "POST body for creating an account",
498 type: :object,
499 properties: %{
500 bot: %Schema{
501 allOf: [BooleanLike],
502 nullable: true,
503 description: "Whether the account has a bot flag."
504 },
505 display_name: %Schema{
506 type: :string,
507 nullable: true,
508 description: "The display name to use for the profile."
509 },
510 note: %Schema{type: :string, description: "The account bio."},
511 avatar: %Schema{
512 type: :string,
513 nullable: true,
514 description: "Avatar image encoded using multipart/form-data",
515 format: :binary
516 },
517 header: %Schema{
518 type: :string,
519 nullable: true,
520 description: "Header image encoded using multipart/form-data",
521 format: :binary
522 },
523 locked: %Schema{
524 allOf: [BooleanLike],
525 nullable: true,
526 description: "Whether manual approval of follow requests is required."
527 },
528 accepts_chat_messages: %Schema{
529 allOf: [BooleanLike],
530 nullable: true,
531 description: "Whether the user accepts receiving chat messages."
532 },
533 fields_attributes: %Schema{
534 nullable: true,
535 oneOf: [
536 %Schema{type: :array, items: attribute_field()},
537 %Schema{type: :object, additionalProperties: attribute_field()}
538 ]
539 },
540 # NOTE: `source` field is not supported
541 #
542 # source: %Schema{
543 # type: :object,
544 # properties: %{
545 # privacy: %Schema{type: :string},
546 # sensitive: %Schema{type: :boolean},
547 # language: %Schema{type: :string}
548 # }
549 # },
550
551 # Pleroma-specific fields
552 no_rich_text: %Schema{
553 allOf: [BooleanLike],
554 nullable: true,
555 description: "html tags are stripped from all statuses requested from the API"
556 },
557 hide_followers: %Schema{
558 allOf: [BooleanLike],
559 nullable: true,
560 description: "user's followers will be hidden"
561 },
562 hide_follows: %Schema{
563 allOf: [BooleanLike],
564 nullable: true,
565 description: "user's follows will be hidden"
566 },
567 hide_followers_count: %Schema{
568 allOf: [BooleanLike],
569 nullable: true,
570 description: "user's follower count will be hidden"
571 },
572 hide_follows_count: %Schema{
573 allOf: [BooleanLike],
574 nullable: true,
575 description: "user's follow count will be hidden"
576 },
577 hide_favorites: %Schema{
578 allOf: [BooleanLike],
579 nullable: true,
580 description: "user's favorites timeline will be hidden"
581 },
582 show_role: %Schema{
583 allOf: [BooleanLike],
584 nullable: true,
585 description: "user's role (e.g admin, moderator) will be exposed to anyone in the
586 API"
587 },
588 default_scope: VisibilityScope,
589 pleroma_settings_store: %Schema{
590 type: :object,
591 nullable: true,
592 description: "Opaque user settings to be saved on the backend."
593 },
594 skip_thread_containment: %Schema{
595 allOf: [BooleanLike],
596 nullable: true,
597 description: "Skip filtering out broken threads"
598 },
599 allow_following_move: %Schema{
600 allOf: [BooleanLike],
601 nullable: true,
602 description: "Allows automatically follow moved following accounts"
603 },
604 pleroma_background_image: %Schema{
605 type: :string,
606 nullable: true,
607 description: "Sets the background image of the user.",
608 format: :binary
609 },
610 discoverable: %Schema{
611 allOf: [BooleanLike],
612 nullable: true,
613 description:
614 "Discovery of this account in search results and other services is allowed."
615 },
616 actor_type: ActorType
617 },
618 example: %{
619 bot: false,
620 display_name: "cofe",
621 note: "foobar",
622 fields_attributes: [%{name: "foo", value: "bar"}],
623 no_rich_text: false,
624 hide_followers: true,
625 hide_follows: false,
626 hide_followers_count: false,
627 hide_follows_count: false,
628 hide_favorites: false,
629 show_role: false,
630 default_scope: "private",
631 pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
632 skip_thread_containment: false,
633 allow_following_move: false,
634 discoverable: false,
635 actor_type: "Person"
636 }
637 }
638 end
639
640 def array_of_accounts do
641 %Schema{
642 title: "ArrayOfAccounts",
643 type: :array,
644 items: Account,
645 example: [Account.schema().example]
646 }
647 end
648
649 defp array_of_relationships do
650 %Schema{
651 title: "ArrayOfRelationships",
652 description: "Response schema for account relationships",
653 type: :array,
654 items: AccountRelationship,
655 example: [
656 %{
657 "id" => "1",
658 "following" => true,
659 "showing_reblogs" => true,
660 "followed_by" => true,
661 "blocking" => false,
662 "blocked_by" => true,
663 "muting" => false,
664 "muting_notifications" => false,
665 "requested" => false,
666 "domain_blocking" => false,
667 "subscribing" => false,
668 "endorsed" => true
669 },
670 %{
671 "id" => "2",
672 "following" => true,
673 "showing_reblogs" => true,
674 "followed_by" => true,
675 "blocking" => false,
676 "blocked_by" => true,
677 "muting" => true,
678 "muting_notifications" => false,
679 "requested" => true,
680 "domain_blocking" => false,
681 "subscribing" => false,
682 "endorsed" => false
683 },
684 %{
685 "id" => "3",
686 "following" => true,
687 "showing_reblogs" => true,
688 "followed_by" => true,
689 "blocking" => true,
690 "blocked_by" => false,
691 "muting" => true,
692 "muting_notifications" => false,
693 "requested" => false,
694 "domain_blocking" => true,
695 "subscribing" => true,
696 "endorsed" => false
697 }
698 ]
699 }
700 end
701
702 defp follow_by_uri_request do
703 %Schema{
704 title: "AccountFollowsRequest",
705 description: "POST body for muting an account",
706 type: :object,
707 properties: %{
708 uri: %Schema{type: :string, nullable: true, format: :uri}
709 },
710 required: [:uri]
711 }
712 end
713
714 defp mute_request do
715 %Schema{
716 title: "AccountMuteRequest",
717 description: "POST body for muting an account",
718 type: :object,
719 properties: %{
720 notifications: %Schema{
721 allOf: [BooleanLike],
722 nullable: true,
723 description: "Mute notifications in addition to statuses? Defaults to true.",
724 default: true
725 }
726 },
727 example: %{
728 "notifications" => true
729 }
730 }
731 end
732
733 defp array_of_lists do
734 %Schema{
735 title: "ArrayOfLists",
736 description: "Response schema for lists",
737 type: :array,
738 items: List,
739 example: [
740 %{"id" => "123", "title" => "my list"},
741 %{"id" => "1337", "title" => "anotehr list"}
742 ]
743 }
744 end
745
746 defp array_of_statuses do
747 %Schema{
748 title: "ArrayOfStatuses",
749 type: :array,
750 items: Status
751 }
752 end
753
754 defp attribute_field do
755 %Schema{
756 title: "AccountAttributeField",
757 description: "Request schema for account custom fields",
758 type: :object,
759 properties: %{
760 name: %Schema{type: :string},
761 value: %Schema{type: :string}
762 },
763 required: [:name, :value],
764 example: %{
765 "name" => "Website",
766 "value" => "https://pleroma.com"
767 }
768 }
769 end
770 end