Projekt srovnan se zasadami v D:\GitHubRepository\CLAUDE.md bez zmeny chovani.
Struktura: scripts/ (skripty konektoru) -> connectors/, src/scripts ->
src/runtime/scripts; src/index.ts jen startuje, novy src/app.ts s createApp();
routes/dashboard.ts a routes/settings.ts rozdeleny do slozek; openapi.ts
rozdelen na openapi/{index,helpers,components} a paths/* (98 cest overeno
shodnych); ticketStore, automationStore a services jsou fasady nad slozkami
data/tickets, data/automations a data/services/catalog. process.env se cte
jen v config.ts. Web: hooky v hooks/, sdilena ui/Table a ui/ServiceIcon,
surove inputy nahrazeny komponentami, sedm velkych souboru rozdeleno.
Nastroje: eslint (typescript-eslint, react-hooks v7), prettier, editorconfig,
nvmrc, .env.example, vitest; skripty lint, format, test. Lint je cisty bez
jedineho eslint-disable (nove hooky useLatest a useSyncFromSource, odvozeny
stav misto setState v effectu). noUncheckedIndexedAccess v obou tsconfig,
84 mist zuzeno bez non-null operatoru; odhalilo zalohu backoffu fronty pri
nule pokusu a Retry-After NaN pri max 0. Cely kod naformatovan prettierem.
Testy: 8 souboru, 105 testu (prava, viditelnost, podminky a opakovani
v executoru, redaktor tajemstvi, sitove guardy, migrace resitelu, tickety,
health a prihlaseni pres supertest). Testy odhalily dve chyby ve vyhodnoceni
podminek, obe opravene: chybejici castka se porovnavala jako nula a podminka
nad vystupem druheho kroku cetla hodnotu prvniho se stejnym nazvem.
Pojmenovane konstanty misto magickych hodnot, ctx.util.base64 pro skripty
konektoru, README a dokumentace aktualizovany vcetne znamych odchylek.
10 KiB
Nastavení, práva, typy ticketů, akce a widgety
Co všechno se dá nastavit z portálu a proč je to postavené takhle. Model je z 09-navrh-rozsireni.md, tady je hotový stav. Seznam funkcí a komponent, které se u toho mají použít, je v 15-rejstrik-funkci.md.
Jedna vrstva pro všechny záznamy
Firmy, uživatelé, role, řešitelé, skupiny, typy ticketů, akce a widgety mají společné to, že se u nich dělá totéž: seznam za firmu, detail, zápis, mazání, kontrola práva, zápis do auditu. Napsat to osmkrát znamená osm skoro stejných souborů, ze kterých se jeden opraví a ostatní ne.
Proto tři vrstvy, každá napsaná jednou:
| Vrstva | Kde | Co dělá |
|---|---|---|
| Úložiště | src/data/store/ |
EntityStore<T> a dvě implementace. Volající nepozná, jestli běží Postgres nebo JSON soubor. |
| API | src/routes/crud.ts |
crudRouter vyrobí pětici endpointů včetně práva a auditu. |
| Klient | components/dashboard/EntityAdmin.tsx |
Tabulka, modál, validace, mazání. |
Nová entita v nastavení pak znamená: defineStore v modulu entity, jeden řádek
v bootstrap.ts, jeden crudRouter v src/routes/settings/<entita>.ts a mount v settings/index.ts, jeden popis v
Settings.tsx. Nic víc.
crudRouter navic s volbou event publikuje <druh>.created, .updated
a .deleted s celym zaznamem v payloadu, takze klientsky sklad ciselniku
(lib/collections.tsx) se opravi bez dotazu. Po zapisu se obnovi jen cache
te entity (bootstrapDataRefresh(route)), ne vsechny.
Práva jsou data
Práv je 26 a jsou v katalogu (src/data/permissions.ts). Role je záznam,
ne konstanta v kódu: firma si může udělat vlastní roli s vlastní kombinací
práv. Systémové role (admin, agent, viewer) se měnit nedají, aby si nikdo
neodebral právo, kterým je odebírá.
Členství uživatele ve firmě nese roleIds, protože jeden člověk může být
v Automii řešitel a u Nordisu správce jejich servicedesku.
Neznámé právo se odmítne už při ukládání role. Kdyby se jen ignorovalo, překlep by znamenal roli, která tiše nic nesmí.
Prava se kontroluji u kazde route, za firmu zaznamu
Do zari 2026 se vetsina rout ptala jen "je clen firmy". Prava v katalogu
byla, ale viewer mohl zalozit konektor nebo smazat automatizaci. Ted:
| Co | Pravo |
|---|---|
| firmy | jen spravce platformy |
| uzivatele | spravce platformy, nebo user.manage jen ve sve firme |
| lide (resitele = clenove firmy) | people.manage jen ve sve firme, zaklada ucet s clenstvim |
| pozvanky | user.manage, role jen z te firmy |
| konektory (zalozeni, uprava, smazani, test) | connector.manage |
| automatizace (zalozeni, uprava, smazani, novy token) | automation.edit |
| stav incidentu | incident.manage za firmu incidentu; platformni jen spravce platformy |
| vestavene akce na ticketu | pravo akce za firmu ticketu plus strop viditelnosti |
| prepnuti na jiny ucet, audit | impersonate, audit.view |
Spravce firmy s user.manage nenastavi platformAdmin, neprida clenstvi
v jine firme, nesahne na spravce platformy a nesmaze cloveka, ktery je i
v jine firme. Duvody a rozhodnuti "kdo koho zaklada" jsou
v 07-firmy-a-prava.md.
Zalozka Lide (people.manage) uz nespravuje zvlastni zaznam resitele:
resitel je clenstvi uctu ve firme a ID resitele je ID uctu, viz
06-tickety.md. Zalozeni cloveka v Lidech zalozi ucet
(heslo nepovinne, bez nej nahodne) nebo prida clenstvi uctu, ktery uz s tim
e-mailem existuje. Upravit jde jmeno, e-mail, popisek, kapacita, externi ID
a role clenstvi; smazani odebere jen clenstvi. Sprava uctu v Nastaveni
(/users, spravce platformy) k clenstvi bere i seesAllTenant, role,
capacity, externalIds a enabled a pri uprave je zachova. Prepinac
enabled v Lidech je za clenstvi: vypne cloveka jen v teto firme, cely ucet
vypina jen sprava uzivatelu.
Firma z registru ARES
Zalozka Firmy ma vedle rucniho zalozeni cestu pres ARES
(components/dashboard/AresTenantDialog.tsx), jen pro spravce
platformy: IC nebo nazev, vyber firmy, vyber statutaru, kteri dostanou ucet
s roli spravce a zastupnou adresou IC-poradi@placeholder.cz. Ucet je
zaroven resitel (resitel je clenstvi uctu), takze jsou hned v Lidech; funkce
z rejstriku je popisek clenstvi. Zastupne
adresy se musi nahradit skutecnymi, jinak se ti lide neprihlasi. Firma pak
nese ico, dic, address a legalForm, IC je unikatni. Endpointy jsou
v 04-api.md.
Navigace chodí ze serveru
Co uživatel vidí za záložky, je průnik dvou věcí:
- co má firma zaplacené a zapnuté (
tenantFeatures), - na co má člověk právo.
Počítá to server (navFor) a klient jen kreslí. Kdyby si klient záložky
dovozoval sám, počítalo by se to na dvou místech a jednou by se to rozešlo -
a ta chyba by znamenala odkaz do 403.
Typy ticketů a tagy
Typ ticketu nese vlastní pole (klíč, popisek, typ, povinnost, nápověda) a může mít vlastní workflow stavů. Tag je volné označení bez polí za ním.
Rozdíl není kosmetický:
- Typ znamená "za tímhle stojí data". Objednávka má číslo objednávky a částku, takže se na ni dá navázat akce, která ta data potřebuje.
- Tag znamená "takhle si to označuju". Hodí se na filtry a widgety.
Akce se proto váže na typ nebo tag, ale akci s daty má smysl vázat na typ.
Pole object a list se v detailu ticketu ručně nevyplňují - plní je
automatizace. Textarea s JSONem by tam byla past, ne pohodlí.
Vydefinované akce
Akce a automatizační stromy jsou dvě různé věci a nemají mezi sebou žádnou společnou mezivrstvu. Automatizace běží sama, akce je tlačítko, které zmáčkne člověk.
Tělo akce je jedno z trojice:
| Tělo | Kdy | Příklad |
|---|---|---|
| Operace konektoru | běžný případ | odeslat objednávku do iDokladu |
| Vlastní strom | akce má víc kroků a rozhodování | dohledat kontakt, vystavit fakturu, odeslat e-mailem |
| Skript | nic z toho nestačí | vlastní výpočet nebo cizí API, které v katalogu není |
Server vrací k ticketu jen akce, které v té situaci opravdu jdou spustit: sedí typ nebo tag, projdou podmínky a volající na ně má právo. Klient nefiltruje nic.
Spuštění akce vrací 200 i když akce selhala - selhání akce není chyba API. V odpovědi je celé chybové hlášení a celý průběh se zapíše do logu ticketu.
Vestavěné akce (typ, tagy, skupina, přiřazení, stav, komentář) jsou zvlášť: mění ticket sám, ne cizí službu, a každá má vlastní právo.
Vlastní widgety
Widget je definice zdroje dat plus způsob zobrazení. Zdroj je ticket nebo automatizace, k tomu filtr a případné seskupení (podle stavu, kanálu, řešitele, typu, tagu).
Data pro celý přehled chodí jedním requestem. Deset dlaždic nesmí znamenat deset dotazů.
Viditelnost je stejná jako u služeb: všichni, konkrétní firmy, konkrétní lidé, nebo jen správce.
Přepnutí na jiný účet
Správce platformy se může podívat na portál očima konkrétního uživatele. Dvě věci, na kterých to stojí:
- Výchozí je jen pro čtení. Podívat se, co klient vidí, je mnohem častější než za něj něco měnit, a nechtěný zápis pod cizím jménem je to nejhorší, co se tady může stát. Zápis se musí zapnout vědomě a middleware ho jinak odmítne u všeho kromě GET.
- Všechno je v auditu včetně toho, kdo to doopravdy byl. Bez auditu nemá přepínání účtů co dělat v produktu.
Svůj původní token si klient odkládá do sessionStorage. Server o něm nic neví,
takže ho nemůže ani omylem prodloužit.
Co se ukládá a co se drží v paměti
| Data | Jak | Proč |
|---|---|---|
| Firmy, uživatelé, role, řešitelé, skupiny, typy, akce, widgety, záložky | withCache |
Čtou se při každém requestu, mění se zřídka. Kopie v paměti (byId je Map), obnova jen te entity po zápisu. |
| Tickety včetně logu, automatizace, incidenty, rozložení dashboardu | withMirror |
Mění se v paměti za provozu, po každé změně se celý záznam zapíše. Zápisy téhož ID jdou za sebou, ne naráz. |
| Audit | přímo do úložiště | Jen se připisuje, nikdy nečte při každém requestu. Oreza se davkou po 50 zapisech nebo jednou za minutu. |
| Konektory | vlastní úložiště | Nesou šifrovaná tajemství a potřebují částečný unikátní index. Viz 14-databaze.md. |
Čítače ID se při startu dopočítají z uložených záznamů, takže nový ticket nikdy nepřepíše starý.