# Pleroma: A lightweight social networking server # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ApiSpec.StreamingOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Response alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Helpers alias Pleroma.Web.ApiSpec.Schemas.Status @spec open_api_operation(atom) :: Operation.t() def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") apply(__MODULE__, operation, []) end @spec streaming_operation() :: Operation.t() def streaming_operation do %Operation{ tags: ["Timelines"], summary: "Establish streaming connection", description: "Receive statuses in real-time via WebSocket.", security: [%{"oAuth" => ["read:statuses", "read:notifications"]}], parameters: [ Operation.parameter(:connection, :header, %Schema{type: :string}, "connection header", required: true ), Operation.parameter(:upgrade, :header, %Schema{type: :string}, "upgrade header", required: true ), Operation.parameter( :"sec-websocket-key", :header, %Schema{type: :string}, "sec-websocket-key header", required: true ), Operation.parameter( :"sec-websocket-version", :header, %Schema{type: :string}, "sec-websocket-version header", required: true ) ], responses: %{ 101 => switching_protocols_response(), 200 => Operation.response( "Server-sent events", "application/json", server_sent_events() ) } } end defp switching_protocols_response do %Response{ description: "Switching protocols", headers: %{ "connection" => %OpenApiSpex.Header{required: true}, "upgrade" => %OpenApiSpex.Header{required: true}, "sec-websocket-accept" => %OpenApiSpex.Header{required: true} } } end defp server_sent_events do %Schema{ oneOf: [ update_event(), status_update_event(), pleroma_respond_event() ] } end defp stream do %Schema{ type: :array, title: "Stream", description: """ The stream identifier. The first item is the name of the stream. If the stream needs a differentiator, the second item will be the corresponding identifier. Currently, for the following stream types, there is a second element in the array: - `list`: The second element is the id of the list, as a string. - `hashtag`: The second element is the name of the hashtag. - `public:remote:media` and `public:remote`: The second element is the domain of the corresponding instance. """, maxItems: 2, minItems: 1, items: %Schema{type: :string}, example: ["hashtag", "mew"] } end defp get_schema(%Schema{} = schema), do: schema defp get_schema(schema), do: schema.schema defp server_sent_event_helper(name, description, type, payload, opts \\ []) do payload_type = opts[:payload_type] || :json has_stream = opts[:has_stream] || true stream_properties = if has_stream do %{stream: stream()} else %{} end stream_example = if has_stream, do: %{"stream" => get_schema(stream()).example}, else: %{} stream_required = if has_stream, do: [:stream], else: [] %Schema{ type: :object, title: name, description: description, required: [:event, :payload] ++ stream_required, properties: %{ event: %Schema{ title: "Event type", description: "Type of the event.", type: :string, required: true, enum: [type] }, payload: if payload_type == :json do %Schema{ title: "Event payload", description: "JSON-encoded string of #{get_schema(payload).title}", allOf: [payload] } else payload end } |> Map.merge(stream_properties), example: %{ "event" => type, "payload" => get_schema(payload).example |> Jason.encode!() } |> Map.merge(stream_example) } end defp update_event do server_sent_event_helper("New status", "A newly-posted status.", "update", Status) end defp status_update_event do server_sent_event_helper("Edit", "A status that was just edited", "status.update", Status) end defp pleroma_respond_event do server_sent_event_helper( "Server response", "A response to a client-sent event.", "pleroma:respond", %Schema{ type: :object, title: "Results", required: [:result], properties: %{ result: %Schema{ type: :string, title: "Result of the request", enum: ["success", "error", "ignored"] }, error: %Schema{ type: :string, title: "Error code", description: "An error identifier. Only appears if `result` is `error`." } }, example: %{"result" => "success"} } ) end end