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