|
|
@@ -1,449 +0,0 @@
|
|
|
-# Storage Module (`lib/storage`)
|
|
|
-
|
|
|
-The `lib/storage` module is the **single source of truth** for reading files
|
|
|
-from the network file share that contains scanned delivery notes.
|
|
|
-
|
|
|
-All code that needs to read from the NAS must go through this module instead
|
|
|
-of using Node.js `fs` directly. This keeps filesystem logic centralized and
|
|
|
-makes it easier to change paths or conventions later.
|
|
|
-
|
|
|
-## High-Level Responsibilities
|
|
|
-
|
|
|
-- Resolve paths under the NAS root (`NAS_ROOT_PATH`).
|
|
|
-- Provide high-level, intention-revealing helpers:
|
|
|
-
|
|
|
- - `listBranches()` → `['NL01', 'NL02', ...]`
|
|
|
- - `listYears(branch)` → `['2023', '2024', ...]`
|
|
|
- - `listMonths(branch, year)` → `['01', '02', ...]`
|
|
|
- - `listDays(branch, year, month)` → `['01', '02', ...]`
|
|
|
- - `listFiles(branch, year, month, day)` → `[{ name, relativePath }, ...]`
|
|
|
-
|
|
|
-- Enforce **read-only** access from the filesystem (no delete/move/write logic
|
|
|
- here).
|
|
|
-- Use asynchronous filesystem APIs (`fs/promises`) to avoid blocking the
|
|
|
- event loop when reading from a network filesystem (SMB). Using async I/O is a
|
|
|
- recommended best practice in Node.js for scalability and performance.
|
|
|
-
|
|
|
-## Environment Configuration
|
|
|
-
|
|
|
-The storage module depends on a single environment variable:
|
|
|
-
|
|
|
-- `NAS_ROOT_PATH`
|
|
|
-
|
|
|
- Absolute path where the NAS share is mounted on the host.
|
|
|
-
|
|
|
-Typical values:
|
|
|
-
|
|
|
-- **Production (Linux server):**
|
|
|
-
|
|
|
- ```env
|
|
|
- NAS_ROOT_PATH=/mnt/niederlassungen
|
|
|
- ```
|
|
|
-
|
|
|
-- **Local development (optional):**
|
|
|
-
|
|
|
- ```env
|
|
|
- # Example: local test folder
|
|
|
- NAS_ROOT_PATH=/Users/<username>/dev/test/niederlassungen
|
|
|
- ```
|
|
|
-
|
|
|
- or, if the NAS is mounted locally (e.g. on macOS):
|
|
|
-
|
|
|
- ```env
|
|
|
- NAS_ROOT_PATH=/Volumes/Niederlassungen
|
|
|
- ```
|
|
|
-
|
|
|
-If `NAS_ROOT_PATH` is not set, the helpers will throw when called. This is
|
|
|
-intentional: configuration issues should fail fast instead of causing
|
|
|
-confusing downstream errors.
|
|
|
-
|
|
|
-## Directory Layout Assumptions
|
|
|
-
|
|
|
-The helpers assume the following structure under `NAS_ROOT_PATH`:
|
|
|
-
|
|
|
-```text
|
|
|
-NAS_ROOT_PATH/
|
|
|
- @Recently-Snapshot/ # ignored
|
|
|
- NL01/
|
|
|
- 2024/
|
|
|
- 10/
|
|
|
- 23/
|
|
|
- file1.pdf
|
|
|
- file2.pdf
|
|
|
- NL02/
|
|
|
- 2023/
|
|
|
- 12/
|
|
|
- 01/
|
|
|
- ...
|
|
|
- ...
|
|
|
-```
|
|
|
-
|
|
|
-Rules:
|
|
|
-
|
|
|
-- Branch directories follow the pattern `NL<Number>`, e.g. `NL01`, `NL23`.
|
|
|
-- Year directories are 4-digit numeric (`2023`, `2024`, ...).
|
|
|
-- Month and day directories are numeric; the helpers normalize them to
|
|
|
- two‑digit strings for consistent display in the UI:
|
|
|
-
|
|
|
- - Months: `"01"` … `"12"`
|
|
|
- - Days: `"01"` … `"31"`
|
|
|
-
|
|
|
-- Only `.pdf` files are returned by `listFiles`.
|
|
|
-
|
|
|
-If the on-disk structure changes, update the logic in `lib/storage` only.
|
|
|
-API routes and UI components should not need to know about the exact layout.
|
|
|
-
|
|
|
-## Helper Functions
|
|
|
-
|
|
|
-All helper functions are asynchronous and return Promises.
|
|
|
-
|
|
|
-### `listBranches(): Promise<string[]>`
|
|
|
-
|
|
|
-Returns the list of branch directories (`NLxx`) under `NAS_ROOT_PATH`.
|
|
|
-
|
|
|
-- Ignores `@Recently-Snapshot`.
|
|
|
-- Filters for names matching `^NL\d+$` (case-insensitive).
|
|
|
-- Sorts branches numerically by their suffix (`NL1`, `NL2`, …, `NL10`).
|
|
|
-
|
|
|
-Example result:
|
|
|
-
|
|
|
-```json
|
|
|
-["NL01", "NL02", "NL03"]
|
|
|
-```
|
|
|
-
|
|
|
-### `listYears(branch: string): Promise<string[]>`
|
|
|
-
|
|
|
-Reads the year directories for a given branch.
|
|
|
-
|
|
|
-- Path: `${NAS_ROOT_PATH}/${branch}`
|
|
|
-- Filters for directories matching `^\d{4}$`.
|
|
|
-- Returns sorted year strings as `['2023', '2024', ...]`.
|
|
|
-
|
|
|
-### `listMonths(branch: string, year: string): Promise<string[]>`
|
|
|
-
|
|
|
-Reads the month directories for the given `branch` and `year`.
|
|
|
-
|
|
|
-- Path: `${NAS_ROOT_PATH}/${branch}/${year}`
|
|
|
-- Filters for directories matching `^\d{1,2}$`.
|
|
|
-- Normalizes month names to two digits (e.g. `'1' → '01'`).
|
|
|
-- Returns sorted month strings.
|
|
|
-
|
|
|
-Example result:
|
|
|
-
|
|
|
-```json
|
|
|
-["01", "02", "03", "10"]
|
|
|
-```
|
|
|
-
|
|
|
-### `listDays(branch: string, year: string, month: string): Promise<string[]>`
|
|
|
-
|
|
|
-Reads the day directories for the given `branch`, `year`, and `month`.
|
|
|
-
|
|
|
-- Path: `${NAS_ROOT_PATH}/${branch}/${year}/${month}`.
|
|
|
-- Filters for directories matching `^\d{1,2}$`.
|
|
|
-- Normalizes day names to two digits (e.g. `'3' → '03'`).
|
|
|
-- Returns sorted day strings.
|
|
|
-
|
|
|
-Example result:
|
|
|
-
|
|
|
-```json
|
|
|
-["01", "02", "03", "23"]
|
|
|
-```
|
|
|
-
|
|
|
-### `listFiles(branch: string, year: string, month: string, day: string): Promise<{ name: string; relativePath: string }[]>`
|
|
|
-
|
|
|
-Reads all PDF files for the given `branch`, `year`, `month`, and `day`.
|
|
|
-
|
|
|
-- Path: `${NAS_ROOT_PATH}/${branch}/${year}/${month}/${day}`.
|
|
|
-- Filters for files whose names end with `.pdf` (case-insensitive).
|
|
|
-- Sorts filenames alphabetically.
|
|
|
-- Returns an array of objects with:
|
|
|
-
|
|
|
- - `name`: the raw filename (e.g. `"Stapel-1_Seiten-1_Zeit-1048.pdf"`).
|
|
|
- - `relativePath`: the relative path from `NAS_ROOT_PATH` (e.g.
|
|
|
- `"NL01/2024/10/23/Stapel-1_Seiten-1_Zeit-1048.pdf"`).
|
|
|
-
|
|
|
-Example result:
|
|
|
-
|
|
|
-```json
|
|
|
-[
|
|
|
- {
|
|
|
- "name": "Stapel-1_Seiten-1_Zeit-1048.pdf",
|
|
|
- "relativePath": "NL01/2024/10/23/Stapel-1_Seiten-1_Zeit-1048.pdf"
|
|
|
- },
|
|
|
- {
|
|
|
- "name": "Stapel-1_Seiten-2_Zeit-1032.pdf",
|
|
|
- "relativePath": "NL01/2024/10/23/Stapel-1_Seiten-2_Zeit-1032.pdf"
|
|
|
- }
|
|
|
-]
|
|
|
-```
|
|
|
-
|
|
|
-## Error Handling
|
|
|
-
|
|
|
-`lib/storage` does **not** swallow errors:
|
|
|
-
|
|
|
-- If a folder does not exist or is not accessible, the underlying
|
|
|
- `fs.promises.readdir` call will throw (e.g. `ENOENT`, `EACCES`).
|
|
|
-- Callers (API routes, services) are responsible for catching these errors and
|
|
|
- converting them into appropriate HTTP responses.
|
|
|
-
|
|
|
-This separation keeps responsibilities clear:
|
|
|
-
|
|
|
-- `lib/storage` → _How do we read data from the filesystem?_
|
|
|
-- API layer (`app/api/.../route.js`) → _How do we map errors to HTTP responses?_
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-# 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.
|
|
|
-
|
|
|
-> **Note:** Authentication and authorization are not implemented yet. In the
|
|
|
-> final system, branch users should only see their own branch, while admins
|
|
|
-> can access all branches.
|
|
|
-
|
|
|
-## General Conventions
|
|
|
-
|
|
|
-- All endpoints return JSON.
|
|
|
-
|
|
|
-- Successful responses use HTTP status `200`.
|
|
|
-
|
|
|
-- Error responses use `4xx` or `5xx` and have the shape:
|
|
|
-
|
|
|
- ```json
|
|
|
- { "error": "Human-readable error message" }
|
|
|
- ```
|
|
|
-
|
|
|
-- Route Handlers are implemented in `app/api/.../route.js` using the standard
|
|
|
- Web `Request` / `Response` primitives as described in the Next.js
|
|
|
- documentation.
|
|
|
-
|
|
|
-- Filesystem access must use `lib/storage` (no direct `fs` calls inside
|
|
|
- route handlers).
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## Health Check
|
|
|
-
|
|
|
-### `GET /api/health`
|
|
|
-
|
|
|
-**Purpose**
|
|
|
-
|
|
|
-Check whether:
|
|
|
-
|
|
|
-- The database is reachable.
|
|
|
-- The NAS root path (`NAS_ROOT_PATH`) is readable from the app container.
|
|
|
-
|
|
|
-**Response 200 (example)**
|
|
|
-
|
|
|
-```json
|
|
|
-{
|
|
|
- "db": "OK",
|
|
|
- "nas": {
|
|
|
- "path": "/mnt/niederlassungen",
|
|
|
- "entriesSample": ["@Recently-Snapshot", "NL01", "NL02", "NL03", "NL04"]
|
|
|
- }
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-**Error cases**
|
|
|
-
|
|
|
-- If the database is not reachable, the `db` field contains an error message.
|
|
|
-- If the NAS path cannot be read, the `nas` field contains an error string,
|
|
|
- e.g. `"error: ENOENT: no such file or directory, scandir '/mnt/niederlassungen'"`.
|
|
|
-
|
|
|
-This endpoint is intended for operations/monitoring and quick manual checks.
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## Delivery Notes Hierarchy
|
|
|
-
|
|
|
-The following endpoints reflect the filesystem hierarchy:
|
|
|
-
|
|
|
-> `NAS_ROOT_PATH` → Branch → Year → Month → Day → PDF files
|
|
|
-
|
|
|
-### `GET /api/branches`
|
|
|
-
|
|
|
-List all branch directories based on the names under `NAS_ROOT_PATH`.
|
|
|
-
|
|
|
-**Response 200**
|
|
|
-
|
|
|
-```json
|
|
|
-{
|
|
|
- "branches": ["NL01", "NL02", "NL03"]
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-**Errors**
|
|
|
-
|
|
|
-- `500` – Internal error (e.g. filesystem error, missing `NAS_ROOT_PATH`).
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### `GET /api/branches/[branch]/years`
|
|
|
-
|
|
|
-Example: `/api/branches/NL01/years`
|
|
|
-
|
|
|
-Return all year folders for a given branch.
|
|
|
-
|
|
|
-**Response 200**
|
|
|
-
|
|
|
-```json
|
|
|
-{
|
|
|
- "branch": "NL01",
|
|
|
- "years": ["2023", "2024"]
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-**Errors**
|
|
|
-
|
|
|
-- `400` – `branch` parameter is missing (indicates a route/handler bug).
|
|
|
-- `500` – Error while reading year directories.
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### `GET /api/branches/[branch]/[year]/months`
|
|
|
-
|
|
|
-Example: `/api/branches/NL01/2024/months`
|
|
|
-
|
|
|
-Return all month folders for the given branch and year.
|
|
|
-
|
|
|
-**Response 200**
|
|
|
-
|
|
|
-```json
|
|
|
-{
|
|
|
- "branch": "NL01",
|
|
|
- "year": "2024",
|
|
|
- "months": ["01", "02", "03", "10"]
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-**Notes**
|
|
|
-
|
|
|
-- Months are returned as two‑digit strings (`"01"` … `"12"`) so that UI
|
|
|
- code does not need to handle formatting.
|
|
|
-
|
|
|
-**Errors**
|
|
|
-
|
|
|
-- `400` – `branch` or `year` parameter is missing.
|
|
|
-- `500` – Filesystem or configuration error.
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### `GET /api/branches/[branch]/[year]/[month]/days`
|
|
|
-
|
|
|
-Example: `/api/NL01/2024/10/days`
|
|
|
-
|
|
|
-Return all day folders for the given branch, year, and month.
|
|
|
-
|
|
|
-**Response 200**
|
|
|
-
|
|
|
-```json
|
|
|
-{
|
|
|
- "branch": "NL01",
|
|
|
- "year": "2024",
|
|
|
- "month": "10",
|
|
|
- "days": ["01", "02", "03", "23"]
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-**Notes**
|
|
|
-
|
|
|
-- Days are returned as two‑digit strings (`"01"` … `"31"`).
|
|
|
-
|
|
|
-**Errors**
|
|
|
-
|
|
|
-- `400` – `branch`, `year`, or `month` parameter is missing.
|
|
|
-- `500` – Filesystem or configuration error.
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### `GET /api/files?branch=&year=&month=&day=`
|
|
|
-
|
|
|
-Example:
|
|
|
-
|
|
|
-```text
|
|
|
-/api/files?branch=NL01&year=2024&month=10&day=23
|
|
|
-```
|
|
|
-
|
|
|
-Return the list of PDF files for a specific branch and date.
|
|
|
-
|
|
|
-**Query parameters**
|
|
|
-
|
|
|
-- `branch` – branch identifier (e.g. `NL01`).
|
|
|
-- `year` – four‑digit year (e.g. `2024`).
|
|
|
-- `month` – month (e.g. `10`).
|
|
|
-- `day` – day (e.g. `23`).
|
|
|
-
|
|
|
-**Response 200**
|
|
|
-
|
|
|
-```json
|
|
|
-{
|
|
|
- "branch": "NL01",
|
|
|
- "year": "2024",
|
|
|
- "month": "10",
|
|
|
- "day": "23",
|
|
|
- "files": [
|
|
|
- {
|
|
|
- "name": "Stapel-1_Seiten-1_Zeit-1048.pdf",
|
|
|
- "relativePath": "NL01/2024/10/23/Stapel-1_Seiten-1_Zeit-1048.pdf"
|
|
|
- },
|
|
|
- {
|
|
|
- "name": "Stapel-1_Seiten-2_Zeit-1032.pdf",
|
|
|
- "relativePath": "NL01/2024/10/23/Stapel-1_Seiten-2_Zeit-1032.pdf"
|
|
|
- }
|
|
|
- ]
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-**Errors**
|
|
|
-
|
|
|
-- `400` – one or more required query parameters are missing.
|
|
|
-- `500` – filesystem error while reading the day directory or files.
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## Adding New Endpoints
|
|
|
-
|
|
|
-When adding new endpoints:
|
|
|
-
|
|
|
-1. **Define the URL and method first**, e.g.:
|
|
|
-
|
|
|
- - `GET /api/file?path=...` (download a single PDF)
|
|
|
- - `GET /api/search?branch=&query=...` (full‑text search via Qsirch)
|
|
|
-
|
|
|
-2. **Create a `route.js` file** in `app/api/...` following Next.js 16 Route
|
|
|
- Handler conventions. For dynamic routes, use the `(request, ctx)` signature
|
|
|
- and resolve parameters via `const params = await ctx.params`.
|
|
|
-
|
|
|
-3. **Use `lib/storage` for filesystem access** instead of calling `fs`
|
|
|
- directly inside route handlers. If needed, add new helpers to
|
|
|
- `lib/storage`.
|
|
|
-
|
|
|
-4. **Handle errors explicitly** with `try/catch` in the handler and return
|
|
|
- `4xx/5xx` responses with clear `error` messages.
|
|
|
-
|
|
|
-5. **Update this document** to describe the new endpoint (URL, purpose,
|
|
|
- parameters, sample responses, error cases).
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## Future Extensions
|
|
|
-
|
|
|
-- **Authentication & Authorization**
|
|
|
-
|
|
|
- - Enforce branch‑level access control (branch user vs. admin).
|
|
|
- - Likely implemented using JWT stored in cookies and a shared helper
|
|
|
- (e.g. `lib/auth`) plus a `middleware.js` or per‑route checks.
|
|
|
-
|
|
|
-- **Search Endpoints (Qsirch)**
|
|
|
-
|
|
|
- - Integrate with QNAP Qsirch via its HTTP API.
|
|
|
- - Provide endpoints like `GET /api/search?branch=&query=&from=&to=`.
|
|
|
-
|
|
|
-- **File Download / Preview**
|
|
|
-
|
|
|
- - Add endpoints for streaming PDF content from the NAS to the browser
|
|
|
- with appropriate `Content-Type` and `Content-Disposition` headers.
|