fix(admin): add /rpc proxy route so the admin UI can reach the server #99

Merged
mahmoud merged 1 commit from development_fix_admin_rpc_proxy into development 2026-04-21 10:00:16 +00:00
Owner

Summary

The admin dashboard pages were silently 404ing on every data load because hero_books_admin never registered a /rpc route. Every template uses a JavaScript rpc() helper that POSTs to BASE_PATH + "/rpc". This PR adds the missing proxy, mirroring the pattern already in hero_books_ui.

Closes #98

Changes

  • crates/hero_books_admin/src/proxy.rs (new) — copied verbatim from crates/hero_books_ui/src/proxy.rs, with admin-duplicated items (BasePath, HeroRequestContext, base_path_middleware) and unused img_proxy / pdf_proxy removed. AppState, proxy_request, and api_proxy are byte-identical to the UI version — same 502-on-upstream-failure behaviour, same Content-Type / Authorization request-header allowlist.
  • crates/hero_books_admin/src/main.rs — resolves the upstream socket (HERO_BOOKS_SOCKET env override, falling back to hero_books_lib::default_server_socket_path()), constructs proxy_state = AppState { server_socket: Arc::new(...) }, and merges a small sub-router Router::new().route("/rpc", any(api_proxy)).with_state(proxy_state) into the main admin router. Admin's own Router<()> state is untouched.
  • crates/hero_books_admin/tests/rpc_proxy.rs (new) — integration test. round_trip_ok spawns a stub Unix-socket HTTP listener and asserts a JSON-RPC body round-trips verbatim. upstream_unreachable_returns_502 asserts HTTP 502 when the upstream socket is missing.
  • crates/hero_books_admin/Cargo.toml — added [dev-dependencies] with tower = { version = "0.5", features = ["util"] } so the test can call tower::ServiceExt::oneshot.

macOS note

The integration test roots its per-run tempdir under /tmp rather than std::env::temp_dir(). macOS resolves the latter to /var/folders/..., which exceeds the 104-byte SUN_LEN limit for Unix sockets once a subdirectory and filename are appended. A short doc comment in the test explains the constraint.

Test Results

  • cargo build -p hero_books_admin — ok.
  • cargo clippy -p hero_books_admin -- -D warnings and --tests -- -D warnings — clean.
  • hero_books_admin tests: 2/2 passed (round_trip_ok, upstream_unreachable_returns_502).
  • hero_books_server library regression gate: 16/16 passed.
  • cargo fmt --check -p hero_books_admin — clean.

Out of scope

  • Migrating both hero_books_ui and hero_books_admin to the openrpc_proxy! macro (current manual proxy.rs matches the sibling crate; macro migration is a larger refactor).
  • The other two items from the broader audit (consolidating architecture.md, adding docs/manual/docusaurus.md) — still pending as docs-only follow-ups.
