defmodule MessageServer.Router do use Plug.Router require Logger alias MessageServer.{ MessageRequest, RemoteHandler, MessageHandler } plug(:match) plug(:debug_content_type) plug(Plug.Parsers, parsers: [:urlencoded, :multipart, :json], json_decoder: Jason, pass: ["application/octet-stream"] ) plug(:dispatch) def json_request?(conn) do conn.request_path != "/api/remote/messages" end post "/api/messages" do with {:ok, message} <- validate_message_request(conn.body_params), :ok <- MessageHandler.handle_message(message) do conn |> put_resp_content_type("application/json") |> send_resp(200, Jason.encode!(%{status: "success"})) else {:error, reason} -> Logger.warning("Message handling failed: #{inspect(reason)}") conn |> put_resp_content_type("application/json") |> send_resp(400, Jason.encode!(%{error: reason})) end end post "/api/remote/messages" do case handle_etf_body(conn) do {:ok, payload} -> payload |> RemoteHandler.handle_remote_message() |> case do :ok -> conn |> put_resp_content_type("application/json") |> send_resp(200, Jason.encode!(%{status: "success"})) {:error, reason} -> Logger.warning("Remote message handling failed: #{inspect(reason)}") conn |> put_resp_content_type("application/json") |> send_resp(400, Jason.encode!(%{error: reason})) end {:error, reason} -> conn |> put_resp_content_type("application/json") |> send_resp(400, Jason.encode!(%{error: reason, message: "Invalid request body"})) end end match _ do send_resp(conn, 404, "Not found") end defp debug_content_type(conn, _opts) do conn |> Plug.Conn.get_req_header("content-type") |> IO.inspect(label: "Received Content-Type") conn end defp handle_etf_body(conn) do with {:ok, body, _conn} <- Plug.Conn.read_body(conn), {:ok, decoded} <- decode_etf(body) do {:ok, decoded} else {:error, reason} -> {:error, "Failed to read body: #{reason}"} {:more, _partial, _conn} -> {:error, "Body too large"} end end @spec decode_etf(binary()) :: {:ok, map()} | {:error, String.t()} defp decode_etf(binary_body) do try do payload = :erlang.binary_to_term(binary_body, [:safe]) {:ok, payload} rescue error -> {:error, "Deserialization failed: #{inspect(error)}"} end end @spec validate_message_request(map()) :: {:ok, map()} | {:error, String.t()} defp validate_message_request(params) do required_fields = ["from", "to", "message"] case Enum.all?(required_fields, &Map.has_key?(params, &1)) do true -> {:ok, %MessageRequest{ from: params["from"], to: params["to"], message: params["message"] }} false -> {:error, "Missing required fields: from, to, message"} end end end