Add `account_activation_required` to /api/v1/instance
[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: pagination_params(),
353 security: [%{"oAuth" => ["read:bookmarks"]}],
354 responses: %{
355 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
356 }
357 }
358 end
359
360 def array_of_statuses do
361 %Schema{type: :array, items: Status, example: [Status.schema().example]}
362 end
363
364 defp create_request do
365 %Schema{
366 title: "StatusCreateRequest",
367 type: :object,
368 properties: %{
369 status: %Schema{
370 type: :string,
371 nullable: true,
372 description:
373 "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
374 },
375 media_ids: %Schema{
376 nullable: true,
377 type: :array,
378 items: %Schema{type: :string},
379 description: "Array of Attachment ids to be attached as media."
380 },
381 poll: %Schema{
382 nullable: true,
383 type: :object,
384 required: [:options],
385 properties: %{
386 options: %Schema{
387 type: :array,
388 items: %Schema{type: :string},
389 description: "Array of possible answers. Must be provided with `poll[expires_in]`."
390 },
391 expires_in: %Schema{
392 type: :integer,
393 nullable: true,
394 description:
395 "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
396 },
397 multiple: %Schema{
398 allOf: [BooleanLike],
399 nullable: true,
400 description: "Allow multiple choices?"
401 },
402 hide_totals: %Schema{
403 allOf: [BooleanLike],
404 nullable: true,
405 description: "Hide vote counts until the poll ends?"
406 }
407 }
408 },
409 in_reply_to_id: %Schema{
410 nullable: true,
411 allOf: [FlakeID],
412 description: "ID of the status being replied to, if status is a reply"
413 },
414 sensitive: %Schema{
415 allOf: [BooleanLike],
416 nullable: true,
417 description: "Mark status and attached media as sensitive?"
418 },
419 spoiler_text: %Schema{
420 type: :string,
421 nullable: true,
422 description:
423 "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
424 },
425 scheduled_at: %Schema{
426 type: :string,
427 format: :"date-time",
428 nullable: true,
429 description:
430 "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."
431 },
432 language: %Schema{
433 type: :string,
434 nullable: true,
435 description: "ISO 639 language code for this status."
436 },
437 # Pleroma-specific properties:
438 preview: %Schema{
439 allOf: [BooleanLike],
440 nullable: true,
441 description:
442 "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"
443 },
444 content_type: %Schema{
445 type: :string,
446 nullable: true,
447 description:
448 "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."
449 },
450 to: %Schema{
451 type: :array,
452 nullable: true,
453 items: %Schema{type: :string},
454 description:
455 "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"
456 },
457 visibility: %Schema{
458 nullable: true,
459 anyOf: [
460 VisibilityScope,
461 %Schema{type: :string, description: "`list:LIST_ID`", example: "LIST:123"}
462 ],
463 description:
464 "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`"
465 },
466 expires_in: %Schema{
467 nullable: true,
468 type: :integer,
469 description:
470 "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."
471 },
472 in_reply_to_conversation_id: %Schema{
473 nullable: true,
474 type: :string,
475 description:
476 "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`."
477 }
478 },
479 example: %{
480 "status" => "What time is it?",
481 "sensitive" => "false",
482 "poll" => %{
483 "options" => ["Cofe", "Adventure"],
484 "expires_in" => 420
485 }
486 }
487 }
488 end
489
490 def id_param do
491 Operation.parameter(:id, :path, FlakeID, "Status ID",
492 example: "9umDrYheeY451cQnEe",
493 required: true
494 )
495 end
496
497 defp status_response do
498 Operation.response("Status", "application/json", Status)
499 end
500
501 defp context do
502 %Schema{
503 title: "StatusContext",
504 description:
505 "Represents the tree around a given status. Used for reconstructing threads of statuses.",
506 type: :object,
507 required: [:ancestors, :descendants],
508 properties: %{
509 ancestors: array_of_statuses(),
510 descendants: array_of_statuses()
511 },
512 example: %{
513 "ancestors" => [Status.schema().example],
514 "descendants" => [Status.schema().example]
515 }
516 }
517 end
518 end