Browse Source

RHL-008-refactor(docs): update API documentation to include new /api/auth/me endpoint and frontend API usage guide

Code_Uwe 1 tháng trước cách đây
mục cha
commit
ced4c4e88c
4 tập tin đã thay đổi với 563 bổ sung33 xóa
  1. 47 16
      Docs/api.md
  2. 27 1
      Docs/auth.md
  3. 431 0
      Docs/frontend-api-usage.md
  4. 58 16
      Docs/runbook.md

+ 47 - 16
Docs/api.md

@@ -1,19 +1,11 @@
-<!-- --------------------------------------------------------------------------- -->
-
-<!-- Ordner: Docs -->
-
-<!-- Datei: api.md -->
-
-<!-- Relativer Pfad: Docs/api.md -->
-
-<!-- --------------------------------------------------------------------------- -->
-
 # 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
@@ -315,7 +307,34 @@ Destroy the current session by clearing the cookie.
 
 ---
 
-### 4.4 `GET /api/branches`
+### 4.4 `GET /api/auth/me`
+
+**Purpose**
+
+Return the current session identity for frontend consumers.
+
+Rationale:
+
+- Frontends should not use `401` as basic control flow to determine “am I logged in?”.
+- This endpoint provides a stable, frontend-friendly session check.
+
+**Authentication**: not required.
+
+**Response 200 (unauthenticated)**
+
+```json
+{ "user": null }
+```
+
+**Response 200 (authenticated)**
+
+```json
+{ "user": { "userId": "...", "role": "branch|admin|dev", "branchId": "NL01" } }
+```
+
+---
+
+### 4.5 `GET /api/branches`
 
 Returns the list of branches (e.g. `["NL01", "NL02"]`).
 
@@ -350,7 +369,7 @@ Returns the list of branches (e.g. `["NL01", "NL02"]`).
 
 ---
 
-### 4.5 `GET /api/branches/[branch]/years`
+### 4.6 `GET /api/branches/[branch]/years`
 
 Example: `/api/branches/NL01/years`
 
@@ -372,7 +391,7 @@ Example: `/api/branches/NL01/years`
 
 ---
 
-### 4.6 `GET /api/branches/[branch]/[year]/months`
+### 4.7 `GET /api/branches/[branch]/[year]/months`
 
 Example: `/api/branches/NL01/2024/months`
 
@@ -394,7 +413,7 @@ Example: `/api/branches/NL01/2024/months`
 
 ---
 
-### 4.7 `GET /api/branches/[branch]/[year]/[month]/days`
+### 4.8 `GET /api/branches/[branch]/[year]/[month]/days`
 
 Example: `/api/branches/NL01/2024/10/days`
 
@@ -416,7 +435,7 @@ Example: `/api/branches/NL01/2024/10/days`
 
 ---
 
-### 4.8 `GET /api/files?branch=&year=&month=&day=`
+### 4.9 `GET /api/files?branch=&year=&month=&day=`
 
 Example:
 
@@ -448,7 +467,19 @@ Example:
 
 ---
 
-## 5. Adding New Endpoints
+## 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:
 

+ 27 - 1
Docs/auth.md

@@ -28,7 +28,7 @@ This document covers:
 - Environment variables related to auth.
 - Roles and RBAC rules.
 - Session payload and cookie configuration.
-- Login and logout endpoints.
+- Login/logout/me endpoints.
 
 ---
 
@@ -268,6 +268,32 @@ Clears the session cookie.
 - Returns `200 { "ok": true }` on success.
 - Logout is idempotent.
 
+### 6.3 `GET /api/auth/me`
+
+Return the current session identity for frontend consumers.
+
+Rationale:
+
+- Frontends should not use `401` as basic control flow to determine “am I logged in?”.
+- `/api/auth/me` provides a stable, low-friction session check.
+
+Response (unauthenticated):
+
+```json
+{ "user": null }
+```
+
+Response (authenticated):
+
+```json
+{ "user": { "userId": "...", "role": "branch|admin|dev", "branchId": "NL01" } }
+```
+
+Security note:
+
+- The endpoint intentionally returns only the minimal session identity.
+- It does not reveal password hashes or user database internals.
+
 ---
 
 ## 7. Security Notes

