defmodule ToweropsWeb.GraphQLSocket do @moduledoc """ WebSocket endpoint for GraphQL subscriptions. Authenticates via either: - API token (prefixed with "towerops_") → sets organization_id + user in context - Mobile session token → sets user only (no organization_id) """ use Phoenix.Socket use Absinthe.Phoenix.Socket, schema: ToweropsWeb.GraphQL.Schema alias Absinthe.Phoenix.Socket alias Towerops.Accounts alias Towerops.ApiTokens alias Towerops.MobileSessions @impl true def connect(%{"token" => "towerops_" <> _ = token}, socket, _connect_info) do case ApiTokens.verify_token(token) do {:ok, org_id, user} -> socket = socket |> assign(:organization_id, org_id) |> assign(:user, user) |> Socket.put_options(context: %{organization_id: org_id, user: user}) {:ok, socket} {:error, :invalid_token} -> :error end end def connect(%{"token" => token}, socket, _connect_info) when is_binary(token) do with session when not is_nil(session) <- MobileSessions.get_session_by_token(token), user when not is_nil(user) <- Accounts.get_user(session.user_id) do _ = Task.Supervisor.start_child(Towerops.TaskSupervisor, fn -> MobileSessions.touch_session(session) end) socket = socket |> assign(:user, user) |> Socket.put_options(context: %{user: user}) {:ok, socket} else _ -> :error end end def connect(_params, _socket, _connect_info), do: :error @impl true def id(%{assigns: %{organization_id: org_id}}), do: "graphql_socket:#{org_id}" def id(%{assigns: %{user: user}}), do: "graphql_socket:user:#{user.id}" def id(_socket), do: nil end