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