+ 431 - 0
Docs/frontend-api-usage.md

@@ -0,0 +1,431 @@
+# 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.
+
+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;
+	}
+}
+```
+
+---
+
+## 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=<user> \
+  --password=<pw> \
+  --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=<user> \
+  --password=<pw> \
+  --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=<user> \
+  --password=<pw> \
+  --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.

+ 58 - 16
Docs/runbook.md

@@ -1,13 +1,3 @@
-<!-- --------------------------------------------------------------------------- -->
-
-<!-- Ordner: Docs -->
-
-<!-- Datei: runbook.md -->
-
-<!-- Relativer Pfad: Docs/runbook.md -->
-
-<!-- --------------------------------------------------------------------------- -->
-
 # Runbook: Local Development vs Server Deployment
 
 This runbook describes how to run the project locally (developer machine) and on the internal server.
@@ -95,6 +85,12 @@ Run with the local override:
 docker compose -f docker-compose.yml -f docker-compose.local.yml up --build
 ```
 
+If you prefer running in the background:
+
+```bash
+docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --build
+```
+
 ### 2.5 Verify health
 
 ```bash
@@ -125,8 +121,6 @@ In `mongosh`:
 ```js
 use rhl-lieferscheine
 
-show collections
-
 db.users.insertOne({
   username: "branchuser",
   email: "nl01@example.com",
@@ -138,7 +132,7 @@ db.users.insertOne({
 })
 ```
 
-### 2.7 Login + call protected endpoints
+### 2.7 Login + call protected endpoints (curl)
 
 Login (stores cookie in `cookies.txt`):
 
@@ -171,7 +165,42 @@ Logout:
 curl -i -b cookies.txt -c cookies.txt http://localhost:3000/api/auth/logout
 ```
 
-### 2.8 Notes for Git Bash on Windows
+### 2.8 Manual end-to-end flow using the apiClient (RHL-008)
+
+The repo contains a manual smoke-test script that exercises:
+
+- authentication
+- drill-down navigation (branches -> years -> months -> days -> files)
+- negative cases (401/403/400/404)
+
+Script:
+
+- `scripts/manual-api-client-flow.mjs`
+
+Run locally from your host machine:
+
+```bash
+node scripts/manual-api-client-flow.mjs \
+  --baseUrl=http://localhost:3000 \
+  --username=<user> \
+  --password=<pw> \
+  --branch=NL01
+```
+
+If your host machine does not have Node, you can also 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=<user> \
+  --password=<pw> \
+  --branch=NL01
+```
+
+> Note: You may see a Node warning about ESM detection (`MODULE_TYPELESS_PACKAGE_JSON`).
+> The script still works and the warning is harmless.
+
+### 2.9 Notes for Git Bash on Windows
 
 Git Bash may rewrite paths like `/mnt/niederlassungen`.
 
@@ -266,9 +295,22 @@ Expected:
 - `db` is `ok`
 - `nas.entriesSample` contains real branch folders (`NLxx`)
 
-> Note: On some Linux servers, `localhost` resolves to IPv6 (`::1`). If `curl http://localhost:3000` fails, use `127.0.0.1` or `curl -4`.
+> Note: On some Linux servers, `localhost` resolves to IPv6 (`::1`).
+> If `curl http://localhost:3000/api/health` fails, use `127.0.0.1` or `curl -4`.
+
+### 3.6 Manual end-to-end flow (recommended inside container)
+
+Run the manual smoke test inside the app container (no Node installation required on the host):
+
+```bash
+docker compose exec app node scripts/manual-api-client-flow.mjs \
+  --baseUrl=http://127.0.0.1:3000 \
+  --username=<user> \
+  --password=<pw> \
+  --branch=NL01
+```
 
-### 3.6 Logs and troubleshooting
+### 3.7 Logs and troubleshooting
 
 ```bash
 docker compose -f docker-compose.yml logs --tail=200 app