mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
Adds `POST /api/doctor-report/pdf` that returns a fully rendered PDF (`Content-Type: application/pdf`) using the same jsPDF document the web client builds locally, just executed server-side. Locale resolves from explicit body param → `Accept-Language` → `de` fallback. The existing `POST /api/doctor-report` JSON endpoint is unchanged; both endpoints share `collectDoctorReportData()` so any future change to the aggregation lands in one place. The render core (`doctor-report-pdf-core`) is isomorphic — same code paths run in browser jsPDF and Node jsPDF. Rate-limit (10/h per user) is shared between the two endpoints so the quota cannot be doubled by mixing JSON and PDF requests. Tests: 6 core tests (PDF header bytes, size, determinism, DE+EN, empty data, jsPDF doc shape) + 5 route tests (401, 429, 200 happy-path with content-type/disposition, default `days=90`, Accept-Language fallback). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
58 lines
2.7 KiB
Markdown
58 lines
2.7 KiB
Markdown
# Doctor Report
|
|
|
|
The doctor report is a printable PDF summary of a user's tracked vitals,
|
|
medication compliance, glucose context, and mood over a configurable window
|
|
(default 90 days, max 365). It is intended as discussion material for a
|
|
doctor's appointment, **not** a medical diagnosis.
|
|
|
|
## Two endpoints
|
|
|
|
The same aggregation logic backs both endpoints — `collectDoctorReportData`
|
|
in `src/lib/doctor-report-data.ts` is the single source of truth — so the
|
|
JSON payload and the server-rendered PDF are guaranteed to be in sync.
|
|
|
|
### `POST /api/doctor-report` — JSON payload
|
|
|
|
Returns the aggregated `DoctorReportData` envelope. Used by the PWA's
|
|
client-side renderer (`src/lib/doctor-report-pdf.ts`, jsPDF + autotable).
|
|
Best on desktop browsers and on Android, where local generation keeps the
|
|
PDF off the wire and works offline (cached page).
|
|
|
|
- Auth: session required.
|
|
- Rate-limit: 10/h per user.
|
|
- Body: `{ days?: number }` (default 90).
|
|
- Response: `200 application/json` with envelope `{ data, error: null }`.
|
|
- Audit action: `doctor-report.generate`.
|
|
|
|
### `POST /api/doctor-report/pdf` — server-rendered PDF
|
|
|
|
Returns the ready-to-download PDF bytes. Used primarily on iOS/Safari, where
|
|
the client-side jsPDF download UX is unreliable (Safari sometimes opens the
|
|
PDF in a new tab without preserving the filename or fails the blob save).
|
|
|
|
- Auth: session required (`401` otherwise).
|
|
- Rate-limit: 10/h per user, shared with the JSON endpoint (`429` on excess).
|
|
- Body: `{ days?: number; locale?: "de" | "en" }`. Body is optional — empty
|
|
bodies are accepted.
|
|
- Locale resolution: explicit `body.locale` wins, then the
|
|
`Accept-Language` header (`de*` → `de`, anything else → `en`), then the
|
|
hard fallback `de`.
|
|
- Response: `200 application/pdf` with
|
|
`Content-Disposition: attachment; filename="healthlog-report-YYYY-MM-DD.pdf"`
|
|
and `Cache-Control: no-store`. Body is the raw PDF bytes.
|
|
- Errors: JSON envelope `{ data: null, error: "..." }` for `401` and `429`;
|
|
`500` if rendering fails.
|
|
- Audit action: `doctor-report.pdf.generate`.
|
|
|
|
## Implementation notes
|
|
|
|
- `src/lib/doctor-report-pdf-core.ts` contains the isomorphic renderer.
|
|
jsPDF runs unchanged in Node — `doc.output("arraybuffer")` returns a valid
|
|
`%PDF-` byte stream in both environments, so we deliberately avoid headless
|
|
Chromium (Puppeteer/Playwright) to keep the runtime dependency surface low.
|
|
- `src/lib/doctor-report-pdf.ts` is a thin client façade that returns the
|
|
raw `jsPDF` instance so the existing settings-page download flow (which
|
|
calls `doc.save(...)`) keeps working unchanged.
|
|
- Server-side translations come from `messages/{de,en}.json` via
|
|
`getServerTranslator()` (`src/lib/i18n/server-translator.ts`).
|