# Frontend API Usage (v1) This document is the **frontend-facing** single source of truth for consuming the Lieferscheine backend APIs. Scope: - Stable **API v1** contracts (URLs, params, response shapes). - The minimal frontend `apiClient` helper layer (`lib/frontend/apiClient.js`). - Practical examples for building the first UI. > UI developers: For the app shell layout and frontend route scaffold (public vs protected routes, placeholder pages), see **`Docs/frontend-ui.md`**. > > For UI navigation, prefer centralized route builders from `lib/frontend/routes.js` instead of hardcoding path strings. Non-goals: - New major features. - PDF streaming/viewer implementation details (see “Out of scope / planned”). --- ## 1. Quickstart ### 1.1 Recommended calling pattern For the first UI, prefer calling the backend from **browser/client-side code** so the HTTP-only session cookie is naturally sent with requests. Key fetch requirements: - Always send cookies: `credentials: "include"` - Always request fresh data: `cache: "no-store"` The project provides a tiny wrapper to enforce those defaults: `lib/frontend/apiClient.js`. ### 1.2 Happy-path navigation flow The core UI flow is a simple drill-down: 1. `login({ username, password })` 2. `getMe()` (optional but recommended) 3. `getBranches()` 4. `getYears(branch)` 5. `getMonths(branch, year)` 6. `getDays(branch, year, month)` 7. `getFiles(branch, year, month, day)` ### 1.3 Example usage (client-side) ```js import { login, getMe, getBranches, getYears, getMonths, getDays, getFiles, ApiClientError, } from "@/lib/frontend/apiClient"; export async function runExampleFlow() { try { await login({ username: "nl01user", password: "secret" }); const me = await getMe(); if (!me.user) throw new Error("Expected to be logged in"); const { branches } = await getBranches(); const branch = branches[0]; const { years } = await getYears(branch); const year = years[years.length - 1]; const { months } = await getMonths(branch, year); const month = months[months.length - 1]; const { days } = await getDays(branch, year, month); const day = days[days.length - 1]; const { files } = await getFiles(branch, year, month, day); return { branch, year, month, day, files }; } catch (err) { if (err instanceof ApiClientError) { // Use err.code for UI decisions. // Example: AUTH_UNAUTHENTICATED -> redirect to login throw err; } throw err; } } ``` ### 1.4 Frontend route helpers (UI navigation) File: - `lib/frontend/routes.js` Purpose: - Centralize URL building so UI code does not scatter hardcoded strings. - Encode dynamic segments defensively. Example: ```js import { branchPath, dayPath, searchPath, loginPath, } from "@/lib/frontend/routes"; const loginUrl = loginPath(); const branchUrl = branchPath("NL01"); const dayUrl = dayPath("NL01", "2025", "12", "31"); const searchUrl = searchPath("NL01"); ``` --- ## 2. The `apiClient` helper File: - `lib/frontend/apiClient.js` Design goals: - Enforce `credentials: "include"` and `cache: "no-store"`. - Parse JSON automatically. - Convert standardized backend errors into a single error type: `ApiClientError`. ### 2.1 `ApiClientError` When the backend returns the standardized error payload: ```json { "error": { "message": "...", "code": "...", "details": {} } } ``` …the client throws: - `name = "ApiClientError"` - `status` (HTTP status) - `code` (machine-readable error code) - `message` (safe human-readable message) - optional `details` ### 2.2 Provided helpers Auth: - `login({ username, password })` - `logout()` - `getMe()` Navigation: - `getBranches()` - `getYears(branch)` - `getMonths(branch, year)` - `getDays(branch, year, month)` Files: - `getFiles(branch, year, month, day)` Low-level: - `apiFetch(path, options)` ### 2.3 Server-side usage note (Node / scripts) Node’s `fetch` does **not** include a cookie jar automatically. For manual verification we provide a script that includes a minimal cookie jar: - `scripts/manual-api-client-flow.mjs` For server checks, run it **inside the app container**: ```bash docker compose exec app node scripts/manual-api-client-flow.mjs \ --baseUrl=http://127.0.0.1:3000 \ --username= \ --password= \ --branch=NL01 ``` --- ## 3. API v1 conventions ### 3.1 Identifiers - `branch`: `NL01`, `NL02`, ... - `year`: `"YYYY"` (4 digits) - `month`: `"MM"` (2 digits, `01`–`12`) - `day`: `"DD"` (2 digits, `01`–`31`) ### 3.2 Sorting Current server responses are sorted **ascending**: - branches: ascending by branch number (`NL01`, `NL02`, ...) - years/months/days: numeric ascending - files: lexicographic ascending by file name If the UI needs “newest first”, reverse the arrays in the UI. ### 3.3 File entries `getFiles()` returns: ```json { "files": [{ "name": "...pdf", "relativePath": "NL01/2025/12/19/...pdf" }] } ``` Notes: - `relativePath` is **relative to `NAS_ROOT_PATH`** inside the container. - Treat it as an opaque identifier for a future download/stream endpoint. --- ## 4. Endpoint contracts (v1) All routes are served under `/api`. ### 4.1 Auth #### `POST /api/auth/login` Body: ```json { "username": "example.user", "password": "plain" } ``` Success: ```json { "ok": true } ``` Errors: - `400 VALIDATION_*` - `401 AUTH_INVALID_CREDENTIALS` #### `GET /api/auth/logout` Success: ```json { "ok": true } ``` #### `GET /api/auth/me` Success (authenticated): ```json { "user": { "userId": "...", "role": "branch|admin|dev", "branchId": "NL01" } } ``` Success (unauthenticated): ```json { "user": null } ``` ### 4.2 Branch navigation All endpoints below require a valid session. #### `GET /api/branches` Success: ```json { "branches": ["NL01", "NL02"] } ``` RBAC: - `branch` role: returns only its own branch - `admin`/`dev`: returns all branches #### `GET /api/branches/:branch/years` Success: ```json { "branch": "NL01", "years": ["2024", "2025"] } ``` #### `GET /api/branches/:branch/:year/months` Success: ```json { "branch": "NL01", "year": "2025", "months": ["01", "02"] } ``` #### `GET /api/branches/:branch/:year/:month/days` Success: ```json { "branch": "NL01", "year": "2025", "month": "12", "days": ["18", "19"] } ``` ### 4.3 Files #### `GET /api/files?branch=&year=&month=&day=` Success: ```json { "branch": "NL01", "year": "2025", "month": "12", "day": "19", "files": [{ "name": "test.pdf", "relativePath": "NL01/2025/12/19/test.pdf" }] } ``` ### 4.4 Health #### `GET /api/health` Always returns `200` and reports partial system state: - database connectivity - NAS readability --- ## 5. Error handling ### 5.1 Standard error payload All error responses use: ```json { "error": { "message": "Human readable message", "code": "SOME_MACHINE_CODE", "details": {} } } ``` ### 5.2 Common codes used by the UI Auth: - `AUTH_UNAUTHENTICATED` - `AUTH_INVALID_CREDENTIALS` - `AUTH_FORBIDDEN_BRANCH` Validation: - `VALIDATION_MISSING_PARAM` - `VALIDATION_MISSING_QUERY` - `VALIDATION_INVALID_JSON` - `VALIDATION_INVALID_BODY` - `VALIDATION_MISSING_FIELD` Storage: - `FS_NOT_FOUND` - `FS_STORAGE_ERROR` Internal: - `INTERNAL_SERVER_ERROR` --- ## 6. Caching & freshness The backend reads from a NAS where new scans can appear at any time. Backend rules: - All route handlers are `dynamic = "force-dynamic"`. - All JSON responses include `Cache-Control: no-store`. - A small process-local TTL cache exists in `lib/storage.js`: - branches/years: 60s - months/days/files: 15s Frontend guidance: - Use `credentials: "include"` and `cache: "no-store"`. - Do not rely on Next.js ISR/revalidate for these endpoints. --- ## 7. Manual verification (RHL-008) The repository contains a manual smoke test script that exercises: - happy path drill-down - negative cases (401/403/400/404) Script: - `scripts/manual-api-client-flow.mjs` Local: ```bash node scripts/manual-api-client-flow.mjs \ --baseUrl=http://localhost:3000 \ --username= \ --password= \ --branch=NL01 ``` Server (recommended from within container): ```bash docker compose exec app node scripts/manual-api-client-flow.mjs \ --baseUrl=http://127.0.0.1:3000 \ --username= \ --password= \ --branch=NL01 ``` --- ## 8. API v1 freeze policy As of RHL-008, the endpoints and response shapes documented here are considered **API v1**. Rules: - Avoid breaking changes to existing URLs, parameters, or response fields. - Prefer additive changes: - add new endpoints - add optional fields - If a breaking change becomes necessary, introduce a new endpoint rather than modifying the existing contract. --- ## 9. Out of scope / planned additions PDF delivery (download/stream) is not part of the current v1 surface documented above. Planned as additive change: - a dedicated endpoint to stream or download a PDF while enforcing RBAC server-side.