# lib/core/http/router.ex defmodule Core.HTTP.Router do @moduledoc """ Default router implementation. This is used by the demo application. """ use Plug.Router require Logger alias Core.Workers.JobQueue plug Plug.Logger, log: :info plug :match plug Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Jason plug Plug.Telemetry, event_prefix: [:server, :http] plug :dispatch # Root endpoint get "/" do send_resp(conn, 200, "Server is running") end # Health check get "/health" do worker_alive = Process.whereis(JobQueue) != nil {status, code} = if worker_alive, do: {"OK", 200}, else: {"DEGRADED", 503} conn |> put_resp_content_type("application/json") |> send_resp(code, Jason.encode!(%{status: status})) end get "/stats" do stats = JobQueue.stats() conn |> put_resp_content_type("application/json") |> send_resp(200, Jason.encode!(stats)) end # Submit a new job post "/jobs" do case conn.body_params do %{"payload" => payload} when is_map(payload) -> opts = [] opts = if conn.body_params["max_attempts"], do: Keyword.put(opts, :max_attempts, conn.body_params["max_attempts"]), else: opts {:ok, id} = JobQueue.submit(payload, opts) conn |> put_resp_content_type("application/json") |> send_resp(202, Jason.encode!(%{message: "Job accepted", job_id: id})) %{"payload" => _} -> conn |> put_resp_content_type("application/json") |> send_resp(400, Jason.encode!(%{error: "'payload' must be a JSON object"})) _ -> conn |> put_resp_content_type("application/json") |> send_resp(400, Jason.encode!(%{error: "Missing 'payload' field"})) end end post "/jobs/schedule" do with %{"payload" => payload, "run_at" => run_at_str} <- conn.body_params, {:ok, run_at, _} <- DateTime.from_iso8601(run_at_str) do {:ok, id} = JobQueue.submit_at(payload, run_at) conn |> put_resp_content_type("application/json") |> send_resp(202, Jason.encode!(%{message: "Job scheduled", job_id: id, run_at: run_at_str})) else _ -> conn |> put_resp_content_type("application/json") |> send_resp(400, Jason.encode!(%{error: "Required fields: payload (object), run_at (ISO8601)"})) end end get "/jobs" do try do params = conn.query_params opts = [] |> maybe_put(:status, params["status"] && String.to_existing_atom(params["status"])) |> maybe_put(:page, params["page"] && String.to_integer(params["page"])) |> maybe_put(:per_page, params["per_page"] && String.to_integer(params["per_page"])) jobs = JobQueue.all(opts) conn |> put_resp_content_type("application/json") |> send_resp(200, Jason.encode!(jobs)) rescue ArgumentError -> conn |> put_resp_content_type("application/json") |> send_resp(400, Jason.encode!(%{error: "Invalid status filter. Valid values: queued, running, done, failed"})) end end get "/jobs/:id" do with {int_id, ""} <- Integer.parse(id), {:ok, job} <- JobQueue.get(int_id) do conn |> put_resp_content_type("application/json") |> send_resp(200, Jason.encode!(job)) else :error -> conn |> put_resp_content_type("application/json") |> send_resp(400, Jason.encode!(%{error: "Job ID must be an integer"})) {:error, :not_found} -> conn |> put_resp_content_type("application/json") |> send_resp(404, Jason.encode!(%{error: "Job not found"})) end end match _ do send_resp(conn, 404, Jason.encode!(%{error: "Not found"})) end defp maybe_put(opts, _key, nil), do: opts defp maybe_put(opts, key, value), do: Keyword.put(opts, key, value) end