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