This commit is contained in:
seb
2026-10-24 06:19:34 +02:00
parent 464021318a
commit 369fbb2a70
6 changed files with 198 additions and 3 deletions

View File

@@ -115,6 +115,7 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/category/_id/attributevalue.put.md`](routes/category/_id/attributevalue.put.md) | `PUT /category/{id}/attributevalue` |
| [`routes/category/delete-image.md`](routes/category/delete-image.md) | `DELETE /category/{id}/image` |
| [`routes/category/add-image.md`](routes/category/add-image.md) | `POST /category/{id}/image` |
| [`routes/category/_id/image/generate.md`](routes/category/_id/image/generate.md) | `POST /category/{id}/image/generate` |
| [`routes/category/_id/property.get.md`](routes/category/_id/property.get.md) | `GET /category/{id}/property` |
| [`routes/category/_id/property.put.md`](routes/category/_id/property.put.md) | `PUT /category/{id}/property` |
| [`routes/category/_id/property/_propertyId.md`](routes/category/_id/property/_propertyId.md) | `DELETE /category/{id}/property/{propertyId}` |
@@ -315,7 +316,7 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A
| `attributename` | `POST /attributename`, `PUT /attributename/order`, `DELETE /attributename/{id}`, `PATCH /attributename/{id}`, `DELETE /attributevalue/{id}/image`, `POST /attributevalue/{id}/image` |
| `brand` | `POST /brand`, `DELETE /brand/{id}`, `PATCH /brand/{id}`, `DELETE /brand/{id}/image`, `POST /brand/{id}/image` |
| `carrier` | `POST /carrier`, `PUT /carrier/order`, `DELETE /carrier/{id}`, `PATCH /carrier/{id}` |
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image` |
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image`, `POST /category/{id}/image/generate` |
| `delivery` | `DELETE /delivery/{id}/file/{fileId}`, `POST /delivery/{id}/pdf`, `POST /stock/accept` |
| `labeltemplate` | `POST /labeltemplate`, `DELETE /labeltemplate/{id}`, `PATCH /labeltemplate/{id}`, `PUT /labeltemplate/{id}` |
| `ledger` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |

View File

@@ -1,6 +1,6 @@
# Dash API Routes Overview (All)
Overview of all 269 API endpoints across all modules.
Overview of all 270 API endpoints across all modules.
### Notation
@@ -329,6 +329,9 @@ _none_
#### Add category image — `POST /category/{id}/image`
- Params: `id: int, image/avif`
- Returns: `201 { category: { id: int, thumbW: int, thumbH: int, thumbHash: str } }`
#### Generate category mosaic image from contents — `POST /category/{id}/image/generate`
- Params: `id: int, body?: { size?: int }`
- Returns: `200 { category: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, size: int, grid: str, articleCount: int } }`
#### List category properties — `GET /category/{id}/property`
- Params: `id: int`
- Returns: `200 { properties: { id: int, propertyId: int, name: str, exclusive: bool, value: str, sort: int, categoryId?: int }[] }`

View File

@@ -1,6 +1,6 @@
# Dash API Routes Overview
Overview of all 189 API endpoints across all modules.
Overview of all 190 API endpoints across all modules.
### Notation
@@ -253,6 +253,9 @@ _none_
#### Delete category image — `DELETE /category/{id}/image`
- Params: `id: int`
- Returns: `200 { category: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null } }`
#### Generate category mosaic image from contents — `POST /category/{id}/image/generate`
- Params: `id: int, body?: { size?: int }`
- Returns: `200 { category: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, size: int, grid: str, articleCount: int } }`
#### List category properties — `GET /category/{id}/property`
- Params: `id: int`
- Returns: `200 { properties: { id: int, propertyId: int, name: str, exclusive: bool, value: str, sort: int, categoryId?: int }[] }`

View File

@@ -0,0 +1,64 @@
# `POST /category/{id}/image/generate`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Generate category mosaic image from contents
Generates a 2x2, 3x3, or 4x4 mosaic image from the category's and its subcategories'
articles (grouped by subcategory) and sets it as the new main picture for the category.
If size is not specified, defaults to 3 (3x3), falling back to 2 (2x2) if fewer than 7 images are available.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | Category id |
## Body (JSON) — _optional_
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `size` | no | integer | Mosaic grid size (2 for 2x2, 3 for 3x3, 4 for 4x4). Defaults to 3x3 (or 2x2 if < 7 images). |
```json
{
"size": 1
}
```
## Signals
On success, emits SSE signal: `category` (see [`GET /events`](../events.md)).
## Response `200`
Mosaic image generated and linked as category main picture
```json
{
"category": {
"id": 1,
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…",
"size": 1,
"grid": "…",
"articleCount": 1
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid category ID or size — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Category not found or no article images found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -907,4 +907,122 @@ export function register(app, basePath = '') {
});
// #endregion
/**
* @openapi
* /category/{id}/image/generate:
* post:
* tags: [Category]
* summary: Generate category mosaic image from contents
* description: |
* Generates a 2x2, 3x3, or 4x4 mosaic image from the category's and its subcategories'
* articles (grouped by subcategory) and sets it as the new main picture for the category.
* If size is not specified, defaults to 3 (3x3), falling back to 2 (2x2) if fewer than 7 images are available.
* security:
* - cookieAuth: []
* x-signals: [category]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: Category id
* requestBody:
* required: false
* content:
* application/json:
* schema:
* type: object
* properties:
* size:
* type: integer
* enum: [2, 3, 4]
* description: Mosaic grid size (2 for 2x2, 3 for 3x3, 4 for 4x4). Defaults to 3x3 (or 2x2 if < 7 images).
* responses:
* 200:
* description: Mosaic image generated and linked as category main picture
* content:
* application/json:
* schema:
* type: object
* properties:
* category:
* type: object
* properties:
* id:
* type: integer
* fullHash:
* type: string
* thumbW:
* type: integer
* thumbH:
* type: integer
* thumbHash:
* type: string
* size:
* type: integer
* grid:
* type: string
* articleCount:
* type: integer
* 400:
* description: Invalid category ID or size
* 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: Category not found or no article images found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
// #region POST /category/:id/image/generate
app.post(
`${basePath}/category/:id/image/generate`,
express.json(),
async (req, res) => {
const parsed = parsePositiveInt(req.params.id, 'id');
if (parsed.error) {
return res.status(400).json({ error: parsed.error });
}
const rawSize = req.body?.size;
let size = undefined;
if (rawSize !== undefined && rawSize !== null && rawSize !== '') {
size = Number(rawSize);
if (![2, 3, 4].includes(size)) {
return res.status(400).json({ error: 'Invalid size, must be 2, 3, or 4' });
}
}
try {
const category = await db.generateImage(req.user.id, parsed.id, { size });
emitCategorySignal(req);
res.status(200).json({ category });
} catch (error) {
const status = categoryErrorStatus(error);
if (status === 500) console.error('Generate category image failed:', error);
res.status(status).json({ error: error.message });
}
},
);
// #endregion
}

View File

@@ -44,3 +44,9 @@ export async function deleteImage(googleAuthId, categoryId) {
const store = await getStore();
return store.category.deleteImage(googleAuthId, categoryId);
}
export async function generateImage(googleAuthId, categoryId, options) {
const store = await getStore();
return store.category.generateImage(googleAuthId, categoryId, options);
}