Commit Graph

2 Commits

Author SHA1 Message Date
MechaCat02
a8d6da167c bugfix: second-pass audit follow-ups (N1-N4)
Four small follow-ups from the second-pass audit:

- N1: `manga_upload_rolls_back_when_cover_storage_fails` covers the
  manga-side of the transactional rollback path. The chapter case had
  a `FailingStorage` regression test already; this completes the
  symmetric pair. With fail-on-put-index=0, the cover put fails on
  the first call, the transaction aborts, and `SELECT count(*) FROM
  mangas WHERE title = 'Berserk'` is 0.

- N2: The SvelteKit proxy now catches network-layer failures from the
  upstream `fetch` (DNS / connection refused / TLS handshake) and
  returns a 502 with the standard error envelope
  (`code: 'upstream_unavailable'`) instead of letting SvelteKit's
  generic 500 HTML page through. `client.ts` can `.json()` the result
  cleanly so callers see a real ApiError with a meaningful code. The
  underlying cause is logged via `console.error` for the operator.
  Test in hooks.server.test.ts asserts the 502, the JSON envelope, and
  that `resolve` is not called (the proxy short-circuits).

- N3: `GET /api/v1/files/*key` now sets
  `X-Content-Type-Options: nosniff`. The upload-time magic-byte sniff
  is authoritative for what we declare as Content-Type; `nosniff`
  makes the contract explicit so older user-agents can't try to
  re-detect HTML/JS in a polyglot file that survived the sniff. Test
  in api_uploads.rs asserts the header.

- N4: The /bookmarks page used `{#if b.page}` to gate the "— page N"
  display, which falsy-elided a legitimate `page == 0`. Backend now
  rejects `page < 1` for new bookmarks (already shipped in 0.9.4),
  but any pre-0.9.4 row with page=0 still rendered without its
  number. Strengthened to `{#if b.page != null && b.page > 0}`.

Lockstep version bump to 0.10.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:55:53 +02:00
MechaCat02
ea60bd97de bugfix: proxy /api/* through the SvelteKit container
The compose deploy was unreachable because frontend code reads its
API base from `import.meta.env.VITE_API_BASE` at build time, but the
shipped image baked in the fallback `/api` and never picked up the
`PUBLIC_API_BASE` env var. The browser then hit
http://localhost:3000/api/...which the Node adapter doesn't serve, so
every request 404'd.

Fix the topology at the right layer: hooks.server.ts proxies /api/*
requests through to the backend container over docker's internal
network. The browser only ever talks to :3000, cookies stay
same-origin, and CORS can stay empty.

- frontend/src/hooks.server.ts: new proxy. Reads BACKEND_URL (defaults
  to http://localhost:8080 for ad-hoc node builds). Strips `host` and
  `content-length` so the backend sees the real client request and
  recomputes the length. Sets `duplex: 'half'` for streamed POST
  bodies. GET/HEAD have no body. Non-/api paths fall through to
  SvelteKit normally.
- docker-compose.yml: drop the host port mapping on the backend
  (browser doesn't reach it directly anymore — use `ports:` instead of
  `expose:` if you want curl access). Set BACKEND_URL=http://backend:8080
  on the frontend service. Drop PUBLIC_API_BASE which was unused.
- .env.example: replace PUBLIC_API_BASE with BACKEND_URL, with a note
  on what it does.
- README: explain the new topology in Quick start, update the bot
  curl examples to hit :3000 (since that's the only published port in
  the default deploy), and call out that the TLS terminator only needs
  one upstream now.

Lockstep version bump to 0.9.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:17:50 +02:00