This commit is contained in:
seb
2026-10-07 20:36:50 +02:00
parent 857963b1c3
commit a29ad50b74
18 changed files with 1024 additions and 220 deletions

View File

@@ -207,7 +207,13 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/pos/init.md`](routes/pos/init.md) | `GET /v1/init` |
| [`routes/pos/pimage.md`](routes/pos/pimage.md) | `GET /v1/pimage` |
| [`routes/v1/product.md`](routes/v1/product.md) | `GET /v1/product` |
| [`routes/vfs/list.md`](routes/vfs/list.md) | `GET /vfs` |
| [`routes/vfs/mkdir.md`](routes/vfs/mkdir.md) | `POST /vfs/mkdir` |
| [`routes/vfs/rename.md`](routes/vfs/rename.md) | `POST /vfs/rename` |
| [`routes/vfs/upload.md`](routes/vfs/upload.md) | `POST /vfs/upload` |
| [`routes/vfs/delete.md`](routes/vfs/delete.md) | `DELETE /vfs/{*path}` |
| [`routes/vfs/_*path.md`](routes/vfs/_*path.md) | `GET /vfs/{*path}` |
| [`routes/vfs/write.md`](routes/vfs/write.md) | `PUT /vfs/{*path}` |
| [`routes/warehouse/list.md`](routes/warehouse/list.md) | `GET /warehouse` |
| [`routes/warehouse/create.md`](routes/warehouse/create.md) | `POST /warehouse` |
| [`routes/warehouse/order.md`](routes/warehouse/order.md) | `PUT /warehouse/order` |
@@ -227,6 +233,7 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A
| `article-<sku>` | `PUT /article/{sku}/brand`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/eans`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}`, `PUT /article/{sku}/images/order`, `PUT /article/{sku}/prices`, `POST /article/{sku}/supplier`, `PUT /article/{sku}/supplier/order`, `DELETE /article/{sku}/supplier/{id}`, `PATCH /article/{sku}/supplier/{id}`, `PUT /article/{sku}/track_stock`, `PUT /article/{sku}/weight` |
| `articles` | `POST /article`, `DELETE /article/{sku}`, `PATCH /article/{sku}` |
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image` |
| `vfs` | `POST /vfs/mkdir`, `POST /vfs/rename`, `POST /vfs/upload`, `DELETE /vfs/{*path}`, `PUT /vfs/{*path}` |
## DESIGN

View File

@@ -1,6 +1,6 @@
# Dash API Routes Overview (All)
Overview of all 183 API endpoints across all modules.
Overview of all 189 API endpoints across all modules.
### Notation
@@ -575,9 +575,27 @@ _none_
#### JTL-POS product catalog synchronization — `GET /v1/product`
- Params: `query: lastChangedProduct?: str, limit?: int`
- Returns: `200 obj[]`
#### List virtual filesystem files — `GET /vfs`
_none_
- Returns: `200 { files: { name: str, size: int, mime: str, updated_at: str, created_at: str, isText: bool, isImage: bool, isPdf: bool }[] }`
#### Create directory in virtual filesystem — `POST /vfs/mkdir`
- Params: `body: { path: str }`
- Returns: `200 { success: bool, dir: str }`
#### Rename or move a virtual filesystem file or directory — `POST /vfs/rename`
- Params: `body: { oldPath: str, newPath: str }`
- Returns: `200 { success: bool, oldPath: str, newPath: str }`
#### Upload files to virtual filesystem — `POST /vfs/upload`
- Params: `form: { files: str[], targetDir?: str }`
- Returns: `200 { success: bool, uploaded: { name: str, size: int, mime: str }[] }`
#### Delete a virtual filesystem file — `DELETE /vfs/{*path}`
- Params: `path: str`
- Returns: `200 { success: bool, deleted: str }`
#### Get a virtual filesystem file — `GET /vfs/{*path}`
- Params: `path: str, query: download?: bool, raw?: bool`
- Returns: `200 str`
#### Create or overwrite a virtual filesystem file — `PUT /vfs/{*path}`
- Params: `path: str, body: { content: str, mime?: str }`
- Returns: `200 { success: bool, file: { name: str, size: int, mime: str, updated_at: str } }`
#### List warehouses — `GET /warehouse`
_none_
- Returns: `200 { warehouses: T37[] }`

View File

