lib/openagents_web/controllers/conversation_controller.ex

58e6347eeb72 · 2 KB

defmodule OpenAgentsWeb.ConversationController do
  @moduledoc """
  Names the account's conversation to a client that holds a `box:control` token.

  Every Box route is addressed by conversation, so a client that does not
  already know its conversation id cannot reach any of them, and nothing told
  it: `GET /api/v1/user` answers the forge identity, and it sits behind
  `forge:write`, which a box token does not carry.

  This is the one route that answers the question, deliberately. The field
  could have been added to `/api/v1/user` instead, but that response is
  GitHub-shaped, and API-001 holds every OpenAgents field there to a namespaced
  `openagents` object. A route of our own carries it at the top level without
  bending that rule, and one canonical answer beats two ways to learn the same
  fact.

  An account has exactly one conversation, and this endpoint creates it when
  it is absent rather than refusing. The alternative dead-ends a headless
  caller: a `box:control` token with no conversation has no route left to try,
  and the only way to get one would be to open a browser. `ensure_conversation/1`
  is the same idempotent call the web chat makes on first visit, so the two
  paths converge on one row instead of racing to make a second.
  """

  use OpenAgentsWeb, :controller

  alias OpenAgents.Conversations
  alias OpenAgents.Conversations.Conversation

  def show(conn, _params) do
    case conversation(conn.assigns.current_user) do
      {:ok, %Conversation{} = conversation} ->
        json(conn, %{
          "conversation_id" => conversation.id,
          "conversation" => %{
            "id" => conversation.id,
            "created_at" => DateTime.to_iso8601(conversation.inserted_at)
          }
        })

      :error ->
        conn
        |> put_status(:service_unavailable)
        |> json(%{"error" => %{"code" => "conversation_unavailable"}})
    end
  end

  defp conversation(user) do
    case Conversations.get_conversation_for_user(user) do
      %Conversation{} = conversation ->
        {:ok, conversation}

      nil ->
        case Conversations.ensure_conversation(user) do
          {:ok, %Conversation{} = conversation} -> {:ok, conversation}
          _other -> :error
        end
    end
  end
end