api.md 13 KB

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

    {
    	"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:

{
	"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:

    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)

{
	"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)

{ "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

{ "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.