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