Merge branch 'fix/configdb-error' into 'develop'
[akkoma] / lib / pleroma / web / api_spec / operations / account_operation.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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: ["Account credentials"],
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: ["Account credentials"],
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: ["Account credentials"],
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_credentials_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: ["Retrieve account information"],
75 summary: "Relationship with current account",
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: ["Retrieve account information"],
99 summary: "Account",
100 operationId: "AccountController.show",
101 description: "View information about a profile.",
102 parameters: [
103 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
104 with_relationships_param()
105 ],
106 responses: %{
107 200 => Operation.response("Account", "application/json", Account),
108 401 => Operation.response("Error", "application/json", ApiError),
109 404 => Operation.response("Error", "application/json", ApiError)
110 }
111 }
112 end
113
114 def statuses_operation do
115 %Operation{
116 summary: "Statuses",
117 tags: ["Retrieve account information"],
118 operationId: "AccountController.statuses",
119 description:
120 "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)",
121 parameters:
122 [
123 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
124 Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
125 Operation.parameter(:tagged, :query, :string, "With tag"),
126 Operation.parameter(
127 :only_media,
128 :query,
129 BooleanLike,
130 "Include only statuses with media attached"
131 ),
132 Operation.parameter(
133 :with_muted,
134 :query,
135 BooleanLike,
136 "Include statuses from muted accounts."
137 ),
138 Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
139 Operation.parameter(:exclude_replies, :query, BooleanLike, "Exclude replies"),
140 Operation.parameter(
141 :exclude_visibilities,
142 :query,
143 %Schema{type: :array, items: VisibilityScope},
144 "Exclude visibilities"
145 ),
146 Operation.parameter(
147 :with_muted,
148 :query,
149 BooleanLike,
150 "Include reactions from muted accounts."
151 )
152 ] ++ pagination_params(),
153 responses: %{
154 200 => Operation.response("Statuses", "application/json", array_of_statuses()),
155 401 => Operation.response("Error", "application/json", ApiError),
156 404 => Operation.response("Error", "application/json", ApiError)
157 }
158 }
159 end
160
161 def followers_operation do
162 %Operation{
163 tags: ["Retrieve account information"],
164 summary: "Followers",
165 operationId: "AccountController.followers",
166 security: [%{"oAuth" => ["read:accounts"]}],
167 description:
168 "Accounts which follow the given account, if network is not hidden by the account owner.",
169 parameters: [
170 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
171 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
172 with_relationships_param() | pagination_params()
173 ],
174 responses: %{
175 200 => Operation.response("Accounts", "application/json", array_of_accounts())
176 }
177 }
178 end
179
180 def following_operation do
181 %Operation{
182 tags: ["Retrieve account information"],
183 summary: "Following",
184 operationId: "AccountController.following",
185 security: [%{"oAuth" => ["read:accounts"]}],
186 description:
187 "Accounts which the given account is following, if network is not hidden by the account owner.",
188 parameters: [
189 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
190 Operation.parameter(:id, :query, :string, "ID of the resource owner"),
191 with_relationships_param() | pagination_params()
192 ],
193 responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
194 }
195 end
196
197 def lists_operation do
198 %Operation{
199 tags: ["Retrieve account information"],
200 summary: "Lists containing this account",
201 operationId: "AccountController.lists",
202 security: [%{"oAuth" => ["read:lists"]}],
203 description: "User lists that you have added this account to.",
204 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
205 responses: %{200 => Operation.response("Lists", "application/json", array_of_lists())}
206 }
207 end
208
209 def follow_operation do
210 %Operation{
211 tags: ["Account actions"],
212 summary: "Follow",
213 operationId: "AccountController.follow",
214 security: [%{"oAuth" => ["follow", "write:follows"]}],
215 description: "Follow the given account",
216 parameters: [
217 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
218 ],
219 requestBody:
220 request_body(
221 "Parameters",
222 %Schema{
223 type: :object,
224 properties: %{
225 reblogs: %Schema{
226 type: :boolean,
227 description: "Receive this account's reblogs in home timeline? Defaults to true.",
228 default: true
229 }
230 }
231 },
232 required: false
233 ),
234 responses: %{
235 200 => Operation.response("Relationship", "application/json", AccountRelationship),
236 400 => Operation.response("Error", "application/json", ApiError),
237 404 => Operation.response("Error", "application/json", ApiError)
238 }
239 }
240 end
241
242 def unfollow_operation do
243 %Operation{
244 tags: ["Account actions"],
245 summary: "Unfollow",
246 operationId: "AccountController.unfollow",
247 security: [%{"oAuth" => ["follow", "write:follows"]}],
248 description: "Unfollow the given account",
249 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
250 responses: %{
251 200 => Operation.response("Relationship", "application/json", AccountRelationship),
252 400 => Operation.response("Error", "application/json", ApiError),
253 404 => Operation.response("Error", "application/json", ApiError)
254 }
255 }
256 end
257
258 def mute_operation do
259 %Operation{
260 tags: ["Account actions"],
261 summary: "Mute",
262 operationId: "AccountController.mute",
263 security: [%{"oAuth" => ["follow", "write:mutes"]}],
264 requestBody: request_body("Parameters", mute_request()),
265 description:
266 "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).",
267 parameters: [
268 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
269 Operation.parameter(
270 :notifications,
271 :query,
272 %Schema{allOf: [BooleanLike], default: true},
273 "Mute notifications in addition to statuses? Defaults to `true`."
274 ),
275 Operation.parameter(
276 :expires_in,
277 :query,
278 %Schema{type: :integer, default: 0},
279 "Expire the mute in `expires_in` seconds. Default 0 for infinity"
280 )
281 ],
282 responses: %{
283 200 => Operation.response("Relationship", "application/json", AccountRelationship)
284 }
285 }
286 end
287
288 def unmute_operation do
289 %Operation{
290 tags: ["Account actions"],
291 summary: "Unmute",
292 operationId: "AccountController.unmute",
293 security: [%{"oAuth" => ["follow", "write:mutes"]}],
294 description: "Unmute the given account.",
295 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
296 responses: %{
297 200 => Operation.response("Relationship", "application/json", AccountRelationship)
298 }
299 }
300 end
301
302 def block_operation do
303 %Operation{
304 tags: ["Account actions"],
305 summary: "Block",
306 operationId: "AccountController.block",
307 security: [%{"oAuth" => ["follow", "write:blocks"]}],
308 description:
309 "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
310 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
311 responses: %{
312 200 => Operation.response("Relationship", "application/json", AccountRelationship)
313 }
314 }
315 end
316
317 def unblock_operation do
318 %Operation{
319 tags: ["Account actions"],
320 summary: "Unblock",
321 operationId: "AccountController.unblock",
322 security: [%{"oAuth" => ["follow", "write:blocks"]}],
323 description: "Unblock the given account.",
324 parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
325 responses: %{
326 200 => Operation.response("Relationship", "application/json", AccountRelationship)
327 }
328 }
329 end
330
331 def follow_by_uri_operation do
332 %Operation{
333 tags: ["Account actions"],
334 summary: "Follow by URI",
335 operationId: "AccountController.follows",
336 security: [%{"oAuth" => ["follow", "write:follows"]}],
337 requestBody: request_body("Parameters", follow_by_uri_request(), required: true),
338 responses: %{
339 200 => Operation.response("Account", "application/json", AccountRelationship),
340 400 => Operation.response("Error", "application/json", ApiError),
341 404 => Operation.response("Error", "application/json", ApiError)
342 }
343 }
344 end
345
346 def mutes_operation do
347 %Operation{
348 tags: ["Blocks and mutes"],
349 summary: "Retrieve list of mutes",
350 operationId: "AccountController.mutes",
351 description: "Accounts the user has muted.",
352 security: [%{"oAuth" => ["follow", "read:mutes"]}],
353 parameters: [with_relationships_param() | pagination_params()],
354 responses: %{
355 200 => Operation.response("Accounts", "application/json", array_of_accounts())
356 }
357 }
358 end
359
360 def blocks_operation do
361 %Operation{
362 tags: ["Blocks and mutes"],
363 summary: "Retrieve list of blocks",
364 operationId: "AccountController.blocks",
365 description: "View your blocks. See also accounts/:id/{block,unblock}",
366 security: [%{"oAuth" => ["read:blocks"]}],
367 parameters: pagination_params(),
368 responses: %{
369 200 => Operation.response("Accounts", "application/json", array_of_accounts())
370 }
371 }
372 end
373
374 def endorsements_operation do
375 %Operation{
376 tags: ["Retrieve account information"],
377 summary: "Endorsements",
378 operationId: "AccountController.endorsements",
379 description: "Not implemented",
380 security: [%{"oAuth" => ["read:accounts"]}],
381 responses: %{
382 200 => empty_array_response()
383 }
384 }
385 end
386
387 def identity_proofs_operation do
388 %Operation{
389 tags: ["Retrieve account information"],
390 summary: "Identity proofs",
391 operationId: "AccountController.identity_proofs",
392 # Validators complains about unused path params otherwise
393 parameters: [
394 %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
395 ],
396 description: "Not implemented",
397 responses: %{
398 200 => empty_array_response()
399 }
400 }
401 end
402
403 defp create_request do
404 %Schema{
405 title: "AccountCreateRequest",
406 description: "POST body for creating an account",
407 type: :object,
408 required: [:username, :password, :agreement],
409 properties: %{
410 reason: %Schema{
411 type: :string,
412 nullable: true,
413 description:
414 "Text that will be reviewed by moderators if registrations require manual approval"
415 },
416 username: %Schema{type: :string, description: "The desired username for the account"},
417 email: %Schema{
418 type: :string,
419 nullable: true,
420 description:
421 "The email address to be used for login. Required when `account_activation_required` is enabled.",
422 format: :email
423 },
424 password: %Schema{
425 type: :string,
426 description: "The password to be used for login",
427 format: :password
428 },
429 agreement: %Schema{
430 allOf: [BooleanLike],
431 description:
432 "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."
433 },
434 locale: %Schema{
435 type: :string,
436 nullable: true,
437 description: "The language of the confirmation email that will be sent"
438 },
439 # Pleroma-specific properties:
440 fullname: %Schema{type: :string, nullable: true, description: "Full name"},
441 bio: %Schema{type: :string, description: "Bio", nullable: true, default: ""},
442 captcha_solution: %Schema{
443 type: :string,
444 nullable: true,
445 description: "Provider-specific captcha solution"
446 },
447 captcha_token: %Schema{
448 type: :string,
449 nullable: true,
450 description: "Provider-specific captcha token"
451 },
452 captcha_answer_data: %Schema{
453 type: :string,
454 nullable: true,
455 description: "Provider-specific captcha data"
456 },
457 token: %Schema{
458 type: :string,
459 nullable: true,
460 description: "Invite token required when the registrations aren't public"
461 }
462 },
463 example: %{
464 "username" => "cofe",
465 "email" => "cofe@example.com",
466 "password" => "secret",
467 "agreement" => "true",
468 "bio" => "☕️"
469 }
470 }
471 end
472
473 # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
474 defp create_response do
475 %Schema{
476 title: "AccountCreateResponse",
477 description: "Response schema for an account",
478 type: :object,
479 properties: %{
480 # The response when auto-login on create succeeds (token is issued):
481 token_type: %Schema{type: :string},
482 access_token: %Schema{type: :string},
483 refresh_token: %Schema{type: :string},
484 scope: %Schema{type: :string},
485 created_at: %Schema{type: :integer, format: :"date-time"},
486 me: %Schema{type: :string},
487 expires_in: %Schema{type: :integer},
488 #
489 # The response when registration succeeds but auto-login fails (no token):
490 identifier: %Schema{type: :string},
491 message: %Schema{type: :string}
492 },
493 # Note: example of successful registration with failed login response:
494 # example: %{
495 # "identifier" => "missing_confirmed_email",
496 # "message" => "You have been registered. Please check your email for further instructions."
497 # },
498 example: %{
499 "token_type" => "Bearer",
500 "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
501 "refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
502 "created_at" => 1_585_918_714,
503 "expires_in" => 600,
504 "scope" => "read write follow push",
505 "me" => "https://gensokyo.2hu/users/raymoo"
506 }
507 }
508 end
509
510 defp update_credentials_request do
511 %Schema{
512 title: "AccountUpdateCredentialsRequest",
513 description: "POST body for creating an account",
514 type: :object,
515 properties: %{
516 bot: %Schema{
517 allOf: [BooleanLike],
518 nullable: true,
519 description: "Whether the account has a bot flag."
520 },
521 display_name: %Schema{
522 type: :string,
523 nullable: true,
524 description: "The display name to use for the profile."
525 },
526 note: %Schema{type: :string, description: "The account bio."},
527 avatar: %Schema{
528 type: :string,
529 nullable: true,
530 description: "Avatar image encoded using multipart/form-data",
531 format: :binary
532 },
533 header: %Schema{
534 type: :string,
535 nullable: true,
536 description: "Header image encoded using multipart/form-data",
537 format: :binary
538 },
539 locked: %Schema{
540 allOf: [BooleanLike],
541 nullable: true,
542 description: "Whether manual approval of follow requests is required."
543 },
544 accepts_chat_messages: %Schema{
545 allOf: [BooleanLike],
546 nullable: true,
547 description: "Whether the user accepts receiving chat messages."
548 },
549 fields_attributes: %Schema{
550 nullable: true,
551 oneOf: [
552 %Schema{type: :array, items: attribute_field()},
553 %Schema{type: :object, additionalProperties: attribute_field()}
554 ]
555 },
556 # NOTE: `source` field is not supported
557 #
558 # source: %Schema{
559 # type: :object,
560 # properties: %{
561 # privacy: %Schema{type: :string},
562 # sensitive: %Schema{type: :boolean},
563 # language: %Schema{type: :string}
564 # }
565 # },
566
567 # Pleroma-specific fields
568 no_rich_text: %Schema{
569 allOf: [BooleanLike],
570 nullable: true,
571 description: "html tags are stripped from all statuses requested from the API"
572 },
573 hide_followers: %Schema{
574 allOf: [BooleanLike],
575 nullable: true,
576 description: "user's followers will be hidden"
577 },
578 hide_follows: %Schema{
579 allOf: [BooleanLike],
580 nullable: true,
581 description: "user's follows will be hidden"
582 },
583 hide_followers_count: %Schema{
584 allOf: [BooleanLike],
585 nullable: true,
586 description: "user's follower count will be hidden"
587 },
588 hide_follows_count: %Schema{
589 allOf: [BooleanLike],
590 nullable: true,
591 description: "user's follow count will be hidden"
592 },
593 hide_favorites: %Schema{
594 allOf: [BooleanLike],
595 nullable: true,
596 description: "user's favorites timeline will be hidden"
597 },
598 show_role: %Schema{
599 allOf: [BooleanLike],
600 nullable: true,
601 description: "user's role (e.g admin, moderator) will be exposed to anyone in the
602 API"
603 },
604 default_scope: VisibilityScope,
605 pleroma_settings_store: %Schema{
606 type: :object,
607 nullable: true,
608 description: "Opaque user settings to be saved on the backend."
609 },
610 skip_thread_containment: %Schema{
611 allOf: [BooleanLike],
612 nullable: true,
613 description: "Skip filtering out broken threads"
614 },
615 allow_following_move: %Schema{
616 allOf: [BooleanLike],
617 nullable: true,
618 description: "Allows automatically follow moved following accounts"
619 },
620 also_known_as: %Schema{
621 type: :array,
622 items: %Schema{type: :string},
623 nullable: true,
624 description: "List of alternate ActivityPub IDs"
625 },
626 pleroma_background_image: %Schema{
627 type: :string,
628 nullable: true,
629 description: "Sets the background image of the user.",
630 format: :binary
631 },
632 discoverable: %Schema{
633 allOf: [BooleanLike],
634 nullable: true,
635 description:
636 "Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
637 },
638 actor_type: ActorType
639 },
640 example: %{
641 bot: false,
642 display_name: "cofe",
643 note: "foobar",
644 fields_attributes: [%{name: "foo", value: "bar"}],
645 no_rich_text: false,
646 hide_followers: true,
647 hide_follows: false,
648 hide_followers_count: false,
649 hide_follows_count: false,
650 hide_favorites: false,
651 show_role: false,
652 default_scope: "private",
653 pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
654 skip_thread_containment: false,
655 allow_following_move: false,
656 also_known_as: ["https://foo.bar/users/foo"],
657 discoverable: false,
658 actor_type: "Person"
659 }
660 }
661 end
662
663 def array_of_accounts do
664 %Schema{
665 title: "ArrayOfAccounts",
666 type: :array,
667 items: Account,
668 example: [Account.schema().example]
669 }
670 end
671
672 defp array_of_relationships do
673 %Schema{
674 title: "ArrayOfRelationships",
675 description: "Response schema for account relationships",
676 type: :array,
677 items: AccountRelationship,
678 example: [
679 %{
680 "id" => "1",
681 "following" => true,
682 "showing_reblogs" => true,
683 "followed_by" => true,
684 "blocking" => false,
685 "blocked_by" => true,
686 "muting" => false,
687 "muting_notifications" => false,
688 "requested" => false,
689 "domain_blocking" => false,
690 "subscribing" => false,
691 "endorsed" => true
692 },
693 %{
694 "id" => "2",
695 "following" => true,
696 "showing_reblogs" => true,
697 "followed_by" => true,
698 "blocking" => false,
699 "blocked_by" => true,
700 "muting" => true,
701 "muting_notifications" => false,
702 "requested" => true,
703 "domain_blocking" => false,
704 "subscribing" => false,
705 "endorsed" => false
706 },
707 %{
708 "id" => "3",
709 "following" => true,
710 "showing_reblogs" => true,
711 "followed_by" => true,
712 "blocking" => true,
713 "blocked_by" => false,
714 "muting" => true,
715 "muting_notifications" => false,
716 "requested" => false,
717 "domain_blocking" => true,
718 "subscribing" => true,
719 "endorsed" => false
720 }
721 ]
722 }
723 end
724
725 defp follow_by_uri_request do
726 %Schema{
727 title: "AccountFollowsRequest",
728 description: "POST body for muting an account",
729 type: :object,
730 properties: %{
731 uri: %Schema{type: :string, nullable: true, format: :uri}
732 },
733 required: [:uri]
734 }
735 end
736
737 defp mute_request do
738 %Schema{
739 title: "AccountMuteRequest",
740 description: "POST body for muting an account",
741 type: :object,
742 properties: %{
743 notifications: %Schema{
744 allOf: [BooleanLike],
745 nullable: true,
746 description: "Mute notifications in addition to statuses? Defaults to true.",
747 default: true
748 },
749 expires_in: %Schema{
750 type: :integer,
751 nullable: true,
752 description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
753 default: 0
754 }
755 },
756 example: %{
757 "notifications" => true,
758 "expires_in" => 86_400
759 }
760 }
761 end
762
763 defp array_of_lists do
764 %Schema{
765 title: "ArrayOfLists",
766 description: "Response schema for lists",
767 type: :array,
768 items: List,
769 example: [
770 %{"id" => "123", "title" => "my list"},
771 %{"id" => "1337", "title" => "anotehr list"}
772 ]
773 }
774 end
775
776 defp array_of_statuses do
777 %Schema{
778 title: "ArrayOfStatuses",
779 type: :array,
780 items: Status
781 }
782 end
783
784 defp attribute_field do
785 %Schema{
786 title: "AccountAttributeField",
787 description: "Request schema for account custom fields",
788 type: :object,
789 properties: %{
790 name: %Schema{type: :string},
791 value: %Schema{type: :string}
792 },
793 required: [:name, :value],
794 example: %{
795 "name" => "Website",
796 "value" => "https://pleroma.com"
797 }
798 }
799 end
800 end