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