This commit is contained in:
seb
2026-10-09 19:02:15 +02:00
parent cd6879f3b7
commit ac10840fb6
42 changed files with 467 additions and 49 deletions

View File

@@ -369,6 +369,47 @@
* description: Brand pictures sorted by sort
* items:
* $ref: '#/components/schemas/BrandPic'
* BrandListPic:
* type: object
* required: [fullHash, thumbW, thumbH, thumbHash]
* properties:
* fullHash:
* type: string
* thumbW:
* type: integer
* thumbH:
* type: integer
* thumbHash:
* type: string
* smallThumbW:
* type: integer
* nullable: true
* description: Width in pixels of the small AVIF thumbnail
* smallThumbH:
* type: integer
* nullable: true
* description: Height in pixels of the small AVIF thumbnail
* smallThumbBinary:
* type: string
* format: byte
* nullable: true
* description: Base64-encoded small AVIF thumbnail bytes
* BrandList:
* type: object
* required: [id, name, pics]
* properties:
* id:
* type: integer
* name:
* type: string
* gsprpinfo:
* type: string
* nullable: true
* pics:
* type: array
* description: Brand pictures sorted by sort, including the 40px-edge small thumbnail
* items:
* $ref: '#/components/schemas/BrandListPic'
* ArticleBrand:
* type: object
* required: [name]
@@ -1619,4 +1660,3 @@
*/
export {};

View File

