lib/openagents_web/controllers/admin_recording_controller.ex

main at 58e6347eeb72 · 1 KB

defmodule OpenAgentsWeb.AdminRecordingController do
  @moduledoc """
  Streams one call recording to an authenticated operator.

  This is the route that hands one person another person's voice, so ADMIN-001
  names it and `test/openagents_web/operator_surface_test.exs` holds it in the
  enumerated operator surface. The read is not audited; ADMIN-001 records why.

  The reader sends the complete ordered recording because later WebM, Ogg, and
  MP4 chunks depend on the first chunk's container header. It advertises no
  range support, unseals one chunk at a time, and never loads a complete call
  into memory.
  """

  use OpenAgentsWeb, :controller

  import Plug.Conn

  alias OpenAgents.Admin
  alias OpenAgents.Repo
  alias OpenAgents.Voice.Recordings

  def show(conn, %{"id" => id}) do
    case Admin.get_recording(id) do
      {:ok, recording, _owner} ->
        if recording.chunk_count > 0 and
             recording.status in OpenAgents.Voice.Recording.playable_statuses() do
          stream_recording(conn, recording)
        else
          send_resp(conn, :not_found, "")
        end

      {:error, :not_found} ->
        send_resp(conn, :not_found, "")
    end
  end

  defp stream_recording(conn, recording) do
    conn =
      conn
      |> put_resp_content_type(Recordings.content_type(recording), nil)
      |> put_resp_header("cache-control", "no-store")
      |> put_resp_header("accept-ranges", "none")
      |> put_resp_header("content-disposition", "inline")
      |> put_resp_header("x-content-type-options", "nosniff")
      |> send_chunked(:ok)

    {:ok, final_conn} =
      Repo.transaction(
        fn ->
          recording
          |> Recordings.stream()
          |> Enum.reduce_while(conn, fn recording_chunk, current_conn ->
            case chunk(current_conn, recording_chunk) do
              {:ok, next_conn} -> {:cont, next_conn}
              {:error, :closed} -> {:halt, current_conn}
            end
          end)
        end,
        timeout: :infinity
      )

    final_conn
  end
end