feat(api): axum skeleton with health and OpenAPI #53

Merged
naps62-yolo merged 2 commits from issue/5-axum-skeleton into main 2026-08-22 20:09:53 +01:00
Owner

Closes #5.

GET /api/health probes Prowlarr (/ping), Transmission (an RPC call — a
409 asking for a session id is a live daemon) and TMDB, concurrently, with a
3s timeout each. It always answers 200; the body carries ok or degraded
plus a per-upstream verdict, so a degraded service can explain itself instead
of looking like a fourth outage. A missing TMDB key is unconfigured, not an
outage. Failure details never include the probed URL, which carries the TMDB
key in its query string.

The OpenAPI document is collected from #[utoipa::path] annotations via
utoipa_axum::routes!, so it is generated, not written. routes! does not
accept a handler without the annotation:

error[E0433]: cannot find module or crate `__path_unannotated` in this scope

Served at /api/openapi.json, browsable at /api/docs (Scalar).

just gen-client dumps the spec via arr --openapi (no port, no upstreams)
and runs openapi-typescript into web/src/api/. Output is gitignored —
nothing consumes it until #6, and generated files no CI step regenerates go
stale silently.

The web/ probe in the Justfile and CI now keys on web/package.json rather
than the directory, since gen-client creates web/ before the SPA exists.

Verification

just ci green: fmt, clippy -D warnings, machete, 24 tests.

$ curl -s localhost:7878/api/health
{"status":"degraded","version":"0.1.0","prowlarr":{"status":"unreachable","detail":"error sending request"},"transmission":{"status":"unreachable","detail":"error sending request"},"tmdb":{"status":"unconfigured","detail":"no ARR_TMDB_API_KEY set"}}
$ curl -so /dev/null -w '%{http_code}' localhost:7878/api/docs
200

Not verified against the live Prowlarr or Transmission — neither answered from
this machine at the addresses in DESIGN.md §3, so the probe shapes are covered
by wiremock only.

Closes #5. `GET /api/health` probes Prowlarr (`/ping`), Transmission (an RPC call — a `409` asking for a session id is a live daemon) and TMDB, concurrently, with a 3s timeout each. It always answers `200`; the body carries `ok` or `degraded` plus a per-upstream verdict, so a degraded service can explain itself instead of looking like a fourth outage. A missing TMDB key is `unconfigured`, not an outage. Failure details never include the probed URL, which carries the TMDB key in its query string. The OpenAPI document is collected from `#[utoipa::path]` annotations via `utoipa_axum::routes!`, so it is generated, not written. `routes!` does not accept a handler without the annotation: ``` error[E0433]: cannot find module or crate `__path_unannotated` in this scope ``` Served at `/api/openapi.json`, browsable at `/api/docs` (Scalar). `just gen-client` dumps the spec via `arr --openapi` (no port, no upstreams) and runs `openapi-typescript` into `web/src/api/`. Output is gitignored — nothing consumes it until #6, and generated files no CI step regenerates go stale silently. The `web/` probe in the Justfile and CI now keys on `web/package.json` rather than the directory, since `gen-client` creates `web/` before the SPA exists. <details> <summary>Verification</summary> `just ci` green: fmt, clippy `-D warnings`, machete, 24 tests. ``` $ curl -s localhost:7878/api/health {"status":"degraded","version":"0.1.0","prowlarr":{"status":"unreachable","detail":"error sending request"},"transmission":{"status":"unreachable","detail":"error sending request"},"tmdb":{"status":"unconfigured","detail":"no ARR_TMDB_API_KEY set"}} $ curl -so /dev/null -w '%{http_code}' localhost:7878/api/docs 200 ``` Not verified against the live Prowlarr or Transmission — neither answered from this machine at the addresses in DESIGN.md §3, so the probe shapes are covered by wiremock only. </details>
naps62-yolo added 1 commit 2026-08-22 19:55:51 +01:00
feat(api): axum skeleton with health and OpenAPI
ci / rust (pull_request) Successful in 1m52s
ci / web (pull_request) Successful in 6s
e2e / e2e (pull_request) Successful in 1m17s
2de7f88e42
The document is collected from `#[utoipa::path]` annotations through
`utoipa_axum::routes!`, which refuses a handler that has none, so an
unannotated route fails the build rather than silently leaving the
TypeScript client short an endpoint.

Health probes the three upstreams DESIGN.md §9.5 calls "Broken", always
answering 200 so a degraded service can still explain itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
naps62-yolo added 1 commit 2026-08-22 19:57:03 +01:00
Merge remote-tracking branch 'origin/main' into issue/5-axum-skeleton
ci / rust (pull_request) Successful in 1m27s
ci / web (pull_request) Successful in 5s
e2e / e2e (pull_request) Successful in 1m33s
bf9426fb1d
# Conflicts:
#	Cargo.lock
naps62-yolo reviewed 2026-08-22 19:57:41 +01:00
naps62-yolo left a comment
Author
Owner

Reviewed 2de7f88e42410a7032a6087b239c93d6dd56c118. No findings.

Reviewed `2de7f88e42410a7032a6087b239c93d6dd56c118`. No findings. <!-- agent-meta: {"model":"gpt-5.6","session":"01a02ad4"} -->
naps62-yolo merged commit 58827da647 into main 2026-08-22 20:09:53 +01:00
naps62-yolo deleted branch issue/5-axum-skeleton 2026-08-22 20:09:54 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yolo/arr#53