@@ -55,6 +55,7 @@ export function register(app, basePath = '') {
* description: Creates an attribute name for the selected company.
* security:
* - cookieAuth: []
* x-signals: [attributename]
* requestBody:
* required: true
* content:
@@ -118,6 +119,7 @@ export function register(app, basePath = '') {
* description: Updates the display sort order of attribute names for the selected company.
* security:
* - cookieAuth: []
* x-signals: [attributename]
* requestBody:
* required: true
* content:
@@ -162,6 +164,7 @@ export function register(app, basePath = '') {
* description: Updates name, sort, and/or exclusive flag for an attribute name.
* security:
* - cookieAuth: []
* x-signals: [attributename]
* parameters:
* - in: path
* name: id
@@ -197,6 +200,7 @@ export function register(app, basePath = '') {
* description: Deletes an attribute name and cascades to attribute values and article assignments.
* security:
* - cookieAuth: []
* x-signals: [attributename]
* parameters:
* - in: path
* name: id

View File

@@ -47,7 +47,7 @@ export function register(app, basePath = '') {
* get:
* tags: [Brand]
* summary: List brands
* description: Lists brands for the authenticated user's selected company, sorted by name. Paged; `total` is the unpaged count.
* description: Lists brands for the authenticated user's selected company, sorted by name. Paged; `total` is the unpaged count. Small-thumbnail fields are omitted by default. Set `includeSmallThumbs=true` to include each picture's `smallThumbW`, `smallThumbH`, and `smallThumbBinary` (base64-encoded AVIF bytes, resized with a 40px maximum edge).
* security:
* - cookieAuth: []
* parameters:
@@ -66,6 +66,12 @@ export function register(app, basePath = '') {
* minimum: 1
* maximum: 100
* description: Number of brands to return
* - in: query
* name: includeSmallThumbs
* schema:
* type: boolean
* default: false
* description: Include each picture's small thumbnail dimensions and base64 AVIF bytes.
* x-markdown: brand/list.md
* responses:
* 200:
@@ -79,7 +85,7 @@ export function register(app, basePath = '') {
* brands:
* type: array
* items:
* $ref: '#/components/schemas/Brand'
* $ref: '#/components/schemas/BrandList'
* total:
* type: integer
* description: Total number of unpaged brands
@@ -90,7 +96,7 @@ export function register(app, basePath = '') {
* type: integer
* description: Number of brands returned
* 400:
* description: Invalid offset, invalid limit, or no company selected
* description: Invalid offset, invalid limit, invalid includeSmallThumbs, or no company selected
* content:
* application/json:
* schema:
@@ -122,6 +128,15 @@ export function register(app, basePath = '') {
const rawOffset = req.query.offset;
const rawLimit = req.query.limit;
const rawIncludeSmallThumbs = req.query.includeSmallThumbs;
if (
rawIncludeSmallThumbs !== undefined
&& rawIncludeSmallThumbs !== 'true'
&& rawIncludeSmallThumbs !== 'false'
) {
return res.status(400).json({ error: 'includeSmallThumbs must be true or false' });
}
let offset = 0;
let limit = 10;
@@ -141,7 +156,11 @@ export function register(app, basePath = '') {
}
try {
const result = await db.list(req.user.id, { offset, limit });
const result = await db.list(req.user.id, {
offset,
limit,
includeSmallThumbs: rawIncludeSmallThumbs === 'true',
});
res.status(200).json(result);
} catch (error) {
const status = brandErrorStatus(error);

View File

@@ -33,24 +33,68 @@ export function register(app, basePath = '') {
*
* ## Signals
*
* | Signal | Emitted after successful |
* |--------|--------------------------|
* | `category` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image` |
* | `articles` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}` |
* | `article-<sku>` | `PUT /article/{sku}/weight`, `PUT /article/{sku}/prices`, `PUT /article/{sku}/eans`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/brand`, `PUT /article/{sku}/images/order`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}` |
* | `brand` | `POST /brand`, `PATCH /brand/{id}`, `DELETE /brand/{id}`, `POST /brand/{id}/image`, `DELETE /brand/{id}/image` |
* Every SSE event uses the event name as the signal id; payloads are
* JSON and most events send an empty object `{}`. Only `brand` emits a
* richer payload.
*
* The `brand` signal carries a JSON payload `{ sender, brandId }`:
* `sender` echoes the caller's `x-client-id` header (empty string if
* absent) so the sender can ignore its own event; `brandId` is the
* affected brand id (`null` only if unknown). Other signals send `{}`.
* | Signal | Payload | Emitted after successful |
* |--------|---------|--------------------------|
* | `category` | `{}` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image`, `PUT /category/{id}/property` |
* | `articles` | `{}` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}`, `PUT /article/{sku}/articles`-style list-level changes, `POST /category/{id}/article` and related article-assigning category mutations |
* | `article-<sku>` | `{}` | `PUT /article/{sku}/weight`, `PUT /article/{sku}/prices`, `PUT /article/{sku}/eans`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/brand`, `PUT /article/{sku}/images/order`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}`, supplier/article-property mutations affecting the article |
* | `stock` | `{}` | `POST /stock/adjust` |
* | `ledger` | `{}` | `POST /stock/adjust` |
* | `attributename` | `{}` | `POST /attributename`, `PUT /attributename/order`, `PATCH /attributename/{id}`, `DELETE /attributename/{id}` |
* | `propertyname` | `{}` | `POST /propertyname`, `PUT /propertyname/order`, `PATCH /propertyname/{id}`, `DELETE /propertyname/{id}` |
* | `labeltemplate` | `{}` | `POST /labeltemplate`, `PUT /labeltemplate/{id}`, `PATCH /labeltemplate/{id}`, `DELETE /labeltemplate/{id}` |
* | `printer` | `{}` | `POST /printer`, `PUT /printer/{id}`, `PATCH /printer/{id}`, `DELETE /printer/{id}` |
* | `warehouse` | `{}` | `POST /warehouse`, `PUT /warehouse/{id}`, `PATCH /warehouse/{id}`, `DELETE /warehouse/{id}` |
* | `vfs` | `{}` | VFS uploads and filesystem mutations such as `POST /vfs/upload`, `POST /vfs/mkdir`, `POST /vfs/rename`, `PUT /vfs/{*path}`, `DELETE /vfs/{*path}` |
* | `modelconversation` | `{}` | `POST /modelconversation`, `DELETE /modelconversation/{id}` |
* | `modelconversation-<id>` | `{}` | Conversation-specific updates on the same model conversation thread; the concrete ID is used in the event name |
* | `brand` | `{ sender, brandId }` | `POST /brand`, `PATCH /brand/{id}`, `DELETE /brand/{id}`, `POST /brand/{id}/image`, `DELETE /brand/{id}/image` |
*
* The `brand` payload is `{ sender, brandId }`: `sender` echoes the
* caller's `x-client-id` header (empty string if absent) so the sender
* can ignore its own event; `brandId` is the affected brand id (`null`
* only if unknown). All other signals send `{}`.
*
* Signals are only sent when the mutation actually succeeds (2xx).
* Failed or no-op validation errors do not emit. The `article-<sku>`
* event name uses the concrete SKU (e.g. `article-42000045`).
* and `modelconversation-<id>` event names use the concrete item id at
* runtime (for example `article-42000045` and `modelconversation-123`).
* security:
* - cookieAuth: []
* x-markdown: events.md
* x-sse-events:
* - name: category
* payload: "{}"
* - name: articles
* payload: "{}"
* - name: article-<sku>
* payload: "{}"
* - name: stock
* payload: "{}"
* - name: ledger
* payload: "{}"
* - name: attributename
* payload: "{}"
* - name: propertyname
* payload: "{}"
* - name: labeltemplate
* payload: "{}"
* - name: printer
* payload: "{}"
* - name: warehouse
* payload: "{}"
* - name: vfs
* payload: "{}"
* - name: modelconversation
* payload: "{}"
* - name: modelconversation-<id>
* payload: "{}"
* - name: brand
* payload: "{ sender, brandId }"
* responses:
* 200:
* description: |

View File

@@ -176,6 +176,7 @@ export function register(app, basePath = '') {
* and stored with the template.
* security:
* - cookieAuth: []
* x-signals: [labeltemplate]
* x-markdown: labeltemplate/create.md
* requestBody:
* required: true
@@ -293,6 +294,7 @@ export function register(app, basePath = '') {
* When `content` (or `width`/`height`) is provided, the thumbnail is automatically re-rendered.
* security:
* - cookieAuth: []
* x-signals: [labeltemplate]
* x-markdown: labeltemplate/update.md
* parameters:
* - in: path
@@ -343,6 +345,7 @@ export function register(app, basePath = '') {
* When `content` (or `width`/`height`) is provided, the thumbnail is automatically re-rendered.
* security:
* - cookieAuth: []
* x-signals: [labeltemplate]
* x-markdown: labeltemplate/update.md
* parameters:
* - in: path
@@ -398,6 +401,7 @@ export function register(app, basePath = '') {
* description: Deletes a label template for the authenticated user's selected company.
* security:
* - cookieAuth: []
* x-signals: [labeltemplate]
* x-markdown: labeltemplate/delete.md
* parameters:
* - in: path

View File

@@ -104,6 +104,7 @@ export function register(app, basePath = '') {
* description: Creates a new conversation in state `idle`.
* security:
* - cookieAuth: []
* x-signals: [modelconversation]
* requestBody:
* required: true
* content:
@@ -248,6 +249,7 @@ export function register(app, basePath = '') {
* description: Deletes one of the authenticated user's model conversations and all its messages.
* security:
* - cookieAuth: []
* x-signals: [modelconversation]
* parameters:
* - in: path
* name: id
@@ -493,6 +495,7 @@ export function register(app, basePath = '') {
* Refused with 409 unless the conversation state is `idle`.
* security:
* - cookieAuth: []
* x-signals: [modelconversation-<id>]
* parameters:
* - in: path
* name: id

View File

@@ -103,6 +103,7 @@ export function register(app, basePath = '') {
* `printerIds` must include all existing printer IDs exactly once.
* security:
* - cookieAuth: []
* x-signals: [printer]
* x-markdown: printer/order.md
* requestBody:
* required: true
@@ -253,6 +254,7 @@ export function register(app, basePath = '') {
* `type` must be `"esc/pos"` (lines), `"zpl"` (pages), or `"ipp"`.
* security:
* - cookieAuth: []
* x-signals: [printer]
* x-markdown: printer/create.md
* requestBody:
* required: true
@@ -355,6 +357,7 @@ export function register(app, basePath = '') {
* description: Updates specified fields of an existing printer for the selected company.
* security:
* - cookieAuth: []
* x-signals: [printer]
* parameters:
* - in: path
* name: id
@@ -409,6 +412,7 @@ export function register(app, basePath = '') {
* description: Updates specified fields of an existing printer for the selected company.
* security:
* - cookieAuth: []
* x-signals: [printer]
* parameters:
* - in: path
* name: id
@@ -469,6 +473,7 @@ export function register(app, basePath = '') {
* description: Deletes a printer for the authenticated user's selected company.
* security:
* - cookieAuth: []
* x-signals: [printer]
* parameters:
* - in: path
* name: id

View File

@@ -61,6 +61,7 @@ export function register(app, basePath = '') {
* description: Creates a new property name for the selected company.
* security:
* - cookieAuth: []
* x-signals: [propertyname]
* requestBody:
* required: true
* content:
@@ -130,6 +131,7 @@ export function register(app, basePath = '') {
* description: Updates the display sort order of property names for the selected company.
* security:
* - cookieAuth: []
* x-signals: [propertyname]
* requestBody:
* required: true
* content:
@@ -176,6 +178,7 @@ export function register(app, basePath = '') {
* description: Updates name and/or sort for a property name.
* security:
* - cookieAuth: []
* x-signals: [propertyname]
* parameters:
* - in: path
* name: id
@@ -211,6 +214,7 @@ export function register(app, basePath = '') {
* description: Deletes a property name and cascades to article properties.
* security:
* - cookieAuth: []
* x-signals: [propertyname]
* parameters:
* - in: path
* name: id

View File

@@ -71,6 +71,7 @@ export function register(app, basePath = '') {
* **Note:** `sku` must be a string (e.g. `"25"`), not a number. For single-item adjustments, provide `sku` and `amount` at top-level. For batch adjustments, provide `lines: [{ sku, amount, ... }]`.
* security:
* - cookieAuth: []
* x-signals: [stock, ledger, articles, article-<sku>]
* x-markdown: stock/adjust.md
* requestBody:
* required: true

View File

@@ -104,6 +104,7 @@ export function register(app, basePath = '') {
* `warehouseIds` must include all existing warehouse IDs exactly once.
* security:
* - cookieAuth: []
* x-signals: [warehouse]
* x-markdown: warehouse/order.md
* requestBody:
* required: true
@@ -254,6 +255,7 @@ export function register(app, basePath = '') {
* Sort order will be automatically appended if omitted.
* security:
* - cookieAuth: []
* x-signals: [warehouse]
* x-markdown: warehouse/create.md
* requestBody:
* required: true
@@ -408,6 +410,7 @@ export function register(app, basePath = '') {
* description: Updates specified fields of an existing warehouse for the selected company.
* security:
* - cookieAuth: []
* x-signals: [warehouse]
* parameters:
* - in: path
* name: id
@@ -474,6 +477,7 @@ export function register(app, basePath = '') {
* description: Deletes a warehouse if not in use in the ledger.
* security:
* - cookieAuth: []
* x-signals: [warehouse]
* parameters:
* - in: path
* name: id