Revize projektu: prava, vykon, runtime, portal a ARES

Prava a bezpecnost: spravce firmy uz nemuze nastavit priznak spravce
platformy ani clenstvi v cizi firme; pozvanky, konektory a automatizace
kontroluji sve pravo; cizi firma v query je 404; zivy stream posila
udalosti jen firmam, kterych se tykaji; akce nad ticketem maji kontrolu
prava za firmu ticketu a strop viditelnosti; tokeny se nelogujou; limit
pokusu na prihlaseni, kontakt a pozvanky; bezpecnostni hlavicky;
zachyceni chyb v async handlerech; timing-safe porovnani tokenu.

Vykon: audit neskenuje celou kolekci pri kazdem zapisu a konecne maze
firemni zaznamy; ticket se uklada jednou misto trikrat; zapisy do
Postgresu jsou serializovane podle ID; prava se pocitaji jednou na
request; widgety nacitaji tickety jednou; strankovani seznamu; worker
je pool misto kol; na webu udalost ze streamu neodmontuje stranku,
dotazy maji spolecny debounce a cache, ciselniky drzi typovany sklad.

Runtime: opakuji se jen chyby oznacene retryable; smycka nenarazi na
strop 50 kroku (novy strop 1000 akci); podminka nad datem funguje;
vystup MCP nastroje neprepisuje spoustec; sandbox skriptu firmy nejde
opustit; MCP session id se drzi mezi volanimi; incident z kroku patri
firme; jedno rozhodnuti o rezimu uloziste; snapshot neprepise soubor
po chybe cteni.

Refaktory: sdilene typy API v src/shared (web nic nekopiruje, osm
rozjetych tvaru sjednoceno); spolecny modul net/guard pro volani ven;
formularova vrstva ui/form; rozdeleni Connectors a FlowCanvas; jeden
helper pro firmu z query, validaci a CRUD udalosti; pomucky ctx.util
pro skripty konektoru; i18n verejneho webu vcetne anglictiny.

Nova funkce: zalozeni firmy z registru ARES v Nastaveni (IC nebo nazev,
dotazeni IC, DIC, sidla a pravni formy, vyber soucasnych statutarnich
zastupcu a prokury, ucty spravce firmy s nahradnim e-mailem
IC-poradi@placeholder.cz).

