# API Overview This document describes the HTTP API exposed by the application using Next.js **Route Handlers** in the App Router (`app/api/*/route.js`). All routes below are served under the `/api` prefix. > Frontend developers: For practical usage examples and the small `apiClient` helper layer, read **`docs/frontend-api-usage.md`**. That document is the frontend-oriented single source of truth. --- ## 1. Configuration Dependencies The API expects a valid server configuration. Required environment variables: - `MONGODB_URI` — database connection string (used by `lib/db.js`). - `SESSION_SECRET` — JWT signing secret for session cookies. - `NAS_ROOT_PATH` — NAS mount root for storage operations. Optional environment variables: - `SESSION_COOKIE_SECURE` — override for the cookie `Secure` flag (`true`/`false`). ### 1.1 Search provider configuration (RHL-016) The Search API can run with different provider backends. - `SEARCH_PROVIDER` (optional) - Allowed values: `fs` | `qsirch` - Default: `fs` Notes: - `fs` is a local/test fallback that traverses the NAS-like folder structure directly. - `qsirch` is the intended production provider (indexed search on QNAP). If `SEARCH_PROVIDER=qsirch`, these variables are required: - `QSIRCH_BASE_URL` — base URL of the Qsirch service (must be reachable from inside the app container). - Example: `http://192.168.0.22:8080` - `QSIRCH_ACCOUNT` — QTS/Qsirch account used for server-to-server search. - `QSIRCH_PASSWORD` — password for the account. - `QSIRCH_PATH_PREFIX` — path prefix that contains the branch folders. - Example: `/Niederlassungen` Optional Qsirch tuning: - `QSIRCH_DATE_FIELD` - Allowed values: `modified` | `created` (case-insensitive) - Default: `modified` - `QSIRCH_MODE` - Allowed values: `sync` | `async` | `auto` (case-insensitive) - Default: `sync` Notes: - The current implementation is **sync-first**. - `auto` currently behaves like `sync` (placeholder for a later async implementation). **Normalization rule (A.1)** - `lib/config/validateEnv.js` accepts these values case-insensitively. - Runtime normalizes Qsirch values (trim + lowercase for `QSIRCH_DATE_FIELD` / `QSIRCH_MODE`, and trim for `QSIRCH_PATH_PREFIX`) so behavior matches validation. The environment can be validated via: - `lib/config/validateEnv.js` - `scripts/validate-env.mjs` In Docker/production-like runs, execute `node scripts/validate-env.mjs` before starting the server to fail fast. > Note: You may see a Node warning like `MODULE_TYPELESS_PACKAGE_JSON` when running env validation. > The validation still succeeds; the warning is harmless. --- ## 2. Authentication & Authorization ### 2.1 Sessions Authentication uses a signed JWT stored in an HTTP-only cookie (`auth_session`). To access protected endpoints: 1. `POST /api/auth/login` to obtain the cookie. 2. Send subsequent requests with that cookie. Notes: - In production-like setups, cookies should be `Secure` and the app should run behind HTTPS. - For local HTTP testing (`http://localhost:3000`), you may set `SESSION_COOKIE_SECURE=false` in your local docker env file. ### 2.2 RBAC: Branch access (RHL-021 / RHL-041) RBAC is enforced on filesystem-related endpoints. Role model: - `branch` — restricted to own branch - `admin` — access all branches - `superadmin` — access all branches - `dev` — access all branches Response semantics: - **401 Unauthorized**: no valid session - **403 Forbidden**: session exists but branch access is not allowed ### 2.3 User management authorization (RHL-041 prerequisite for RHL-012) User management is a separate capability from branch access. Rules: - Allowed: `superadmin`, `dev` - Forbidden: `admin`, `branch` When an endpoint is guarded by this capability, it must return: - `403 AUTH_FORBIDDEN_USER_MANAGEMENT` ```json { "error": { "message": "Forbidden", "code": "AUTH_FORBIDDEN_USER_MANAGEMENT" } } ``` Notes: - RHL-041 does **not** introduce user management endpoints. - RHL-012 will add the actual user management endpoints and must enforce this capability consistently. --- ## 3. Error Handling & Conventions ### 3.1 Standard error response format Most endpoints return JSON. Success responses keep their existing shapes (**unchanged**). Error responses always use this standardized shape: ```json { "error": { "message": "Human readable message", "code": "SOME_MACHINE_CODE", "details": {} } } ``` Notes: - `error.message` is intended for humans (UI, logs). - `error.code` is a stable machine-readable identifier (frontend handling, tests, monitoring). - `error.details` is optional. When present, it must be a JSON object (e.g. validation info). **Binary endpoints** Some endpoints may return a non-JSON body on the **200** happy path (for example `application/pdf`). Rules for such endpoints: - On success: return the documented binary payload. - On non-200 errors: return the standardized JSON error payload above. ### 3.2 Status code rules The API uses the following status codes consistently: - `400` — invalid/missing parameters, validation errors - `401` — unauthenticated (missing/invalid session) or invalid credentials - `403` — authenticated but not allowed (RBAC / capability mismatch) - `404` — resource not found (branch/year/month/day/file does not exist) - `500` — unexpected server errors (internal failures) ### 3.3 Common error codes The API uses these machine-readable codes (non-exhaustive list): - Auth: - `AUTH_UNAUTHENTICATED` - `AUTH_INVALID_CREDENTIALS` - `AUTH_FORBIDDEN_BRANCH` - `AUTH_FORBIDDEN_USER_MANAGEMENT` (RHL-041; used by RHL-012) - Validation (generic): - `VALIDATION_MISSING_PARAM` - `VALIDATION_MISSING_QUERY` - `VALIDATION_INVALID_JSON` - `VALIDATION_INVALID_BODY` - `VALIDATION_MISSING_FIELD` - Validation (password management): - `VALIDATION_WEAK_PASSWORD` - Validation (filesystem route params): - `VALIDATION_BRANCH` - `VALIDATION_YEAR` - `VALIDATION_MONTH` - `VALIDATION_DAY` - `VALIDATION_FILENAME` - `VALIDATION_FILE_EXTENSION` - `VALIDATION_PATH_TRAVERSAL` - Validation (search): - `VALIDATION_SEARCH_SCOPE` - `VALIDATION_SEARCH_BRANCH` - `VALIDATION_SEARCH_BRANCHES` - `VALIDATION_SEARCH_DATE` - `VALIDATION_SEARCH_RANGE` - `VALIDATION_SEARCH_LIMIT` - `VALIDATION_SEARCH_CURSOR` - `VALIDATION_SEARCH_MISSING_FILTER` - Storage: - `FS_NOT_FOUND` - `FS_STORAGE_ERROR` - Search (backend/provider): - `SEARCH_BACKEND_UNAVAILABLE` (provider misconfiguration/unavailability) - Internal: - `INTERNAL_SERVER_ERROR` ### 3.4 Implementation notes Route handlers use shared helpers: - `lib/api/errors.js` (standard error payloads + `withErrorHandling`) - `lib/api/storageErrors.js` (maps filesystem errors like ENOENT to 404 vs 500) ### 3.5 Testing note - For realistic `Secure` cookie behavior, prefer HTTPS. - For local testing on `http://localhost`, many tools/browsers treat localhost as a special-case “secure context”. Behavior may vary between environments. ### 3.6 Caching & Freshness (RHL-006) This project reads delivery-note PDFs from a NAS mount. New scans can appear at any time. The caching strategy is designed to: - show new files predictably - reduce filesystem load where possible - avoid any accidental shared caching for auth-protected responses #### 3.6.1 HTTP caching policy All JSON API responses explicitly disable HTTP caching: - `Cache-Control: no-store` This is applied centrally by `lib/api/errors.js` (the `json()` / `jsonError()` helpers). The policy applies to both success and error responses. For binary endpoints (e.g. PDF streaming), the route handler must explicitly set: - `Cache-Control: no-store` #### 3.6.2 Next.js route handler execution All API route handlers are forced to execute dynamically at request time: - each route exports `export const dynamic = "force-dynamic";` This avoids any static/ISR-like behavior for NAS-dependent endpoints and keeps auth/RBAC behavior safe. #### 3.6.3 Storage micro-cache (server-side TTL) To reduce repeated `fs.readdir()` calls, `lib/storage.js` implements a small process-local TTL cache **after** RBAC is enforced (RBAC is checked in the route handlers). TTL defaults: - `listBranches()` / `listYears()` → **60 seconds** - `listMonths()` / `listDays()` / `listFiles()` → **15 seconds** Important: - TTL is a **max staleness** guarantee (new files may appear immediately; they must appear after TTL). - Cache is **process-local** (not shared across multiple app instances). #### 3.6.4 Frontend fetch guidelines Frontend code should call these endpoints with explicit “fresh data” settings: - Use credentials: ```js fetch(url, { credentials: "include", cache: "no-store" }); ``` - Do not rely on `next: { revalidate: ... }` for these endpoints. Freshness is controlled via: - `Cache-Control: no-store` (HTTP) - server-side storage TTL micro-cache --- ## 4. Endpoints ### 4.1 `GET /api/health` **Purpose** Health check endpoint: - Verifies database connectivity (`db.command({ ping: 1 })`). - Verifies readability of `NAS_ROOT_PATH`. **Authentication**: not required. **Response 200 (example)** ```json { "db": "ok", "nas": { "path": "/mnt/niederlassungen", "entriesSample": ["@Recently-Snapshot", "NL01", "NL02"] } } ``` --- ### 4.2 `POST /api/auth/login` **Purpose** Authenticate a user and set the session cookie. **Authentication**: not required. **Request body (JSON)** ```json { "username": "example.user", "password": "plain-text-password" } ``` **Responses** - `200 { "ok": true }` - `400` (invalid JSON/body) - `400` (missing username/password) - `401` (invalid credentials) - `500` --- ### 4.3 `GET /api/auth/logout` **Purpose** Destroy the current session by clearing the cookie. **Authentication**: recommended (but endpoint is idempotent). **Response** - `200 { "ok": true }` --- ### 4.4 `GET /api/auth/me` **Purpose** Provide the current session identity for frontend consumers. Semantics (frontend-friendly): - 200 with `{ user: null }` when unauthenticated - 200 with `{ user: { userId, role, branchId, email } }` when authenticated Notes: - `role` is one of: `branch | admin | superadmin | dev`. - `email` is optional and may be `null`. --- ### 4.5 `POST /api/auth/change-password` (RHL-009) Change password for the currently authenticated user. --- ### 4.6 `GET /api/branches` Returns the list of branches (e.g. `['NL01', 'NL02']`). **Authentication**: required. **RBAC behavior** - `branch` role → only own branch - `admin` / `superadmin` / `dev` → all branches **Response 200** ```json { "branches": ["NL01", "NL02"] } ``` --- ### 4.7 `GET /api/branches/[branch]/years` Example: `/api/branches/NL01/years` **Authentication**: required. --- ### 4.8 `GET /api/branches/[branch]/[year]/months` Example: `/api/branches/NL01/2024/months` **Authentication**: required. --- ### 4.9 `GET /api/branches/[branch]/[year]/[month]/days` Example: `/api/branches/NL01/2024/10/days` **Authentication**: required. --- ### 4.10 `GET /api/files?branch=&year=&month=&day=` Returns files for a given day. **Authentication**: required. --- ### 4.11 `GET /api/files/:branch/:year/:month/:day/:filename` **Purpose** Stream (or download) a single PDF file from the NAS while enforcing authentication and branch-level RBAC. --- ### 4.12 `GET /api/search` **Purpose** Search delivery note content across PDFs. **Authentication**: required. **RBAC behavior** - `branch` role: - results are limited to the user’s `branchId` - attempting to query other branches returns `403 AUTH_FORBIDDEN_BRANCH` - `admin` / `superadmin` / `dev` role: - can search across branches using explicit scope parameters Filter rule: - The backend requires at least one of: `q`, `from`, `to`. - If all three are missing, the API returns `400 VALIDATION_SEARCH_MISSING_FILTER`. --- ## 5. API v1 freeze (RHL-008) The endpoints and response shapes documented here (and in `docs/frontend-api-usage.md`) are considered **API v1** for the first frontend implementation. Rules: - Avoid breaking changes to existing endpoints, parameters, or response shapes. - Prefer additive changes (new endpoints, new optional fields). - If a breaking change becomes necessary, introduce a new endpoint rather than modifying the v1 contract. --- ## 6. Adding New Endpoints When adding new endpoints: 1. Define URL + method. 2. Implement a `route.js` under `app/api/...`. 3. Enforce auth: - `getSession()` for protected endpoints - return `401 AUTH_UNAUTHENTICATED` when session is missing 4. Enforce branch RBAC (when applicable): - `canAccessBranch(session, branch)` - return `403 AUTH_FORBIDDEN_BRANCH` when forbidden 5. Enforce user-management capability (only for user management APIs; RHL-012): - `requireUserManagement(session)` - return `403 AUTH_FORBIDDEN_USER_MANAGEMENT` when forbidden 6. Use the standardized error contract: - `lib/api/errors.js` (`withErrorHandling`, `ApiError`, helpers) 7. Add route tests (Vitest). 8. Update this document.