Apply suggestion to lib/pleroma/web/api_spec/operations/status_operation.ex
[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:
225 "Do not receive notifications for the thread that this status is part of.",
226 operationId: "StatusController.mute_conversation",
227 parameters: [id_param()],
228 responses: %{
229 200 => status_response(),
230 400 => Operation.response("Error", "application/json", ApiError)
231 }
232 }
233 end
234
235 def unmute_conversation_operation do
236 %Operation{
237 tags: ["Statuses"],
238 summary: "Unmute conversation",
239 security: [%{"oAuth" => ["write:mutes"]}],
240 description:
241 "Start receiving notifications again for the thread that this status is part of",
242 operationId: "StatusController.unmute_conversation",
243 parameters: [id_param()],
244 responses: %{
245 200 => status_response(),
246 400 => Operation.response("Error", "application/json", ApiError)
247 }
248 }
249 end
250
251 def card_operation do
252 %Operation{
253 tags: ["Statuses"],
254 deprecated: true,
255 summary: "Preview card",
256 description: "Deprecated in favor of card property inlined on Status entity",
257 operationId: "StatusController.card",
258 parameters: [id_param()],
259 security: [%{"oAuth" => ["read:statuses"]}],
260 responses: %{
261 200 =>
262 Operation.response("Card", "application/json", %Schema{
263 type: :object,
264 nullable: true,
265 properties: %{
266 type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
267 provider_name: %Schema{type: :string, nullable: true},
268 provider_url: %Schema{type: :string, format: :uri},
269 url: %Schema{type: :string, format: :uri},
270 image: %Schema{type: :string, nullable: true, format: :uri},
271 title: %Schema{type: :string},
272 description: %Schema{type: :string}
273 }
274 })
275 }
276 }
277 end
278
279 def favourited_by_operation do
280 %Operation{
281 tags: ["Statuses"],
282 summary: "Favourited by",
283 description: "View who favourited a given status",
284 operationId: "StatusController.favourited_by",
285 security: [%{"oAuth" => ["read:accounts"]}],
286 parameters: [id_param()],
287 responses: %{
288 200 =>
289 Operation.response(
290 "Array of Accounts",
291 "application/json",
292 AccountOperation.array_of_accounts()
293 ),
294 404 => Operation.response("Not Found", "application/json", ApiError)
295 }
296 }
297 end
298
299 def reblogged_by_operation do
300 %Operation{
301 tags: ["Statuses"],
302 summary: "Boosted by",
303 description: "View who boosted a given status",
304 operationId: "StatusController.reblogged_by",
305 security: [%{"oAuth" => ["read:accounts"]}],
306 parameters: [id_param()],
307 responses: %{
308 200 =>
309 Operation.response(
310 "Array of Accounts",
311 "application/json",
312 AccountOperation.array_of_accounts()
313 ),
314 404 => Operation.response("Not Found", "application/json", ApiError)
315 }
316 }
317 end
318
319 def context_operation do
320 %Operation{
321 tags: ["Statuses"],
322 summary: "Parent and child statuses",
323 description: "View statuses above and below this status in the thread",
324 operationId: "StatusController.context",
325 security: [%{"oAuth" => ["read:statuses"]}],
326 parameters: [id_param()],
327 responses: %{
328 200 => Operation.response("Context", "application/json", context())
329 }
330 }
331 end
332
333 def favourites_operation do
334 %Operation{
335 tags: ["Statuses"],
336 summary: "Favourited statuses",
337 description: "Statuses the user has favourited",
338 operationId: "StatusController.favourites",
339 parameters: pagination_params(),
340 security: [%{"oAuth" => ["read:favourites"]}],
341 responses: %{
342 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
343 }
344 }
345 end
346
347 def bookmarks_operation do
348 %Operation{
349 tags: ["Statuses"],
350 summary: "Bookmarked statuses",
351 description: "Statuses the user has bookmarked",
352 operationId: "StatusController.bookmarks",
353 parameters: [
354 Operation.parameter(:with_relationships, :query, BooleanLike, "Include relationships")
355 | pagination_params()
356 ],
357 security: [%{"oAuth" => ["read:bookmarks"]}],
358 responses: %{
359 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
360 }
361 }
362 end
363
364 defp array_of_statuses do
365 %Schema{type: :array, items: Status, example: [Status.schema().example]}
366 end
367
368 defp create_request do
369 %Schema{
370 title: "StatusCreateRequest",
371 type: :object,
372 properties: %{
373 status: %Schema{
374 type: :string,
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 type: :array,
380 items: %Schema{type: :string},
381 description:
382 "Array of Attachment ids to be attached as media. If provided, `status` becomes optional, and `poll` cannot be used."
383 },
384 poll: %Schema{
385 type: :object,
386 required: [:options],
387 properties: %{
388 options: %Schema{
389 type: :array,
390 items: %Schema{type: :string},
391 description:
392 "Array of possible answers. If provided, `media_ids` cannot be used, and `poll[expires_in]` must be provided."
393 },
394 expires_in: %Schema{
395 type: :integer,
396 description:
397 "Duration the poll should be open, in seconds. If provided, `media_ids` cannot be used, and `poll[options]` must be provided."
398 },
399 multiple: %Schema{type: :boolean, description: "Allow multiple choices?"},
400 hide_totals: %Schema{
401 type: :boolean,
402 description: "Hide vote counts until the poll ends?"
403 }
404 }
405 },
406 in_reply_to_id: %Schema{
407 allOf: [FlakeID],
408 description: "ID of the status being replied to, if status is a reply"
409 },
410 sensitive: %Schema{
411 type: :boolean,
412 description: "Mark status and attached media as sensitive?"
413 },
414 spoiler_text: %Schema{
415 type: :string,
416 description:
417 "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
418 },
419 scheduled_at: %Schema{
420 type: :string,
421 format: :"date-time",
422 nullable: true,
423 description:
424 "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."
425 },
426 language: %Schema{type: :string, description: "ISO 639 language code for this status."},
427 # Pleroma-specific properties:
428 preview: %Schema{
429 type: :boolean,
430 description:
431 "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"
432 },
433 content_type: %Schema{
434 type: :string,
435 description:
436 "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."
437 },
438 to: %Schema{
439 type: :array,
440 items: %Schema{type: :string},
441 description:
442 "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"
443 },
444 visibility: %Schema{
445 anyOf: [
446 VisibilityScope,
447 %Schema{type: :string, description: "`list:LIST_ID`", example: "LIST:123"}
448 ],
449 description:
450 "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`"
451 },
452 expires_in: %Schema{
453 type: :integer,
454 description:
455 "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."
456 },
457 in_reply_to_conversation_id: %Schema{
458 type: :string,
459 description:
460 "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`."
461 }
462 },
463 example: %{
464 "status" => "What time is it?",
465 "sensitive" => "false",
466 "poll" => %{
467 "options" => ["Cofe", "Adventure"],
468 "expires_in" => 420
469 }
470 }
471 }
472 end
473
474 defp id_param do
475 Operation.parameter(:id, :path, FlakeID, "Status ID",
476 example: "9umDrYheeY451cQnEe",
477 required: true
478 )
479 end
480
481 defp status_response do
482 Operation.response("Status", "application/json", Status)
483 end
484
485 defp context do
486 %Schema{
487 title: "StatusContext",
488 description:
489 "Represents the tree around a given status. Used for reconstructing threads of statuses.",
490 type: :object,
491 required: [:ancestors, :descendants],
492 properties: %{
493 ancestors: array_of_statuses(),
494 descendants: array_of_statuses()
495 },
496 example: %{
497 "ancestors" => [Status.schema().example],
498 "descendants" => [Status.schema().example]
499 }
500 }
501 end
502 end