diff --git a/Dockerfile b/Dockerfile index ace68f3..1a4657c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,6 +43,7 @@ RUN mix deps.compile COPY priv priv COPY lib lib COPY assets assets +COPY mcp-server/package.json mcp-server/package.json # Compile the release RUN mix compile diff --git a/lib/loopctl/tenant_keys.ex b/lib/loopctl/tenant_keys.ex index 1de6fb0..844cc1a 100644 --- a/lib/loopctl/tenant_keys.ex +++ b/lib/loopctl/tenant_keys.ex @@ -74,7 +74,10 @@ defmodule Loopctl.TenantKeys do secret_name = Secrets.audit_key_secret_name(slug) case Secrets.get(secret_name) do - {:ok, key} -> + {:ok, encoded} -> + # Fly secrets store keys as base64 (set via FlyAdapter.set/2). + # Decode to raw bytes for :crypto.sign/5. + key = decode_key(encoded) :ets.insert(@cache_table, {tenant_id, key, now + @ttl_seconds}) {:ok, key} @@ -87,4 +90,13 @@ defmodule Loopctl.TenantKeys do end end end + + # Keys may be stored as base64 (FlyAdapter encodes) or raw bytes (test mocks). + # Try base64 decode first; if it fails, assume raw bytes. + defp decode_key(value) when is_binary(value) do + case Base.decode64(value) do + {:ok, raw} when byte_size(raw) == 32 -> raw + _ -> value + end + end end diff --git a/lib/loopctl_web/controllers/redirect_controller.ex b/lib/loopctl_web/controllers/redirect_controller.ex new file mode 100644 index 0000000..b478faf --- /dev/null +++ b/lib/loopctl_web/controllers/redirect_controller.ex @@ -0,0 +1,11 @@ +defmodule LoopctlWeb.RedirectController do + @moduledoc """ + Simple redirects for common URL aliases. + """ + + use LoopctlWeb, :controller + + def swagger(conn, _params) do + redirect(conn, to: "/swaggerui") + end +end diff --git a/lib/loopctl_web/controllers/route_discovery_controller.ex b/lib/loopctl_web/controllers/route_discovery_controller.ex index 62656d3..758139b 100644 --- a/lib/loopctl_web/controllers/route_discovery_controller.ex +++ b/lib/loopctl_web/controllers/route_discovery_controller.ex @@ -606,7 +606,7 @@ defmodule LoopctlWeb.RouteDiscoveryController do }, # OpenAPI spec - %{method: "GET", path: "/api/openapi", description: "Full OpenAPI 3.0 spec (Swagger)"}, + %{method: "GET", path: "/api/v1/openapi", description: "Full OpenAPI 3.0 spec (Swagger)"}, # Superadmin endpoints %{ diff --git a/lib/loopctl_web/router.ex b/lib/loopctl_web/router.ex index 37c1cb9..236f36b 100644 --- a/lib/loopctl_web/router.ex +++ b/lib/loopctl_web/router.ex @@ -98,6 +98,11 @@ defmodule LoopctlWeb.Router do get "/", OpenApiSpex.Plug.SwaggerUI, path: "/api/v1/openapi" end + # Convenience alias — agents and humans commonly try /swagger + scope "/" do + get "/swagger", LoopctlWeb.RedirectController, :swagger + end + # Dev-only routes (dashboard, etc.) if Application.compile_env(:loopctl, :dev_routes, false) do end