Merge branch 'develop' into 'remove-twitter-api'
[akkoma] / lib / pleroma / web / api_spec / operations / status_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.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: ["Statuses"],
26 summary: "Get multiple statuses by IDs",
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 ],
36 operationId: "StatusController.index",
37 responses: %{
38 200 => Operation.response("Array of Status", "application/json", array_of_statuses())
39 }
40 }
41 end
42
43 def create_operation do
44 %Operation{
45 tags: ["Statuses"],
46 summary: "Publish new status",
47 security: [%{"oAuth" => ["write:statuses"]}],
48 description: "Post a new status",
49 operationId: "StatusController.create",
50 requestBody: request_body("Parameters", create_request(), required: true),
51 responses: %{
52 200 =>
53 Operation.response(
54 "Status. When `scheduled_at` is present, ScheduledStatus is returned instead",
55 "application/json",
56 %Schema{oneOf: [Status, ScheduledStatus]}
57 ),
58 422 => Operation.response("Bad Request", "application/json", ApiError)
59 }
60 }
61 end
62
63 def show_operation do
64 %Operation{
65 tags: ["Statuses"],
66 summary: "View specific status",
67 description: "View information about a status",
68 operationId: "StatusController.show",
69 security: [%{"oAuth" => ["read:statuses"]}],
70 parameters: [id_param()],
71 responses: %{
72 200 => status_response(),
73 404 => Operation.response("Not Found", "application/json", ApiError)
74 }
75 }
76 end
77
78 def delete_operation do
79 %Operation{
80 tags: ["Statuses"],
81 summary: "Delete status",
82 security: [%{"oAuth" => ["write:statuses"]}],
83 description: "Delete one of your own statuses",
84 operationId: "StatusController.delete",
85 parameters: [id_param()],
86 responses: %{
87 200 => empty_object_response(),
88 403 => Operation.response("Forbidden", "application/json", ApiError),
89 404 => Operation.response("Not Found", "application/json", ApiError)
90 }
91 }
92 end
93
94 def reblog_operation do
95 %Operation{
96 tags: ["Statuses"],
97 summary: "Boost",
98 security: [%{"oAuth" => ["write:statuses"]}],
99 description: "Share a status",
100 operationId: "StatusController.reblog",
101 parameters: [id_param()],
102 requestBody:
103 request_body("Parameters", %Schema{
104 type: :object,
105 properties: %{
106 visibility: %Schema{allOf: [VisibilityScope], default: "public"}
107 }
108 }),
109 responses: %{
110 200 => status_response(),
111 404 => Operation.response("Not Found", "application/json", ApiError)
112 }
113 }
114 end
115
116 def unreblog_operation do
117 %Operation{
118 tags: ["Statuses"],
119 summary: "Undo boost",
120 security: [%{"oAuth" => ["write:statuses"]}],
121 description: "Undo a reshare of a status",
122 operationId: "StatusController.unreblog",
123 parameters: [id_param()],
124 responses: %{
125 200 => status_response(),
126 404 => Operation.response("Not Found", "application/json", ApiError)
127 }
128 }
129 end
130
131 def favourite_operation do
132 %Operation{
133 tags: ["Statuses"],
134 summary: "Favourite",
135 security: [%{"oAuth" => ["write:favourites"]}],
136 description: "Add a status to your favourites list",
137 operationId: "StatusController.favourite",
138 parameters: [id_param()],
139 responses: %{
140 200 => status_response(),
141 404 => Operation.response("Not Found", "application/json", ApiError)
142 }
143 }
144 end
145
146 def unfavourite_operation do
147 %Operation{
148 tags: ["Statuses"],
149 summary: "Undo favourite",
150 security: [%{"oAuth" => ["write:favourites"]}],
151 description: "Remove a status from your favourites list",
152 operationId: "StatusController.unfavourite",
153 parameters: [id_param()],
154 responses: %{
155 200 => status_response(),
156 404 => Operation.response("Not Found", "application/json", ApiError)
157 }
158 }
159 end
160
161 def pin_operation do
162 %Operation{
163 tags: ["Statuses"],
164 summary: "Pin to profile",
165 security: [%{"oAuth" => ["write:accounts"]}],
166 description: "Feature one of your own public statuses at the top of your profile",
167 operationId: "StatusController.pin",
168 parameters: [id_param()],
169 responses: %{
170 200 => status_response(),
171 400 => Operation.response("Error", "application/json", ApiError)
172 }
173 }
174 end
175
176 def unpin_operation do
177 %Operation{
178 tags: ["Statuses"],
179 summary: "Unpin to profile",
180 security: [%{"oAuth" => ["write:accounts"]}],
181 description: "Unfeature a status from the top of your profile",
182 operationId: "StatusController.unpin",
183 parameters: [id_param()],
184 responses: %{
185 200 => status_response(),
186 400 => Operation.response("Error", "application/json", ApiError)
187 }
188 }
189 end
190
191 def bookmark_operation do
192 %Operation{
193 tags: ["Statuses"],
194 summary: "Bookmark",
195 security: [%{"oAuth" => ["write:bookmarks"]}],
196 description: "Privately bookmark a status",
197 operationId: "StatusController.bookmark",
198 parameters: [id_param()],
199 responses: %{
200 200 => status_response()
201 }
202 }
203 end
204
205 def unbookmark_operation do
206 %Operation{
207 tags: ["Statuses"],
208 summary: "Undo bookmark",
209 security: [%{"oAuth" => ["write:bookmarks"]}],
210 description: "Remove a status from your private bookmarks",
211 operationId: "StatusController.unbookmark",
212 parameters: [id_param()],
213 responses: %{
214 200 => status_response()
215 }
216 }
217 end
218
219 def mute_conversation_operation do
220 %Operation{
221 tags: ["Statuses"],
222 summary: "Mute conversation",
223 security: [%{"oAuth" => ["write:mutes"]}],
224 description: "Do not receive notifications for the thread that this status is part of.",
225 operationId: "StatusController.mute_conversation",
226 parameters: [id_param()],
227 responses: %{
228 200 => status_response(),
229 400 => Operation.response("Error", "application/json", ApiError)
230 }
231 }
232 end
233
234 def unmute_conversation_operation do
235 %Operation{
236 tags: ["Statuses"],
237 summary: "Unmute conversation",
238 security: [%{"oAuth" => ["write:mutes"]}],
239 description:
240 "Start receiving notifications again for the thread that this status is part of",
241 operationId: "StatusController.unmute_conversation",
242 parameters: [id_param()],
243 responses: %{
244 200 => status_response(),
245 400 => Operation.response("Error", "application/json", ApiError)
246 }
247 }
248 end
249
250 def card_operation do
251 %Operation{
252 tags: ["Statuses"],
253 deprecated: true,
254 summary: "Preview card",
255 description: "Deprecated in favor of card property inlined on Status entity",
256 operationId: "StatusController.card",
257 parameters: [id_param()],
258 security: [%{"oAuth" => ["read:statuses"]}],
259 responses: %{
260 200 =>
261 Operation.response("Card", "application/json", %Schema{
262 type: :object,
263 nullable: true,
264 properties: %{
265 type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
266 provider_name: %Schema{type: :string, nullable: true},
267 provider_url: %Schema{type: :string, format: :uri},
268 url: %Schema{type: :string, format: :uri},
269 image: %Schema{type: :string, nullable: true, format: :uri},
270 title: %Schema{type: :string},
271 description: %Schema{type: :string}
272 }
273 })
274 }
275 }
276 end
277
278 def favourited_by_operation do
279 %Operation{
280 tags: ["Statuses"],
281 summary: "Favourited by",
282 description: "View who favourited a given status",
283 operationId: "StatusController.favourited_by",
284 security: [%{"oAuth" => ["read:accounts"]}],
285 parameters: [id_param()],
286 responses: %{
287 200 =>
288 Operation.response(
289 "Array of Accounts",
290 "application/json",
291 AccountOperation.array_of_accounts()
292 ),
293 404 => Operation.response("Not Found", "application/json", ApiError)
294 }
295 }
296 end
297
298 def reblogged_by_operation do
299 %Operation{
300 tags: ["Statuses"],
301 summary: "Boosted by",
302 description: "View who boosted a given status",
303 operationId: "StatusController.reblogged_by",
304 security: [%{"oAuth" => ["read:accounts"]}],
305 parameters: [id_param()],
306 responses: %{
307 200 =>
308 Operation.response(
309 "Array of Accounts",
310 "application/json",
311 AccountOperation.array_of_accounts()
312 ),
313 404 => Operation.response("Not Found", "application/json", ApiError)
314 }
315 }
316 end
317
318 def context_operation do
319 %Operation{
320 tags: ["Statuses"],
321 summary: "Parent and child statuses",
322 description: "View statuses above and below this status in the thread",
323 operationId: "StatusController.context",
324 security: [%{"oAuth" => ["read:statuses"]}],
325 parameters: [id_param()],
326 responses: %{
327 200 => Operation.response("Context", "application/json", context())
328 }
329 }
330 end
331
332 def favourites_operation do
333 %Operation{
334 tags: ["Statuses"],
335 summary: "Favourited statuses",
336 description: "Statuses the user has favourited",
337 operationId: "StatusController.favourites",
338 parameters: pagination_params(),
339 security: [%{"oAuth" => ["read:favourites"]}],
340 responses: %{
341 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
342 }
343 }
344 end
345
346 def bookmarks_operation do
347 %Operation{
348 tags: ["Statuses"],
349 summary: "Bookmarked statuses",
350 description: "Statuses the user has bookmarked",
351 operationId: "StatusController.bookmarks",
352 parameters: [
353 Operation.parameter(:with_relationships, :query, BooleanLike, "Include relationships")
354 | pagination_params()
355 ],
356 security: [%{"oAuth" => ["read:bookmarks"]}],
357 responses: %{
358 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
359 }
360 }
361 end
362
363 def array_of_statuses do
364 %Schema{type: :array, items: Status, example: [Status.schema().example]}
365 end
366
367 defp create_request do
368 %Schema{
369 title: "StatusCreateRequest",
370 type: :object,
371 properties: %{
372 status: %Schema{
373 type: :string,
374 nullable: true,
375 description:
376 "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
377 },
378 media_ids: %Schema{
379 nullable: true,
380 type: :array,
381 items: %Schema{type: :string},
382 description: "Array of Attachment ids to be attached as media."
383 },
384 poll: %Schema{
385 nullable: true,
386 type: :object,
387 required: [:options],
388 properties: %{
389 options: %Schema{
390 type: :array,
391 items: %Schema{type: :string},
392 description: "Array of possible answers. Must be provided with `poll[expires_in]`."
393 },
394 expires_in: %Schema{
395 type: :integer,
396 nullable: true,
397 description:
398 "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
399 },
400 multiple: %Schema{
401 type: :boolean,
402 nullable: true,
403 description: "Allow multiple choices?"
404 },
405 hide_totals: %Schema{
406 type: :boolean,
407 nullable: true,
408 description: "Hide vote counts until the poll ends?"
409 }
410 }
411 },
412 in_reply_to_id: %Schema{
413 nullable: true,
414 allOf: [FlakeID],
415 description: "ID of the status being replied to, if status is a reply"
416 },
417 sensitive: %Schema{
418 type: :boolean,
419 nullable: true,
420 description: "Mark status and attached media as sensitive?"
421 },
422 spoiler_text: %Schema{
423 type: :string,
424 nullable: true,
425 description:
426 "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
427 },
428 scheduled_at: %Schema{
429 type: :string,
430 format: :"date-time",
431 nullable: true,
432 description:
433 "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."
434 },
435 language: %Schema{
436 type: :string,
437 nullable: true,
438 description: "ISO 639 language code for this status."
439 },
440 # Pleroma-specific properties:
441 preview: %Schema{
442 type: :boolean,
443 nullable: true,
444 description:
445 "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"
446 },
447 content_type: %Schema{
448 type: :string,
449 nullable: true,
450 description:
451 "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."
452 },
453 to: %Schema{
454 type: :array,
455 nullable: true,
456 items: %Schema{type: :string},
457 description:
458 "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"
459 },
460 visibility: %Schema{
461 nullable: true,
462 anyOf: [
463 VisibilityScope,
464 %Schema{type: :string, description: "`list:LIST_ID`", example: "LIST:123"}
465 ],
466 description:
467 "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`"
468 },
469 expires_in: %Schema{
470 nullable: true,
471 type: :integer,
472 description:
473 "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."
474 },
475 in_reply_to_conversation_id: %Schema{
476 nullable: true,
477 type: :string,
478 description:
479 "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`."
480 }
481 },
482 example: %{
483 "status" => "What time is it?",
484 "sensitive" => "false",
485 "poll" => %{
486 "options" => ["Cofe", "Adventure"],
487 "expires_in" => 420
488 }
489 }
490 }
491 end
492
493 defp id_param do
494 Operation.parameter(:id, :path, FlakeID, "Status ID",
495 example: "9umDrYheeY451cQnEe",
496 required: true
497 )
498 end
499
500 defp status_response do
501 Operation.response("Status", "application/json", Status)
502 end
503
504 defp context do
505 %Schema{
506 title: "StatusContext",
507 description:
508 "Represents the tree around a given status. Used for reconstructing threads of statuses.",
509 type: :object,
510 required: [:ancestors, :descendants],
511 properties: %{
512 ancestors: array_of_statuses(),
513 descendants: array_of_statuses()
514 },
515 example: %{
516 "ancestors" => [Status.schema().example],
517 "descendants" => [Status.schema().example]
518 }
519 }
520 end
521 end