Seed z repozitare, zatezove testy, worker bez spanku, dialogy bez rozmazani

Nastaveni prezije nasazeni: seed/records.json se pri prazdnem ulozisti
nacte misto ukazkovych dat (zive uloziste se nikdy neprepisuje). Soubor
nese soucasny stav produkce (firmy s provozovatelem, role, typ ticketu,
akce, widgety, skupiny, rozlozeni). Novy GET /api/admin/export a skript
npm run seed:export pro dalsi exporty, Dockerfile slozku kopiruje.

Vykonnostni testy (npm run test:perf) nad 200 firmami a 10 000 tickety
a zatezovy skript (npm run load) proti bezici instanci vcetne davky
udalosti na webhook. Mereni odhalilo strop workeru: po obsazeni vsech
mist spal sekundu, takze fronta odbavila nejvys 4 behy za sekundu. Ted
ceka na prvni dokonceny beh: 500 udalosti za 1,3 s (395 behu/s). Strop
posluchacu streamu zvednut na 2 000.

Dialogy: prekryv modalu a menu v portalu bez backdrop-blur, tecka Zive
pulzuje jen pri navazovani spojeni - rozmazani cele obrazovky pod trvalou
animaci sekalo video vedle portalu. Bublina udalosti drzi 0,5 s.

Dokumentace 14, 19, 20, 22, 04, 01, 03, 15 a 99 aktualizovana.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-09-09 20:41:42 +02:00
co-authored by Claude Fable 5.1
parent c25e826766
commit ea9387bea9
44 changed files with 3512 additions and 28 deletions
+67
View File
@@ -49,6 +49,7 @@ Psat do rozbiteho schematu je horsi nez psat do souboru.
| `DATABASE_POOL_MAX` | kolik spojeni si vezme jedna instance, vychozi 10 |
| `DATABASE_SSL` | `true` u spravovanych databazi, ktere vyzaduji TLS |
| `DATA_DIR` | slozka pro JSON mimo databazi, vychozi `./data`. Prazdna hodnota vypne i soubor |
| `SEED_FILE` | nastaveni z repozitare pro prazdne uloziste, vychozi `./seed/records.json`. Prazdna hodnota vypne |
`SECRETS_KEY` ma byt nahodny retezec, ne heslo:
@@ -100,6 +101,72 @@ a nic se neztrati. Chyba cteni ale neznamena, ze data neexistuji - a start
s prazdnem, ktery by je pri prvnim zapisu prepsal, je jedina cesta, jak
o ne v rezimu `file` opravdu prijit.
## Nastaveni v repozitari (seed/records.json)
Nasazeni bezi v rezimu `file` bez svazku, takze kazdy redeploy smaze data
a spravce platformy zadaval znovu firmu provozovatele, ucty, widgety
a rozlozeni. Spravne reseni je `DATABASE_URL` nebo `DATA_DIR` na svazku,
obe jsou vec infrastruktury mimo tento repozitar. Do te doby se konfigurace
drzi v repu a pri startu se nasype sama.
**Proc soubor v repu a ne seed v kodu.** Seed v kodu je ukazka pro prazdnou
instalaci a meni se jen s kodem. Nastaveni provozovatele se meni v portalu
a nikdo ho nema prepisovat do TypeScriptu. Export je jeden prikaz a vysledek
je data, ne kod.
| Co | Jak |
| ----------------------- | ----------------------------------------------------------------------------------------- |
| soubor | `seed/records.json`, cesta z `SEED_FILE` (`src/config.ts`), v gitu |
| tvar | `{ "exportedAt": "...", "kinds": { "<druh>": [zaznam, ...] } }`, druh je `store.kind` |
| kdy se pouzije | **jen do prazdneho uloziste**, presne tam, kde by se pouzil seed z kodu (`store.init`) |
| co vyhrava | druh v souboru ma prednost pred kodem, i kdyz je prazdny (`[]` = spravce je smazal) |
| chybejici soubor | ticho, jede se ze seedu v kodu (bezny stav pri vyvoji) |
| rozbity soubor | `console.error` a seed z kodu; nikdy nepada start |
| druh mimo konfiguraci | ignoruje se s varovanim |
| log | `[seed] <druh>: N zaznamu ze souboru` nebo `[seed] <druh>: kod`, jen u prazdneho uloziste |
Rozhodnuti "soubor, nebo kod" je na jednom miste: `seedFromFile(kind, seed)`
v `src/data/store/seedFile.ts`, ktere vola `defineStore` v `init`. Uloziste
(`local.ts`, `postgres.ts`) o souboru nevi, `bootstrap.ts` se nemeni
a plati to pro vsechny tri rezimy vcetne `withMirror` (automatizace,
rozlozeni). Automatizace ze souboru prochazi stejnym `withDerived` jako ty
z kodu, nedodelky a pocet kroku se pri startu prepocitaji.
**Co v souboru je**: `tenant`, `user`, `role`, `personGroup`,
`tenantFeatures`, `ticketType`, `ticketAction`, `customWidget`,
`dashboardLayout`, `automation`, `tenantScript` (seznam `SEED_KINDS`).
**Co v nem neni a proc**:
| Druh | Proc ne |
| ------------------------------------------------ | ------------------------------------------------------------------------- |
| tickety, incidenty, audit, upozorneni, prilohy, fronta | provozni data, vznikaji provozem; v repu nemaji co delat |
| konektory | udaje jsou sifrovane klicem, ktery se bez `SECRETS_KEY` meni s nasazenim |
| pozvanky | maji platnost a kod, po nasazeni jsou stejne prosle |
**Hash hesla.** Export bere ucty tak, jak lezi v ulozisti, tedy vcetne
`passwordHash` (bcrypt). Bez nej by se po nasazeni nikdo neprihlasil.
Bcrypt neni plaintext, ale hash v gitu je hash v gitu: **repozitar musi
zustat soukromy** a tokeny firem (`intakeToken`, webhooky automatizaci)
jsou v nem take. Kdo to nechce, nastavi `DATABASE_URL` a soubor smaze.
**Export**: `GET /api/admin/export` (spravce platformy, pravo `audit.view`,
audit `admin.export`) vraci stejny tvar primo z ulozist (`listAll`).
Skript `scripts/export-seed.mjs` se prihlasi a soubor zapise:
```bash
SEED_EXPORT_URL=https://services.csbot.cz/apps/csbot-prototype SEED_EXPORT_EMAIL=admin@... SEED_EXPORT_PASSWORD=... npm run seed:export
```
Vypise pocty po druzich, zapisuje UTF-8 bez BOM s LF. Po exportu se soubor
commitne a dalsi nasazeni z nej vyjde. Zmena v portalu, ktera se
nevyexportuje, se pri dalsim nasazeni ztrati - to je cena za chybejici
databazi, ne vlastnost seedu.
Testy: `tests/data/seedFile.test.ts` (rezim souboru nad docasnou slozkou),
`tests/routes/adminExport.test.ts`. V testech je `SEED_FILE` prazdny, aby
nezavisely na obsahu repa.
## Vrstvy nad ulozistem
Dve obalky, kazda pro jiny druh dat (podrobne