feat(numbers): add number sequences store; restrict companysetting keys

- Add `numbers` store module with `list` and `update` methods backed by
  the `numbers` table (prefix, padding, next) per company.
- Add sql queries LIST_BY_COMPANY, GET_BY_COMPANY_AND_ID, UPDATE_...
- Regenerate and wire API docs for `numbers`.

companysetting:
- Remove the `delete` endpoint (settings are now upsert/read only).
- Restrict `set` to a fixed allow-list of keys:
  ui.theme, keys.tavily, keys.openrouter, labelprinter.addr, labelprinter.type.
- Drop DELETE_BY_COMPANY_AND_KEY sql and the delete store file.

Docs: update README, API.md, API.companysetting.md, API.numbers.md.
This commit is contained in:
seb
2026-08-08 09:03:51 +02:00
parent aefd97a171
commit f302cda39e
12 changed files with 272 additions and 79 deletions

View File

@@ -163,13 +163,14 @@ Company-scoped key/value settings. Auth via selected company (`google_auth.selec
| `list` | `(googleAuthId, { frontendOnly? })` | `{ key, value, frontend }[]` |
| `get` | `(googleAuthId, key, { frontendOnly? })` | `{ key, value, frontend } \| undefined` |
| `set` | `(googleAuthId, { key, value, frontend? })` | `{ key, value, frontend }` |
| `delete` | `(googleAuthId, key)` | `void` |
- `value` is always a string (`NVARCHAR(MAX)`).
- `frontend: true` → readable by frontend routes (`frontendOnly: true`). Default on `set` is `false` (backend-only).
- With `frontendOnly: true`, backend-only keys are omitted / `get` returns `undefined` (no leak via error type beyond missing).
- `set` only allows a fixed allow-list of keys: `ui.theme`, `keys.tavily`, `keys.openrouter`, `labelprinter.addr`, `labelprinter.type`.
- No delete endpoint — settings are upsert / read only.
Errors: `No company selected`, `Company not found`, `Not authorized`, `Invalid setting`, `Setting not found`.
Errors: `No company selected`, `Company not found`, `Not authorized`, `Invalid setting`.
---
@@ -660,6 +661,19 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
---
## `numbers`
Company-scoped number sequences (`numbers` table). Auth via selected company (`google_auth.selected_company` + membership).
| Method | Signature | Returns |
| --- | --- | --- |
| `list` | `(googleAuthId)` | `{ id, type, prefix, padding, next }[]` |
| `update` | `(googleAuthId, id, { prefix?, padding?, next? })` | `{ id, type, prefix, padding, next }` |
`list` returns every sequence for the company, ordered by `type`. `update` updates only the provided fields: `prefix` is trimmed (max 50 chars), `padding` must be an integer `>= 0`, `next` must be an integer `>= 1`. Errors: `No company selected`, `Company not found`, `Not authorized`, `Number not found`, `Invalid number`.
---
## Backend route cheatsheet
| Route | Store call |
@@ -683,7 +697,6 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
| `DELETE /api/unit` | `unit.delete(googleAuthId, si)` |
| `GET /api/company/:id/settings` | `companysetting.list(googleAuthId, { frontendOnly: true })` |
| `PUT /api/company/:id/settings` | `companysetting.set(googleAuthId, { key, value, frontend? })` |
| `DELETE /api/company/:id/settings/:key` | `companysetting.delete(googleAuthId, key)` |
| `GET /api/brand` | `brand.list(googleAuthId)` |
| `PUT /api/brand` | `brand.upsert(googleAuthId, { name, gsprpinfo? })` |
| `DELETE /api/brand/:id` | `brand.delete(googleAuthId, id)` |