Pleroma.Web.get_api_routes/0 --> Pleroma.Web.Router.get_api_routes/0
[akkoma] / lib / pleroma / web / api_spec / operations / status_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.StatusOperation do
6 alias OpenApiSpex.Operation
7 alias OpenApiSpex.Schema
8 alias Pleroma.Web.ApiSpec.AccountOperation
9 alias Pleroma.Web.ApiSpec.Schemas.ApiError
10 alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
11 alias Pleroma.Web.ApiSpec.Schemas.FlakeID
12 alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus
13 alias Pleroma.Web.ApiSpec.Schemas.Status
14 alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
15
16 import Pleroma.Web.ApiSpec.Helpers
17
18 def open_api_operation(action) do
19 operation = String.to_existing_atom("#{action}_operation")
20 apply(__MODULE__, operation, [])
21 end
22
23 def index_operation do
24 %Operation{
25 tags: ["Retrieve status information"],
26 summary: "Multiple statuses",
27 security: [%{"oAuth" => ["read:statuses"]}],
28 parameters: [
29 Operation.parameter(
30 :ids,
31 :query,
32 %Schema{type: :array, items: FlakeID},
33 "Array of status IDs"
34 ),
35 Operation.parameter(
36 :with_muted,
37 :query,
38 BooleanLike,
39 "Include reactions from muted acccounts."
40 )
41 ],
42 operationId: "StatusController.index",
43 responses: %{
44 200 => Operation.response("Array of Status", "application/json", array_of_statuses())
45 }
46 }
47 end
48
49 def create_operation do
50 %Operation{
51 tags: ["Status actions"],
52 summary: "Publish new status",
53 security: [%{"oAuth" => ["write:statuses"]}],
54 description: "Post a new status",
55 operationId: "StatusController.create",
56 requestBody: request_body("Parameters", create_request(), required: true),
57 responses: %{
58 200 =>
59 Operation.response(
60 "Status. When `scheduled_at` is present, ScheduledStatus is returned instead",
61 "application/json",
62 %Schema{oneOf: [Status, ScheduledStatus]}
63 ),
64 422 => Operation.response("Bad Request / MRF Rejection", "application/json", ApiError)
65 }
66 }
67 end
68
69 def show_operation do
70 %Operation{
71 tags: ["Retrieve status information"],
72 summary: "Status",
73 description: "View information about a status",
74 operationId: "StatusController.show",
75 security: [%{"oAuth" => ["read:statuses"]}],
76 parameters: [
77 id_param(),
78 Operation.parameter(
79 :with_muted,
80 :query,
81 BooleanLike,
82 "Include reactions from muted acccounts."
83 )
84 ],
85 responses: %{
86 200 => status_response(),
87 404 => Operation.response("Not Found", "application/json", ApiError)
88 }
89 }
90 end
91
92 def delete_operation do
93 %Operation{
94 tags: ["Status actions"],
95 summary: "Delete",
96 security: [%{"oAuth" => ["write:statuses"]}],
97 description: "Delete one of your own statuses",
98 operationId: "StatusController.delete",
99 parameters: [id_param()],
100 responses: %{
101 200 => status_response(),
102 403 => Operation.response("Forbidden", "application/json", ApiError),
103 404 => Operation.response("Not Found", "application/json", ApiError)
104 }
105 }
106 end
107
108 def reblog_operation do
109 %Operation{
110 tags: ["Status actions"],
111 summary: "Reblog",
112 security: [%{"oAuth" => ["write:statuses"]}],
113 description: "Share a status",
114 operationId: "StatusController.reblog",
115 parameters: [id_param()],
116 requestBody:
117 request_body("Parameters", %Schema{
118 type: :object,
119 properties: %{
120 visibility: %Schema{allOf: [VisibilityScope]}
121 }
122 }),
123 responses: %{
124 200 => status_response(),
125 404 => Operation.response("Not Found", "application/json", ApiError)
126 }
127 }
128 end
129
130 def unreblog_operation do
131 %Operation{
132 tags: ["Status actions"],
133 summary: "Undo reblog",
134 security: [%{"oAuth" => ["write:statuses"]}],
135 description: "Undo a reshare of a status",
136 operationId: "StatusController.unreblog",
137 parameters: [id_param()],
138 responses: %{
139 200 => status_response(),
140 404 => Operation.response("Not Found", "application/json", ApiError)
141 }
142 }
143 end
144
145 def favourite_operation do
146 %Operation{
147 tags: ["Status actions"],
148 summary: "Favourite",
149 security: [%{"oAuth" => ["write:favourites"]}],
150 description: "Add a status to your favourites list",
151 operationId: "StatusController.favourite",
152 parameters: [id_param()],
153 responses: %{
154 200 => status_response(),
155 404 => Operation.response("Not Found", "application/json", ApiError)
156 }
157 }
158 end
159
160 def unfavourite_operation do
161 %Operation{
162 tags: ["Status actions"],
163 summary: "Undo favourite",
164 security: [%{"oAuth" => ["write:favourites"]}],
165 description: "Remove a status from your favourites list",
166 operationId: "StatusController.unfavourite",
167 parameters: [id_param()],
168 responses: %{
169 200 => status_response(),
170 404 => Operation.response("Not Found", "application/json", ApiError)
171 }
172 }
173 end
174
175 def pin_operation do
176 %Operation{
177 tags: ["Status actions"],
178 summary: "Pin to profile",
179 security: [%{"oAuth" => ["write:accounts"]}],
180 description: "Feature one of your own public statuses at the top of your profile",
181 operationId: "StatusController.pin",
182 parameters: [id_param()],
183 responses: %{
184 200 => status_response(),
185 400 => Operation.response("Error", "application/json", ApiError)
186 }
187 }
188 end
189
190 def unpin_operation do
191 %Operation{
192 tags: ["Status actions"],
193 summary: "Unpin from profile",
194 security: [%{"oAuth" => ["write:accounts"]}],
195 description: "Unfeature a status from the top of your profile",
196 operationId: "StatusController.unpin",
197 parameters: [id_param()],
198 responses: %{
199 200 => status_response(),
200 400 => Operation.response("Error", "application/json", ApiError)
201 }
202 }
203 end
204
205 def bookmark_operation do
206 %Operation{
207 tags: ["Status actions"],
208 summary: "Bookmark",
209 security: [%{"oAuth" => ["write:bookmarks"]}],
210 description: "Privately bookmark a status",
211 operationId: "StatusController.bookmark",
212 parameters: [id_param()],
213 responses: %{
214 200 => status_response()
215 }
216 }
217 end
218
219 def unbookmark_operation do
220 %Operation{
221 tags: ["Status actions"],
222 summary: "Undo bookmark",
223 security: [%{"oAuth" => ["write:bookmarks"]}],
224 description: "Remove a status from your private bookmarks",
225 operationId: "StatusController.unbookmark",
226 parameters: [id_param()],
227 responses: %{
228 200 => status_response()
229 }
230 }
231 end
232
233 def mute_conversation_operation do
234 %Operation{
235 tags: ["Status actions"],
236 summary: "Mute conversation",
237 security: [%{"oAuth" => ["write:mutes"]}],
238 description: "Do not receive notifications for the thread that this status is part of.",
239 operationId: "StatusController.mute_conversation",
240 requestBody:
241 request_body("Parameters", %Schema{
242 type: :object,
243 properties: %{
244 expires_in: %Schema{
245 type: :integer,
246 nullable: true,
247 description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
248 default: 0
249 }
250 }
251 }),
252 parameters: [
253 id_param(),
254 Operation.parameter(
255 :expires_in,
256 :query,
257 %Schema{type: :integer, default: 0},
258 "Expire the mute in `expires_in` seconds. Default 0 for infinity"
259 )
260 ],
261 responses: %{
262 200 => status_response(),
263 400 => Operation.response("Error", "application/json", ApiError)
264 }
265 }
266 end
267
268 def unmute_conversation_operation do
269 %Operation{
270 tags: ["Status actions"],
271 summary: "Unmute conversation",
272 security: [%{"oAuth" => ["write:mutes"]}],
273 description:
274 "Start receiving notifications again for the thread that this status is part of",
275 operationId: "StatusController.unmute_conversation",
276 parameters: [id_param()],
277 responses: %{
278 200 => status_response(),
279 400 => Operation.response("Error", "application/json", ApiError)
280 }
281 }
282 end
283
284 def card_operation do
285 %Operation{
286 tags: ["Retrieve status information"],
287 deprecated: true,
288 summary: "Preview card",
289 description: "Deprecated in favor of card property inlined on Status entity",
290 operationId: "StatusController.card",
291 parameters: [id_param()],
292 security: [%{"oAuth" => ["read:statuses"]}],
293 responses: %{
294 200 =>
295 Operation.response("Card", "application/json", %Schema{
296 type: :object,
297 nullable: true,
298 properties: %{
299 type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
300 provider_name: %Schema{type: :string, nullable: true},
301 provider_url: %Schema{type: :string, format: :uri},
302 url: %Schema{type: :string, format: :uri},
303 image: %Schema{type: :string, nullable: true, format: :uri},
304 title: %Schema{type: :string},
305 description: %Schema{type: :string}
306 }
307 })
308 }
309 }
310 end
311
312 def favourited_by_operation do
313 %Operation{
314 tags: ["Retrieve status information"],
315 summary: "Favourited by",
316 description: "View who favourited a given status",
317 operationId: "StatusController.favourited_by",
318 security: [%{"oAuth" => ["read:accounts"]}],
319 parameters: [id_param()],
320 responses: %{
321 200 =>
322 Operation.response(
323 "Array of Accounts",
324 "application/json",
325 AccountOperation.array_of_accounts()
326 ),
327 404 => Operation.response("Not Found", "application/json", ApiError)
328 }
329 }
330 end
331
332 def reblogged_by_operation do
333 %Operation{
334 tags: ["Retrieve status information"],
335 summary: "Reblogged by",
336 description: "View who reblogged a given status",
337 operationId: "StatusController.reblogged_by",
338 security: [%{"oAuth" => ["read:accounts"]}],
339 parameters: [id_param()],
340 responses: %{
341 200 =>
342 Operation.response(
343 "Array of Accounts",
344 "application/json",
345 AccountOperation.array_of_accounts()
346 ),
347 404 => Operation.response("Not Found", "application/json", ApiError)
348 }
349 }
350 end
351
352 def context_operation do
353 %Operation{
354 tags: ["Retrieve status information"],
355 summary: "Parent and child statuses",
356 description: "View statuses above and below this status in the thread",
357 operationId: "StatusController.context",
358 security: [%{"oAuth" => ["read:statuses"]}],
359 parameters: [id_param()],
360 responses: %{
361 200 => Operation.response("Context", "application/json", context())
362 }
363 }
364 end
365
366 def favourites_operation do
367 %Operation{
368 tags: ["Timelines"],
369 summary: "Favourited statuses",
370 description:
371 "Statuses the user has favourited. Please note that you have to use the link headers to paginate this. You can not build the query parameters yourself.",
372 operationId: "StatusController.favourites",
373 parameters: pagination_params(),
374 security: [%{"oAuth" => ["read:favourites"]}],
375 responses: %{
376 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
377 }
378 }
379 end
380
381 def bookmarks_operation do
382 %Operation{
383 tags: ["Timelines"],
384 summary: "Bookmarked statuses",
385 description: "Statuses the user has bookmarked",
386 operationId: "StatusController.bookmarks",
387 parameters: pagination_params(),
388 security: [%{"oAuth" => ["read:bookmarks"]}],
389 responses: %{
390 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
391 }
392 }
393 end
394
395 def array_of_statuses do
396 %Schema{type: :array, items: Status, example: [Status.schema().example]}
397 end
398
399 defp create_request do
400 %Schema{
401 title: "StatusCreateRequest",
402 type: :object,
403 properties: %{
404 status: %Schema{
405 type: :string,
406 nullable: true,
407 description:
408 "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
409 },
410 media_ids: %Schema{
411 nullable: true,
412 type: :array,
413 items: %Schema{type: :string},
414 description: "Array of Attachment ids to be attached as media."
415 },
416 poll: poll_params(),
417 in_reply_to_id: %Schema{
418 nullable: true,
419 allOf: [FlakeID],
420 description: "ID of the status being replied to, if status is a reply"
421 },
422 sensitive: %Schema{
423 allOf: [BooleanLike],
424 nullable: true,
425 description: "Mark status and attached media as sensitive?"
426 },
427 spoiler_text: %Schema{
428 type: :string,
429 nullable: true,
430 description:
431 "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
432 },
433 scheduled_at: %Schema{
434 type: :string,
435 format: :"date-time",
436 nullable: true,
437 description:
438 "ISO 8601 Datetime at which to schedule a status. Providing this paramter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future."
439 },
440 language: %Schema{
441 type: :string,
442 nullable: true,
443 description: "ISO 639 language code for this status."
444 },
445 # Pleroma-specific properties:
446 preview: %Schema{
447 allOf: [BooleanLike],
448 nullable: true,
449 description:
450 "If set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example"
451 },
452 content_type: %Schema{
453 type: :string,
454 nullable: true,
455 description:
456 "The MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint."
457 },
458 to: %Schema{
459 type: :array,
460 nullable: true,
461 items: %Schema{type: :string},
462 description:
463 "A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply"
464 },
465 visibility: %Schema{
466 nullable: true,
467 anyOf: [
468 VisibilityScope,
469 %Schema{type: :string, description: "`list:LIST_ID`", example: "LIST:123"}
470 ],
471 description:
472 "Visibility of the posted status. Besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`"
473 },
474 expires_in: %Schema{
475 nullable: true,
476 type: :integer,
477 description:
478 "The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour."
479 },
480 in_reply_to_conversation_id: %Schema{
481 nullable: true,
482 type: :string,
483 description:
484 "Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`."
485 }
486 },
487 example: %{
488 "status" => "What time is it?",
489 "sensitive" => "false",
490 "poll" => %{
491 "options" => ["Cofe", "Adventure"],
492 "expires_in" => 420
493 }
494 }
495 }
496 end
497
498 def poll_params do
499 %Schema{
500 nullable: true,
501 type: :object,
502 required: [:options, :expires_in],
503 properties: %{
504 options: %Schema{
505 type: :array,
506 items: %Schema{type: :string},
507 description: "Array of possible answers. Must be provided with `poll[expires_in]`."
508 },
509 expires_in: %Schema{
510 type: :integer,
511 nullable: true,
512 description:
513 "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
514 },
515 multiple: %Schema{
516 allOf: [BooleanLike],
517 nullable: true,
518 description: "Allow multiple choices?"
519 },
520 hide_totals: %Schema{
521 allOf: [BooleanLike],
522 nullable: true,
523 description: "Hide vote counts until the poll ends?"
524 }
525 }
526 }
527 end
528
529 def id_param do
530 Operation.parameter(:id, :path, FlakeID, "Status ID",
531 example: "9umDrYheeY451cQnEe",
532 required: true
533 )
534 end
535
536 defp status_response do
537 Operation.response("Status", "application/json", Status)
538 end
539
540 defp context do
541 %Schema{
542 title: "StatusContext",
543 description:
544 "Represents the tree around a given status. Used for reconstructing threads of statuses.",
545 type: :object,
546 required: [:ancestors, :descendants],
547 properties: %{
548 ancestors: array_of_statuses(),
549 descendants: array_of_statuses()
550 },
551 example: %{
552 "ancestors" => [Status.schema().example],
553 "descendants" => [Status.schema().example]
554 }
555 }
556 end
557 end