Add article history by SKU and SSE update signals for live clients.

Expose getHistoryBySku as GET /article/:sku/history, and document mutation-emitted company signals via GET /events.
This commit is contained in:
seb
2026-08-05 09:54:56 +02:00
parent 4a9403bfd1
commit 782c49a680
28 changed files with 757 additions and 2 deletions

View File

@@ -2,10 +2,18 @@ import express from 'express';
import JSZip from 'jszip';
import * as db from '../../db/category.js';
import * as picDb from '../../db/pic.js';
import { emit } from '../sse.js';
import { buildKategorienCsv, toWindows1252 } from '../../util/kategorienCsv.js';
const AVIF_BODY_LIMIT = '20mb';
/** Notify SSE subscribers of the actor's selected company after a real category change. */
function emitCategorySignal(req) {
const companyId = req.user?.selectedCompanyId;
if (companyId == null) return;
emit('category', { companyId });
}
function parsePositiveInt(value, field) {
const id = Number(value);
if (!Number.isInteger(id) || id <= 0) {
@@ -198,6 +206,7 @@ export function register(app, basePath = '') {
* description: Creates a category under an authorized parent.
* security:
* - cookieAuth: []
* x-signals: [category]
* x-markdown: category/create.md
* requestBody:
* required: true
@@ -266,6 +275,7 @@ export function register(app, basePath = '') {
parentId: parent.id,
name,
});
emitCategorySignal(req);
res.status(201).json({ category });
} catch (error) {
const status = categoryErrorStatus(error);
@@ -352,6 +362,7 @@ export function register(app, basePath = '') {
* Either `name` or `parentId` (or both) is required.
* security:
* - cookieAuth: []
* x-signals: [category]
* x-markdown: category/update.md
* parameters:
* - in: path
@@ -426,6 +437,7 @@ export function register(app, basePath = '') {
* description: Deletes a category the user is authorized for (not the company root).
* security:
* - cookieAuth: []
* x-signals: [category]
* x-markdown: category/delete.md
* parameters:
* - in: path
@@ -467,6 +479,7 @@ export function register(app, basePath = '') {
googleAuthId: req.user.id,
id: parsed.id,
});
emitCategorySignal(req);
res.sendStatus(204);
} catch (error) {
const status = categoryErrorStatus(error);
@@ -517,6 +530,7 @@ export function register(app, basePath = '') {
}
}
emitCategorySignal(req);
res.status(200).json({ category });
} catch (error) {
const status = categoryErrorStatus(error);
@@ -538,6 +552,7 @@ export function register(app, basePath = '') {
* to its index `0..n-1` in one transaction.
* security:
* - cookieAuth: []
* x-signals: [category]
* x-markdown: category/order.md
* requestBody:
* required: true
@@ -615,6 +630,7 @@ export function register(app, basePath = '') {
try {
const result = await db.order({ googleAuthId: req.user.id, parentId, categoryIds });
emitCategorySignal(req);
res.status(200).json(result);
} catch (error) {
const status = categoryErrorStatus(error);
@@ -636,6 +652,7 @@ export function register(app, basePath = '') {
* Auth like GET /category/tree — user must be authorized for the category's company.
* security:
* - cookieAuth: []
* x-signals: [category]
* x-markdown: category/add-image.md
* parameters:
* - in: path
@@ -702,6 +719,7 @@ export function register(app, basePath = '') {
* Auth like GET /category/tree — user must be authorized for the category's company.
* security:
* - cookieAuth: []
* x-signals: [category]
* x-markdown: category/delete-image.md
* parameters:
* - in: path
@@ -767,6 +785,7 @@ export function register(app, basePath = '') {
try {
const category = await db.addImage(req.user.id, parsed.id, imageBuffer);
emitCategorySignal(req);
res.status(201).json({ category });
} catch (error) {
const status = categoryErrorStatus(error);
@@ -784,6 +803,7 @@ export function register(app, basePath = '') {
try {
const category = await db.deleteImage(req.user.id, parsed.id);
emitCategorySignal(req);
res.status(200).json({ category });
} catch (error) {
const status = categoryErrorStatus(error);