Merge branch 'openapi/search' 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.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 properties: %{
371 reason: %Schema{
372 type: :string,
373 description:
374 "Text that will be reviewed by moderators if registrations require manual approval"
375 },
376 username: %Schema{type: :string, description: "The desired username for the account"},
377 email: %Schema{
378 type: :string,
379 description:
380 "The email address to be used for login. Required when `account_activation_required` is enabled.",
381 format: :email
382 },
383 password: %Schema{
384 type: :string,
385 description: "The password to be used for login",
386 format: :password
387 },
388 agreement: %Schema{
389 type: :boolean,
390 description:
391 "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."
392 },
393 locale: %Schema{
394 type: :string,
395 description: "The language of the confirmation email that will be sent"
396 },
397 # Pleroma-specific properties:
398 fullname: %Schema{type: :string, description: "Full name"},
399 bio: %Schema{type: :string, description: "Bio", default: ""},
400 captcha_solution: %Schema{
401 type: :string,
402 description: "Provider-specific captcha solution"
403 },
404 captcha_token: %Schema{type: :string, description: "Provider-specific captcha token"},
405 captcha_answer_data: %Schema{type: :string, description: "Provider-specific captcha data"},
406 token: %Schema{
407 type: :string,
408 description: "Invite token required when the registrations aren't public"
409 }
410 },
411 required: [:username, :password, :agreement],
412 example: %{
413 "username" => "cofe",
414 "email" => "cofe@example.com",
415 "password" => "secret",
416 "agreement" => "true",
417 "bio" => "☕️"
418 }
419 }
420 end
421
422 defp create_response do
423 %Schema{
424 title: "AccountCreateResponse",
425 description: "Response schema for an account",
426 type: :object,
427 properties: %{
428 token_type: %Schema{type: :string},
429 access_token: %Schema{type: :string},
430 scope: %Schema{type: :array, items: %Schema{type: :string}},
431 created_at: %Schema{type: :integer, format: :"date-time"}
432 },
433 example: %{
434 "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
435 "created_at" => 1_585_918_714,
436 "scope" => ["read", "write", "follow", "push"],
437 "token_type" => "Bearer"
438 }
439 }
440 end
441
442 defp update_creadentials_request do
443 %Schema{
444 title: "AccountUpdateCredentialsRequest",
445 description: "POST body for creating an account",
446 type: :object,
447 properties: %{
448 bot: %Schema{
449 type: :boolean,
450 description: "Whether the account has a bot flag."
451 },
452 display_name: %Schema{
453 type: :string,
454 description: "The display name to use for the profile."
455 },
456 note: %Schema{type: :string, description: "The account bio."},
457 avatar: %Schema{
458 type: :string,
459 description: "Avatar image encoded using multipart/form-data",
460 format: :binary
461 },
462 header: %Schema{
463 type: :string,
464 description: "Header image encoded using multipart/form-data",
465 format: :binary
466 },
467 locked: %Schema{
468 type: :boolean,
469 description: "Whether manual approval of follow requests is required."
470 },
471 fields_attributes: %Schema{
472 oneOf: [
473 %Schema{type: :array, items: attribute_field()},
474 %Schema{type: :object, additionalProperties: %Schema{type: attribute_field()}}
475 ]
476 },
477 # NOTE: `source` field is not supported
478 #
479 # source: %Schema{
480 # type: :object,
481 # properties: %{
482 # privacy: %Schema{type: :string},
483 # sensitive: %Schema{type: :boolean},
484 # language: %Schema{type: :string}
485 # }
486 # },
487
488 # Pleroma-specific fields
489 no_rich_text: %Schema{
490 type: :boolean,
491 description: "html tags are stripped from all statuses requested from the API"
492 },
493 hide_followers: %Schema{type: :boolean, description: "user's followers will be hidden"},
494 hide_follows: %Schema{type: :boolean, description: "user's follows will be hidden"},
495 hide_followers_count: %Schema{
496 type: :boolean,
497 description: "user's follower count will be hidden"
498 },
499 hide_follows_count: %Schema{
500 type: :boolean,
501 description: "user's follow count will be hidden"
502 },
503 hide_favorites: %Schema{
504 type: :boolean,
505 description: "user's favorites timeline will be hidden"
506 },
507 show_role: %Schema{
508 type: :boolean,
509 description: "user's role (e.g admin, moderator) will be exposed to anyone in the
510 API"
511 },
512 default_scope: VisibilityScope,
513 pleroma_settings_store: %Schema{
514 type: :object,
515 description: "Opaque user settings to be saved on the backend."
516 },
517 skip_thread_containment: %Schema{
518 type: :boolean,
519 description: "Skip filtering out broken threads"
520 },
521 allow_following_move: %Schema{
522 type: :boolean,
523 description: "Allows automatically follow moved following accounts"
524 },
525 pleroma_background_image: %Schema{
526 type: :string,
527 description: "Sets the background image of the user.",
528 format: :binary
529 },
530 discoverable: %Schema{
531 type: :boolean,
532 description:
533 "Discovery of this account in search results and other services is allowed."
534 },
535 actor_type: ActorType
536 },
537 example: %{
538 bot: false,
539 display_name: "cofe",
540 note: "foobar",
541 fields_attributes: [%{name: "foo", value: "bar"}],
542 no_rich_text: false,
543 hide_followers: true,
544 hide_follows: false,
545 hide_followers_count: false,
546 hide_follows_count: false,
547 hide_favorites: false,
548 show_role: false,
549 default_scope: "private",
550 pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
551 skip_thread_containment: false,
552 allow_following_move: false,
553 discoverable: false,
554 actor_type: "Person"
555 }
556 }
557 end
558
559 def array_of_accounts do
560 %Schema{
561 title: "ArrayOfAccounts",
562 type: :array,
563 items: Account,
564 example: [Account.schema().example]
565 }
566 end
567
568 defp array_of_relationships do
569 %Schema{
570 title: "ArrayOfRelationships",
571 description: "Response schema for account relationships",
572 type: :array,
573 items: AccountRelationship,
574 example: [
575 %{
576 "id" => "1",
577 "following" => true,
578 "showing_reblogs" => true,
579 "followed_by" => true,
580 "blocking" => false,
581 "blocked_by" => true,
582 "muting" => false,
583 "muting_notifications" => false,
584 "requested" => false,
585 "domain_blocking" => false,
586 "subscribing" => false,
587 "endorsed" => true
588 },
589 %{
590 "id" => "2",
591 "following" => true,
592 "showing_reblogs" => true,
593 "followed_by" => true,
594 "blocking" => false,
595 "blocked_by" => true,
596 "muting" => true,
597 "muting_notifications" => false,
598 "requested" => true,
599 "domain_blocking" => false,
600 "subscribing" => false,
601 "endorsed" => false
602 },
603 %{
604 "id" => "3",
605 "following" => true,
606 "showing_reblogs" => true,
607 "followed_by" => true,
608 "blocking" => true,
609 "blocked_by" => false,
610 "muting" => true,
611 "muting_notifications" => false,
612 "requested" => false,
613 "domain_blocking" => true,
614 "subscribing" => true,
615 "endorsed" => false
616 }
617 ]
618 }
619 end
620
621 defp follow_by_uri_request do
622 %Schema{
623 title: "AccountFollowsRequest",
624 description: "POST body for muting an account",
625 type: :object,
626 properties: %{
627 uri: %Schema{type: :string, format: :uri}
628 },
629 required: [:uri]
630 }
631 end
632
633 defp mute_request do
634 %Schema{
635 title: "AccountMuteRequest",
636 description: "POST body for muting an account",
637 type: :object,
638 properties: %{
639 notifications: %Schema{
640 type: :boolean,
641 description: "Mute notifications in addition to statuses? Defaults to true.",
642 default: true
643 }
644 },
645 example: %{
646 "notifications" => true
647 }
648 }
649 end
650
651 defp array_of_lists do
652 %Schema{
653 title: "ArrayOfLists",
654 description: "Response schema for lists",
655 type: :array,
656 items: List,
657 example: [
658 %{"id" => "123", "title" => "my list"},
659 %{"id" => "1337", "title" => "anotehr list"}
660 ]
661 }
662 end
663
664 defp array_of_statuses do
665 %Schema{
666 title: "ArrayOfStatuses",
667 type: :array,
668 items: Status
669 }
670 end
671
672 defp attribute_field do
673 %Schema{
674 title: "AccountAttributeField",
675 description: "Request schema for account custom fields",
676 type: :object,
677 properties: %{
678 name: %Schema{type: :string},
679 value: %Schema{type: :string}
680 },
681 required: [:name, :value],
682 example: %{
683 "name" => "Website",
684 "value" => "https://pleroma.com"
685 }
686 }
687 end
688 end