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