Dokumentace: zaznam v 99-zmeny.md a aktualizace 15 dalsich dokumentu.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-09-09 10:26:07 +02:00
co-authored by Claude Fable 5.1
parent 0c405ea55a
commit 104ae36783
215 changed files with 13226 additions and 8320 deletions
+47 -15
View File
@@ -1,21 +1,26 @@
# 14 - Databaze
Naprogramovano a overeno. Zatim se ukladaji **konektory**, tedy pristupove udaje
k sluzbam. Zbytek je v pameti procesu, poradi dalsich kroku je na konci.
Naprogramovano a overeno. Uklada se vsechno: konektory, tickety, automatizace,
incidenty, rozlozeni dashboardu, entity nastaveni, audit a notifikace.
Ukladat jde tremi zpusoby a rezim se vybira sam podle toho, co je k dispozici.
## Tri rezimy, jedno rozhrani
## Tri rezimy, jedno rozhrani, jedno rozhodnuti
Rozdil se resi **na jednom miste**, v `src/data/connectorStore.ts`. Nikde jinde
se nezjistuje, ktery rezim jede - kdyby se to rozlezlo po kodu, jedno misto by
se zapomnelo a chovalo by se pak jinak nez zbytek.
Rozdil se resi **na jednom miste**, v `initStores` v `src/data/store/index.ts`.
Nikde jinde se nezjistuje, ktery rezim jede - kdyby se to rozlezlo po kodu,
jedno misto by se zapomnelo a chovalo by se pak jinak nez zbytek.
| Rezim | Kdy | Prezije restart | Prezije redeploy |
| ---------- | --------------------------------- | --------------- | ---------------- |
| `postgres` | je `DATABASE_URL` i `SECRETS_KEY` | ano | ano |
| `file` | neni databaze, ale je `DATA_DIR` | ano | **ne** |
| `memory` | ani jedno, nebo nejde zapsat | ne | ne |
Presne to se stalo: konektory mely vlastni rozhodnuti v `connectorStore.ts`
s jinymi podminkami nez zbytek, takze konektory mohly jet z databaze a tickety
ze souboru. Ted `connectorStore` jen vola `initStores` a rezim je **jeden pro
vsechna uloziste**.
| Rezim | Kdy | Prezije restart | Prezije redeploy |
| ---------- | --------------------------------------------------------- | --------------- | ---------------- |
| `postgres` | je `DATABASE_URL`, migrace prosly a je cim sifrovat (`SECRETS_KEY`) | ano | ano |
| `file` | neni databaze, ale je `DATA_DIR` | ano | **ne** |
| `memory` | ani jedno, nebo nejde zapsat | ne | ne |
Rezim `file` je pro mockup. Filesystem containeru je docasny, takze soubor
prezije restart procesu i containeru, ale nove nasazeni ho smaze. Je to
@@ -87,8 +92,38 @@ Kdyby to byly dve implementace, jedna by se casem opravila a druha ne.
| Slucovani zapisu | deset uprav za sebou znamena jeden zapis na disk |
| Zapis pri ukonceni | `SIGTERM` dokonci rozepsany zapis, jinak by se posledni zmena ztratila |
| Rozbity soubor | prejmenuje se na `.broken`, zaloguje a jede se s prazdnymi daty. Aplikace, ktera nenastartuje, je pro AppFactory nefunkcni sluzba |
| Necitelny soubor | jen `ENOENT` je prvni start. Jina chyba cteni (prava, plny disk) **zamkne zapisy** a zaloguje se - jinak by se soubor s daty prepsal prazdnym |
| Sifrovani | tajne hodnoty jsou v souboru zasifrovane, plaintext nikdy |
Rozdil mezi poslednimi dvema radky je zamer. Rozbity JSON je zalozeny bokem
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.
## Vrstvy nad ulozistem
Dve obalky, kazda pro jiny druh dat (podrobne
v [17-nastaveni-a-prava.md](17-nastaveni-a-prava.md)):
**`withCache`** pro entity, ktere se ctou pri kazdem requestu a meni zridka.
Kopie v pameti, `byId` je `Map`. Vsechny cache jsou v registru
(`src/data/store/cached.ts`): `refreshCache(kind)` obnovi jednu,
`refreshAllCaches()` vsechny. Route nastaveni driv po kazdem zapisu obnovovala
vsechny cache, ted jen entitu, do ktere psala (`bootstrapDataRefresh(route)`
v `src/data/refresh.ts`). `listByTenant(tenantIds, sortBy)` je jeden filtr
a razeni misto sedmi kopii v modulech.
**`withMirror`** pro provozni data: meni se v pameti, po zmene se zapise cely
zaznam. Zapisy tehoz ID jsou **serazene za sebou** retezem promise. Bez toho
mohl Postgres potvrdit dva `put` tehoz ticketu v opacnem poradi, nez prisly,
a v tabulce zustala starsi verze - v pameti to nebylo videt, po restartu ano.
Tickety navic slucuji vic zmen v jednom tiku do jednoho zapisu
(`persist` / `flushPersist`).
**Audit** se jen pripisuje a oreza se davkou (`removeMany`) po 50 zapisech
nebo nejvys jednou za minutu. Prvni verze mazala jen radky platformy
(`tenantId: null`) a audit firem rostl donekonecna.
### Klic mimo databazi
Bez `SECRETS_KEY` si aplikace v rezimu `file` vygeneruje klic do
@@ -221,10 +256,7 @@ Proti Postgresu 16 v kontejneru:
| Chybi | Poznamka |
| ------------------------------------ | ----------------------------------------------------------------------------- |
| Automatizace v ulozisti | dalsi na rade, je to to, co si clovek nastavi. Pujde do souboru i do databaze |
| Rozlozeni dashboardu | male a samostatne, hned po automatizacich |
| Tickety a incidenty | naposled, dnes je generuje simulace |
| Uzivatele, firmy, resitele | v prototypu je to spis konfigurace nez data |
| Fronta nad Postgresem | vyber behu je v pameti jedne instance, chce to `SKIP LOCKED` |
| Sbernice udalosti pres LISTEN/NOTIFY | dnes `EventEmitter` v pameti jedne instance |
| Vymena klice (rotace) | `v` je pripravene, prevod dat napsany neni |
| Retence a partitionovani | az u tabulek behu, viz dokument 10 |