## Summary The admin dashboard pages were silently 404ing on every data load because `hero_books_admin` never registered a `/rpc` route. Every template uses a JavaScript `rpc()` helper that POSTs to `BASE_PATH + "/rpc"`. This PR adds the missing proxy, mirroring the pattern already in `hero_books_ui`. ## Related Issue Closes https://forge.ourworld.tf/lhumina_code/hero_books/issues/98 ## Changes - **`crates/hero_books_admin/src/proxy.rs`** (new) — copied verbatim from `crates/hero_books_ui/src/proxy.rs`, with admin-duplicated items (`BasePath`, `HeroRequestContext`, `base_path_middleware`) and unused `img_proxy` / `pdf_proxy` removed. `AppState`, `proxy_request`, and `api_proxy` are byte-identical to the UI version — same 502-on-upstream-failure behaviour, same `Content-Type` / `Authorization` request-header allowlist. - **`crates/hero_books_admin/src/main.rs`** — resolves the upstream socket (`HERO_BOOKS_SOCKET` env override, falling back to `hero_books_lib::default_server_socket_path()`), constructs `proxy_state = AppState { server_socket: Arc::new(...) }`, and merges a small sub-router `Router::new().route("/rpc", any(api_proxy)).with_state(proxy_state)` into the main admin router. Admin's own `Router<()>` state is untouched. - **`crates/hero_books_admin/tests/rpc_proxy.rs`** (new) — integration test. `round_trip_ok` spawns a stub Unix-socket HTTP listener and asserts a JSON-RPC body round-trips verbatim. `upstream_unreachable_returns_502` asserts HTTP 502 when the upstream socket is missing. - **`crates/hero_books_admin/Cargo.toml`** — added `[dev-dependencies]` with `tower = { version = "0.5", features = ["util"] }` so the test can call `tower::ServiceExt::oneshot`. ## macOS note The integration test roots its per-run tempdir under `/tmp` rather than `std::env::temp_dir()`. macOS resolves the latter to `/var/folders/...`, which exceeds the 104-byte `SUN_LEN` limit for Unix sockets once a subdirectory and filename are appended. A short doc comment in the test explains the constraint. ## Test Results - `cargo build -p hero_books_admin` — ok. - `cargo clippy -p hero_books_admin -- -D warnings` and `--tests -- -D warnings` — clean. - `hero_books_admin` tests: 2/2 passed (`round_trip_ok`, `upstream_unreachable_returns_502`). - `hero_books_server` library regression gate: 16/16 passed. - `cargo fmt --check -p hero_books_admin` — clean. ## Out of scope - Migrating both `hero_books_ui` and `hero_books_admin` to the `openrpc_proxy!` macro (current manual `proxy.rs` matches the sibling crate; macro migration is a larger refactor). - The other two items from the broader audit (consolidating `architecture.md`, adding `docs/manual/docusaurus.md`) — still pending as docs-only follow-ups.
fix(admin): add /rpc proxy route so the admin UI can reach the server
All checks were successful
Test / test (pull_request) Successful in 9m15s
Test / integration (pull_request) Successful in 4m25s
0bc065c01d
Every page in hero_books_admin used a JavaScript rpc() helper that
POSTed to BASE_PATH + "/rpc", but the admin router never registered
/rpc — so Libraries, Collections, Import, Export, and Health all 404ed
on data load in the browser. Classic hero_ui_openrpc_proxy gap.

The fix mirrors the pattern already in hero_books_ui:

- new module crates/hero_books_admin/src/proxy.rs, copied from the
  UI's proxy.rs with admin-duplicated items (BasePath,
  HeroRequestContext, base_path_middleware) and unused img_proxy /
  pdf_proxy handlers removed. AppState, proxy_request, and api_proxy
  are byte-identical to the UI, including the 502-on-upstream-failure
  behaviour and the Content-Type / Authorization request-header
  allowlist.
- crates/hero_books_admin/src/main.rs resolves the upstream socket
  (HERO_BOOKS_SOCKET override → hero_books_lib::default_server_socket_path()
  fallback) and merges a small sub-router
  Router::new().route("/rpc", any(api_proxy)).with_state(proxy_state)
  into the main admin router so admin's own Router<()> state is
  untouched.
- new integration test crates/hero_books_admin/tests/rpc_proxy.rs:
  round_trip_ok spawns a stub Unix-socket HTTP server and asserts a
  JSON-RPC body round-trips verbatim; upstream_unreachable_returns_502
  asserts HTTP 502 when the upstream socket is missing.
- dev-dep added: tower = { version = "0.5", features = ["util"] } so
  the test can call tower::ServiceExt::oneshot.

The test roots its per-run tempdir under /tmp rather than
std::env::temp_dir() — on macOS the latter resolves to /var/folders/...
which exceeds the 104-byte SUN_LEN limit for Unix sockets once a
subdirectory and filename are appended. A short doc comment in the
test explains the constraint.

Build, clippy (-D warnings including --tests), and fmt are clean.
Admin tests 2/2 pass; hero_books_server regression gate 16/16 pass.

#98
mahmoud merged commit 6c0860e6fc into development 2026-04-21 10:00:16 +00:00
mahmoud deleted branch development_fix_admin_rpc_proxy 2026-04-21 10:00:21 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_books!99
No description provided.