@@ -1,6 +1,6 @@
# Dash API Routes Overview
Overview of all 146 API endpoints across all modules.
Overview of all 145 API endpoints across all modules.
### Notation
@@ -460,9 +460,6 @@ _none_
#### Upsert unit — `PUT /unit`
- Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }`
- Returns: `200 { unit: { si: str, basesi: str, basesifactor: num, symbol: str, name: str } }`
#### Get a virtual filesystem file — `GET /vfs/{*path}`
- Params: `path: str, query: download?: bool, raw?: bool`
- Returns: `200 str`
#### List warehouses — `GET /warehouse`
_none_
- Returns: `200 { warehouses: T34[] }`

View File

@@ -4,42 +4,70 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
Get a virtual filesystem file
Returns a file the model produced in the `javascript_execute` sandbox
(virtual filesystem), for the authenticated user's selected company.
Returns a file from the virtual filesystem for the authenticated user's selected company.
Commonly used to retrieve artifacts generated by the model in the `javascript_execute` sandbox or uploaded by users.
Embed the returned URL in chat markdown to display a generated image:
`![title](/api/vfs/example.png)`.
### URL Format
Accepts:
- Relative VFS paths: `example.png`, `reports/summary.pdf`
- Full endpoint paths: `/vfs/example.png`, `/api/vfs/example.png` (leading `api/` and `vfs/` prefixes are automatically stripped)
- Absolute URLs: `https://.../api/vfs/example.png`
- Traversal (`..`), null bytes (`\0`), and backslashes (`\`) are strictly forbidden and rejected with HTTP 400.
Images are returned as a 250x250 aspect-preserving preview unless requested with
`download=1` or `raw=1`.
Browser-compatible files (HTML, CSS, JS, JSON, PDF, text, images, media) are served
inline so they open directly in the browser; other files are sent as downloads.
### Image Previews & Thumbnails
Supported raster images (`image/png`, `image/jpeg`, `image/gif`, `image/webp`, `image/avif`, `image/bmp`)
are automatically transformed into a **250×250 aspect-preserving preview thumbnail** (WebP format, quality 88):
- Scaled to fit within a 250×250 box without distortion (e.g. 2000×100 becomes 250×13).
- Images whose larger edge is below 100px are upscaled to 100px for icon legibility.
- A translucent dark metadata strip (`#101317`, max 50% height) with monospace text (`DejaVu Sans Mono`)
is rendered along the bottom edge, displaying original dimensions and file size.
- Served with `Content-Security-Policy: default-src 'none'; sandbox`.
- Use `?raw=1` (or `?raw=true`) to bypass preview generation and return the original unmodified image inline.
### Inline vs Attachment
- Browser-compatible types (HTML, CSS, JS, JSON, PDF, text, images, audio, video, fonts, WASM)
are served with `Content-Disposition: inline; filename="..."` so they open directly in modern browsers.
- Non-browser/binary types default to `Content-Disposition: attachment; filename="..."`.
- Use `?download=1` (or `?download=true`) to force file download with `Content-Disposition: attachment`.
### Caching & Conditional Requests
- Generates weak ETags (`W/"thumb-..."` for previews, `W/"<timestamp>-<mode>"` for originals).
- Sets `Cache-Control: private, max-age=0, must-revalidate`.
- Supports `If-None-Match` conditional requests returning HTTP `304 Not Modified`.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `path` | yes | string | VFS file path (e.g. `example.png`, `labels/a.png`) |
| `path` | yes | string | VFS file path (e.g. `example.png`, `charts/graph.svg`) |
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `download` | no | boolean | Force file download with Content-Disposition: attachment.
|
| `raw` | no | boolean | Return original unmodified image inline without generating a preview thumbnail.
|
| `download` | no | boolean | Force file download with Content-Disposition: attachment (1 or true). |
| `raw` | no | boolean | Return original unmodified image inline without generating a preview thumbnail (1 or true). |
## Response `200`
File bytes
File bytes. Served inline for browser-renderable formats or as a download.
Includes headers:
- `Content-Type`: MIME type of file, or `image/webp` for previews
- `Content-Length`: Size of served payload in bytes
- `Content-Disposition`: `inline; filename="..."` or `attachment; filename="..."`
- `Cache-Control`: `private, max-age=0, must-revalidate`
- `ETag`: Weak ETag identifier
- `X-Content-Type-Options`: `nosniff`
- `Content-Security-Policy`: `default-src 'none'; sandbox` (for preview thumbnails)
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid path, or no company selected — `{"error":"…"}` |
| 304 | Not modified (If-None-Match matched ETag) |
| 400 | Invalid path, path escapes VFS boundary, or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | File not found — `{"error":"…"}` |

View File

@@ -0,0 +1,42 @@
# `DELETE /vfs/{*path}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Delete a virtual filesystem file
Deletes a file from both the on-disk virtual filesystem and database for the authenticated user's selected company.
Deleting read-only database files (`image/*` or filenames containing 64-character hex hashes) is blocked with HTTP 403.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `path` | yes | string | VFS file path to delete (e.g. `temp.png`, `notes.txt`) |
## Signals
On success, emits SSE signal: `vfs` (see [`GET /events`](../events.md)).
## Response `200`
File successfully deleted
```json
{
"success": true,
"deleted": "…"
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid path, path traversal, or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized, or cannot delete read-only database files — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,43 @@
# `GET /vfs`
Protected. Requires a valid session cookie (`credentials: 'include'`).
List virtual filesystem files
Lists all virtual filesystem files for the authenticated user's selected company.
Scans directories recursively (ignoring hidden dotfiles) and returns file
metadata including path, byte size, detected MIME type, timestamps, and classification flags.
## Response `200`
List of virtual filesystem files
```json
{
"files": [
{
"name": "…",
"size": 1,
"mime": "…",
"updated_at": "2026-08-10T00:00:00.000Z",
"created_at": "2026-08-10T00:00:00.000Z",
"isText": true,
"isImage": true,
"isPdf": true
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,48 @@
# `POST /vfs/mkdir`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Create directory in virtual filesystem
Creates a directory (and any necessary parent directories) in the virtual filesystem
for the authenticated user's selected company.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `path` | yes | string | Directory path to create (e.g. `reports/2026/q1`) |
```json
{
"path": "…"
}
```
## Signals
On success, emits SSE signal: `vfs` (see [`GET /events`](../events.md)).
## Response `200`
Directory created successfully
```json
{
"success": true,
"dir": "…"
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Path is required, invalid characters, traversal, or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized, or cannot create directory inside read-only database paths — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,52 @@
# `POST /vfs/rename`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Rename or move a virtual filesystem file or directory
Renames or moves a file or directory within the virtual filesystem for the authenticated user's selected company.
Parent directories for the destination path are created automatically if needed.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `oldPath` | yes | string | Existing VFS file or directory path (e.g. `draft.txt`) |
| `newPath` | yes | string | New destination VFS path (e.g. `archive/final.txt`) |
```json
{
"oldPath": "…",
"newPath": "…"
}
```
## Signals
On success, emits SSE signal: `vfs` (see [`GET /events`](../events.md)).
## Response `200`
File or directory successfully renamed
```json
{
"success": true,
"oldPath": "…",
"newPath": "…"
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Path is required, invalid characters, traversal, or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized, or cannot rename read-only database paths — `{"error":"…"}` |
| 404 | Source file or directory not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,53 @@
# `POST /vfs/upload`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Upload files to virtual filesystem
Uploads one or more files to the virtual filesystem for the authenticated user's selected company.
Files are written to the company's VFS root directory and saved to the database.
An optional `targetDir` form field specifies a subfolder within the VFS.
Writing to read-only database paths (`image/*` or filenames containing 64-character hex hashes) is
forbidden and automatically skipped during upload.
## Body (`multipart/form-data`)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `files` | yes | file | One or more files to upload (form field name `files`) |
| `targetDir` | no | string | Optional destination subdirectory within VFS (e.g. `reports` or `charts/q1`) |
## Signals
On success, emits SSE signal: `vfs` (see [`GET /events`](../events.md)).
## Response `200`
Successfully uploaded files
```json
{
"success": true,
"uploaded": [
{
"name": "…",
"size": 1,
"mime": "…"
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No files uploaded, invalid path, or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,66 @@
# `PUT /vfs/{*path}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Create or overwrite a virtual filesystem file
Creates or overwrites a file at the specified virtual filesystem path for the authenticated user's selected company.
Supports multiple request payload formats:
- JSON body: `{"content": "text content", "mime": "optional/mime"}`
- Binary body: raw binary buffer with optional `Content-Type` header
- Plain text: raw UTF-8 string
Modifying read-only database paths (`image/*` or filenames containing 64-character hex hashes) is blocked with HTTP 403.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `path` | yes | string | Destination VFS file path (e.g. `notes.txt`, `data/metrics.json`) |
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `content` | yes | string | File text content |
| `mime` | no | string | Optional MIME type override |
```json
{
"content": "…",
"mime": "…"
}
```
## Signals
On success, emits SSE signal: `vfs` (see [`GET /events`](../events.md)).
## Response `200`
File successfully written
```json
{
"success": true,
"file": {
"name": "…",
"size": 1,
"mime": "…",
"updated_at": "2026-08-10T00:00:00.000Z"
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Path is required, invalid characters/traversal, no content provided, or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized, or cannot modify read-only database files — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._