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.
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.
The storage module depends on a single environment variable:
NAS_ROOT_PATHAbsolute path where the NAS share is mounted on the host.
Typical values:
Production (Linux server):
NAS_ROOT_PATH=/mnt/niederlassungen
Local development (optional):
# Example: local test folder
NAS_ROOT_PATH=/Users/<username>/dev/test/niederlassungen
or, if the NAS is mounted locally (e.g. on macOS):
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.
The helpers assume the following structure under NAS_ROOT_PATH:
NAS_ROOT_PATH/
@Recently-Snapshot/ # ignored
NL01/
2024/
10/
23/
file1.pdf
file2.pdf
NL02/
2023/
12/
01/
...
...
Rules:
NL<Number>, e.g. NL01, NL23.2023, 2024, ...).Month and day directories are numeric; the helpers normalize them to two‑digit strings for consistent display in the UI:
"01" … "12""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.
All helper functions are asynchronous and return Promises.
listBranches(): Promise<string[]>Returns the list of branch directories (NLxx) under NAS_ROOT_PATH.
@Recently-Snapshot.^NL\d+$ (case-insensitive).NL1, NL2, …, NL10).Example result:
["NL01", "NL02", "NL03"]
listYears(branch: string): Promise<string[]>Reads the year directories for a given branch.
${NAS_ROOT_PATH}/${branch}^\d{4}$.['2023', '2024', ...].listMonths(branch: string, year: string): Promise<string[]>Reads the month directories for the given branch and year.
${NAS_ROOT_PATH}/${branch}/${year}^\d{1,2}$.'1' → '01').Example result:
["01", "02", "03", "10"]
listDays(branch: string, year: string, month: string): Promise<string[]>Reads the day directories for the given branch, year, and month.
${NAS_ROOT_PATH}/${branch}/${year}/${month}.^\d{1,2}$.'3' → '03').Example result:
["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.
${NAS_ROOT_PATH}/${branch}/${year}/${month}/${day}..pdf (case-insensitive).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:
[
{
"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"
}
]
lib/storage does not swallow errors:
fs.promises.readdir call will throw (e.g. ENOENT, EACCES).This separation keeps responsibilities clear:
lib/storage → How do we read data from the filesystem?app/api/.../route.js) → How do we map errors to HTTP responses?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
/apiprefix.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.
All endpoints return JSON.
Successful responses use HTTP status 200.
Error responses use 4xx or 5xx and have the shape:
{ "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).
GET /api/healthPurpose
Check whether:
NAS_ROOT_PATH) is readable from the app container.Response 200 (example)
{
"db": "OK",
"nas": {
"path": "/mnt/niederlassungen",
"entriesSample": ["@Recently-Snapshot", "NL01", "NL02", "NL03", "NL04"]
}
}
Error cases
db field contains an error message.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.
The following endpoints reflect the filesystem hierarchy:
NAS_ROOT_PATH→ Branch → Year → Month → Day → PDF files
GET /api/branchesList all branch directories based on the names under NAS_ROOT_PATH.
Response 200
{
"branches": ["NL01", "NL02", "NL03"]
}
Errors
500 – Internal error (e.g. filesystem error, missing NAS_ROOT_PATH).GET /api/branches/[branch]/yearsExample: /api/branches/NL01/years
Return all year folders for a given branch.
Response 200
{
"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]/monthsExample: /api/branches/NL01/2024/months
Return all month folders for the given branch and year.
Response 200
{
"branch": "NL01",
"year": "2024",
"months": ["01", "02", "03", "10"]
}
Notes
"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]/daysExample: /api/NL01/2024/10/days
Return all day folders for the given branch, year, and month.
Response 200
{
"branch": "NL01",
"year": "2024",
"month": "10",
"days": ["01", "02", "03", "23"]
}
Notes
"01" … "31").Errors
400 – branch, year, or month parameter is missing.500 – Filesystem or configuration error.GET /api/files?branch=&year=&month=&day=Example:
/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
{
"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.When adding new endpoints:
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)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.
Use lib/storage for filesystem access instead of calling fs
directly inside route handlers. If needed, add new helpers to
lib/storage.
Handle errors explicitly with try/catch in the handler and return
4xx/5xx responses with clear error messages.
Update this document to describe the new endpoint (URL, purpose, parameters, sample responses, error cases).
Authentication & Authorization
lib/auth) plus a middleware.js or per‑route checks.Search Endpoints (Qsirch)
GET /api/search?branch=&query=&from=&to=.File Download / Preview
Content-Type and Content-Disposition headers.