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