From d4e2b05cb09cce1766262a082619256f6af3b605 Mon Sep 17 00:00:00 2001 From: seb Date: Fri, 9 Oct 2026 21:43:17 +0200 Subject: [PATCH] u --- docs/api/README.md | 1 + docs/api/routes-all.md | 5 +- docs/api/routes.md | 5 +- docs/api/routes/smallthumb/get.md | 33 ++++++++++++ src/api/routes/pic.js | 85 +++++++++++++++++++++++++++++++ src/db/pic.js | 5 ++ 6 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 docs/api/routes/smallthumb/get.md diff --git a/docs/api/README.md b/docs/api/README.md index 293d227..69cb9c1 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -207,6 +207,7 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route | [`routes/shipping/_id.md`](routes/shipping/_id.md) | `DELETE /shipping/{id}` | | [`routes/shipping/_id/package.get.md`](routes/shipping/_id/package.get.md) | `GET /shipping/{id}/package` | | [`routes/shipping/_id/package.post.md`](routes/shipping/_id/package.post.md) | `POST /shipping/{id}/package` | +| [`routes/smallthumb/get.md`](routes/smallthumb/get.md) | `GET /smallthumb/{fullHash}` | | [`routes/stock/adjust.md`](routes/stock/adjust.md) | `POST /stock/adjust` | | [`routes/supplier/list.md`](routes/supplier/list.md) | `GET /supplier` | | [`routes/supplier/create.md`](routes/supplier/create.md) | `POST /supplier` | diff --git a/docs/api/routes-all.md b/docs/api/routes-all.md index 7cb1bc4..667059b 100644 --- a/docs/api/routes-all.md +++ b/docs/api/routes-all.md @@ -1,6 +1,6 @@ # Dash API Routes Overview (All) -Overview of all 205 API endpoints across all modules. +Overview of all 206 API endpoints across all modules. ### Notation @@ -613,6 +613,9 @@ _none_ #### Create package for a shipping — `POST /shipping/{id}/package` - Params: `id: int, body: { entityaddressId: int, type: str, state: str, details?: str }` - Returns: `201 { package: { id: int, shippingId: int, entityaddressId: int, number: str, type: str, state: str, details?: str|null } }` +#### Small thumb image by hash — `GET /smallthumb/{fullHash}` +- Params: `fullHash: str` +- Returns: `200 avif` #### Adjust stock manually — `POST /stock/adjust` - Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str|null, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }` - Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: { manifestarticle_id: int, sku: str, name: str, warehouse_id: int, amount: num, text: str }[], ledger: T15[] }` diff --git a/docs/api/routes.md b/docs/api/routes.md index b31365f..41db0a7 100644 --- a/docs/api/routes.md +++ b/docs/api/routes.md @@ -1,6 +1,6 @@ # Dash API Routes Overview -Overview of all 159 API endpoints across all modules. +Overview of all 160 API endpoints across all modules. ### Notation @@ -478,6 +478,9 @@ _none_ #### Create package for a shipping — `POST /shipping/{id}/package` - Params: `id: int, body: { entityaddressId: int, type: str, state: str, details?: str }` - Returns: `201 { package: { id: int, shippingId: int, entityaddressId: int, number: str, type: str, state: str, details?: str|null } }` +#### Small thumb image by hash — `GET /smallthumb/{fullHash}` +- Params: `fullHash: str` +- Returns: `200 avif` #### Adjust stock manually — `POST /stock/adjust` - Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str|null, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }` - Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: { manifestarticle_id: int, sku: str, name: str, warehouse_id: int, amount: num, text: str }[], ledger: T14[] }` diff --git a/docs/api/routes/smallthumb/get.md b/docs/api/routes/smallthumb/get.md new file mode 100644 index 0000000..8a78682 --- /dev/null +++ b/docs/api/routes/smallthumb/get.md @@ -0,0 +1,33 @@ +# `GET /smallthumb/{fullHash}` + +Protected. Requires a valid session cookie (`credentials: 'include'`). + +Small thumb image by hash + +Returns the small thumb AVIF (40px max edge) for `pic.hash` (fullHash — not thumbhash). +Auth via article category access. Browser-cacheable (`private`, `immutable`, `ETag`). + + +## Path parameters + +| Param | Required | Type | Description | +|-------|----------|------|-------------| +| `fullHash` | yes | string | 8-char alphanumeric hash of the full image bytes | + +## Response `200` + +AVIF small thumb bytes + +## Errors + +| Status | Description | +|--------|-------------| +| 304 | Not modified (If-None-Match matched ETag) | +| 400 | Invalid hash — `{"error":"…"}` | +| 401 | Unauthorized — `{"error":"…"}` | +| 403 | Not authorized — `{"error":"…"}` | +| 404 | Image not found — `{"error":"…"}` | + +--- + +_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._ diff --git a/src/api/routes/pic.js b/src/api/routes/pic.js index 8c0c2fe..137387d 100644 --- a/src/api/routes/pic.js +++ b/src/api/routes/pic.js @@ -209,4 +209,89 @@ export function register(app, basePath = '') { res.status(status).json({ error: error.message }); } }); + + /** + * @openapi + * /smallthumb/{fullHash}: + * get: + * tags: [Image] + * summary: Small thumb image by hash + * description: | + * Returns the small thumb AVIF (40px max edge) for `pic.hash` (fullHash — not thumbhash). + * Auth via article category access. Browser-cacheable (`private`, `immutable`, `ETag`). + * security: + * - cookieAuth: [] + * x-markdown: smallthumb/get.md + * parameters: + * - in: path + * name: fullHash + * required: true + * schema: + * type: string + * pattern: '^[a-zA-Z0-9]{8}$' + * description: 8-char alphanumeric hash of the full image bytes + * responses: + * 200: + * description: AVIF small thumb bytes + * headers: + * Cache-Control: + * schema: + * type: string + * example: private, max-age=31536000, immutable + * ETag: + * schema: + * type: string + * content: + * image/avif: + * schema: + * type: string + * format: binary + * 304: + * description: Not modified (If-None-Match matched ETag) + * 400: + * description: Invalid hash + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 401: + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 403: + * description: Not authorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 404: + * description: Image not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ + const smallThumbPaths = [`${basePath}/smallthumb/:fullHash`]; + if (basePath) smallThumbPaths.push('/smallthumb/:fullHash'); + app.get(smallThumbPaths, async (req, res) => { + const parsed = parseFullHash(req.params.fullHash); + if (parsed.error) { + return res.status(400).json({ error: parsed.error }); + } + + try { + const image = await db.getSmallThumbByHash(req.user.id, parsed.fullHash); + sendImmutableAvif(req, res, { + buffer: image.buffer, + contentType: image.contentType, + fullHash: parsed.fullHash, + }); + } catch (error) { + const status = picErrorStatus(error); + if (status === 500) console.error('Get smallthumb by hash failed:', error); + res.status(status).json({ error: error.message }); + } + }); } diff --git a/src/db/pic.js b/src/db/pic.js index 23dc12b..e856a9a 100644 --- a/src/db/pic.js +++ b/src/db/pic.js @@ -9,3 +9,8 @@ export async function getThumbByHash(googleAuthId, fullHash) { const store = await getStore(); return store.pic.getThumbByHash(googleAuthId, fullHash); } + +export async function getSmallThumbByHash(googleAuthId, fullHash) { + const store = await getStore(); + return store.pic.getSmallThumbByHash(googleAuthId, fullHash); +}