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:
co-authored by
Claude Fable 5.1
parent
0c405ea55a
commit
104ae36783
@@ -86,6 +86,38 @@ s tim, co uzivatel smi. Dvoji vypocet se jednou rozejde.
|
||||
firmu jako povinny argument. Clovek muze byt spravce v jedne firme a bezny
|
||||
uzivatel v druhe.
|
||||
|
||||
**Pravo se kontroluje u kazde route, za firmu zaznamu.** Clenstvi ve firme
|
||||
neni pravo. Konektor chce `connector.manage`, automatizace `automation.edit`,
|
||||
uzivatele `user.manage` a jen ve sve firme, firmy jen spravce platformy.
|
||||
Vestavene akce na ticketu jdou pres `builtinAction` v `ticketActions.ts`, kde
|
||||
se pravo pta za firmu **toho ticketu** a ticket se nejdriv hleda pres strop
|
||||
viditelnosti. Pristup se pocita jednou na request (`attachAccess`,
|
||||
`req.access`) a routy si firmu berou pres `tenantOrDeny`.
|
||||
|
||||
**Udalost nese firmu.** `publish(kind, message, payload, tenantId)` ma
|
||||
`tenantId` povinny a posledni, `null` znamena cela platforma. Stream ji
|
||||
pouziva k filtru, takze udalost bez firmy by videl kazdy. Zmena entity
|
||||
v nastaveni publikuje `<druh>.created|updated|deleted` s celym zaznamem, aby
|
||||
klient nemusel po kazde udalosti znovu nacitat.
|
||||
|
||||
**Ciselniky ma klient ve skladu, provozni data ne.** Lide, skupiny, typy,
|
||||
sluzby, konektory a pristup se berou z `lib/collections.tsx`
|
||||
(`useCollection`), ktery se opravuje z udalosti. Tickety, behy a statistiky
|
||||
zustavaji `useApiQuery` se strankovanim - jsou velke, meni se porad a strop
|
||||
viditelnosti pocita server. Nova stranka nema volat `apiFetch` na ciselnik
|
||||
primo.
|
||||
|
||||
**Opakuje se jen to, co samo rekne `retryable`.** Vychozi je ne. Chyba
|
||||
spojeni, timeout pred odeslanim, 5xx a 429 ano; 401, 403, 404, spatny vstup,
|
||||
`ctx.fail` a chyba v kodu ne - ty konci hned a zakladaji incident. Timeout uz
|
||||
odeslaneho volani MCP se neopakuje, protoze MCP nema idempotencni klic.
|
||||
Pravidlo je nad `StepResult` v `executor.ts` a plati pro kazdy novy druh kroku.
|
||||
|
||||
**Typ odpovedi API je jednou, v `src/shared`.** Web ho nekopiruje, bere ho
|
||||
pres alias `@shared/*`; `web/src/types/` je jen fasada. Kopie na klientovi se
|
||||
rozejde se serverem a prekladac to nepozna. Prevod je hotovy: 16 modulu
|
||||
v `src/shared` vcetne uctu (`users.ts`), web nema zadny vlastni typ API.
|
||||
|
||||
## Jak vznika operace, kterou clovek vybere v builderu
|
||||
|
||||
Tri cesty, kazda ma svuj duvod:
|
||||
@@ -131,14 +163,16 @@ tam je operace vlastnost sluzby, ne napojeni.
|
||||
|
||||
## Uloziste ma tri rezimy
|
||||
|
||||
| Rezim | Kdy | Prezije |
|
||||
| ---------- | ----------------------------------- | -------------------- |
|
||||
| `postgres` | je `DATABASE_URL` i `SECRETS_KEY` | vse |
|
||||
| `file` | neni databaze, ale je datova slozka | restart, ne redeploy |
|
||||
| `memory` | ani jedno | nic |
|
||||
| Rezim | Kdy | Prezije |
|
||||
| ---------- | ------------------------------------------------------ | -------------------- |
|
||||
| `postgres` | je `DATABASE_URL`, migrace prosly a je cim sifrovat | vse |
|
||||
| `file` | neni databaze, ale je datova slozka | restart, ne redeploy |
|
||||
| `memory` | ani jedno | nic |
|
||||
|
||||
**Rozhodnuti je na jednom miste** (`connectorStore.ts`). Kdyby se rozlezlo po
|
||||
kodu, jedno misto by se zapomnelo a chovalo by se pak jinak nez zbytek.
|
||||
**Rozhodnuti je na jednom miste** (`initStores` v `store/index.ts`) a plati
|
||||
pro vsechna uloziste vcetne konektoru. Kdyby se rozlezlo po kodu, jedno misto
|
||||
by se zapomnelo a chovalo by se pak jinak nez zbytek - presne to se stalo,
|
||||
kdyz mely konektory vlastni rozhodnuti.
|
||||
|
||||
Nasazeni bezi v rezimu `file`. Ma to dusledek, ktery je poznat az pri zatezi:
|
||||
**kazda zmena prepisuje celou kolekci** jako formatovany JSON. U ticketu, ktere
|
||||
@@ -167,6 +201,17 @@ ho napsal.
|
||||
**Novy text v portalu** patri do `web/src/i18n/cs.ts` a klic do `en.ts`.
|
||||
Anglictina je `Partial`, takze nemusi byt uplna - co chybi, spadne na cestinu.
|
||||
|
||||
**Novy typ odpovedi** patri do `src/shared`, web ho nekopiruje.
|
||||
|
||||
**Nova route** vznika pres `safeRouter`, ne `Router()`, aby odmitnuta promise
|
||||
skoncila jako 500 a ne padem procesu. Chyba validace jde pres
|
||||
`validationError`, aby mela `issues` ve stejnem tvaru jako zbytek. Kazde
|
||||
volani ven (HTTP, MCP, SMTP, ARES) cte telo pres `src/net/guard.ts`.
|
||||
|
||||
**Novy formular** sklada `Field`, `Input`, `Select`, `Textarea`
|
||||
z `components/ui/form/` a odeslani pres `useSubmit`. Vlastni tridy vstupniho
|
||||
pole ve strance jsou to, co se prave odstranilo z patnacti mist.
|
||||
|
||||
**Novy sloupec** znamena novou migraci **a** doplneni obou uloziste,
|
||||
souborového i databazoveho. Rozhrani je jedno, implementace dve.
|
||||
|
||||
|
||||
@@ -67,6 +67,16 @@ React aplikaci ze slozky `dist/public`.
|
||||
| Helpdesk pro zadavatele | hotovo | pozadavek vidi zadavatel i resitel, kazdy ze sve strany |
|
||||
| Odesilani e-mailu pres SMTP | hotovo | konektor se schrankou firmy, HTML telo s promennymi |
|
||||
| Odesilani e-mailu z formulare | chybi | poptavka se zatim jen loguje |
|
||||
| Jeden rezim uloziste | hotovo | `initStores` rozhoduje pro vsechna uloziste naraz |
|
||||
| Zapisy serazene za sebou | hotovo | `withMirror` radi zapisy tehoz zaznamu, audit se oreza |
|
||||
| Worker jako pool | hotovo | ctyri behy naraz nezavisle, tlukot, opakovani jen kdyz ma smysl |
|
||||
| Prava za firmu u kazde route | hotovo | firmy, uzivatele, konektory, automatizace, akce ticketu |
|
||||
| Udalosti za firmu | hotovo | stream filtruje, entity hlasi vznik, zmenu a smazani |
|
||||
| Strankovani ticketu a behu | hotovo | `limit`, `offset`, `X-Total-Count` |
|
||||
| Klientsky sklad ciselniku | hotovo | lide, skupiny, typy, sluzby, konektory, pristup |
|
||||
| Formularova vrstva | hotovo | `ui/form`, `useSubmit`, `options`, jedna sada trid |
|
||||
| Firma z registru ARES | hotovo | IC nebo nazev, statutari jako ucty, jen spravce platformy |
|
||||
| Sdilene typy `src/shared` | hotovo | web je re-exportuje pres `@shared/*`, nic nekopiruje |
|
||||
|
||||
## Znama omezeni
|
||||
|
||||
@@ -74,17 +84,24 @@ Data prezijou restart, ale ne redeploy, kdyz neni databaze. Rezim se pozna
|
||||
v portalu i v `/health/ready` a rozhoduje o nem jedno misto, viz
|
||||
[14-databaze.md](14-databaze.md):
|
||||
|
||||
| Rezim | Kdy | Nasledek |
|
||||
| ---------- | ---------------------------------- | ---------------------------- |
|
||||
| `postgres` | je `DATABASE_URL` a migrace prosly | data se neztraci |
|
||||
| `file` | neni databaze, je `DATA_DIR` | prezije restart, ne redeploy |
|
||||
| `memory` | neni ani `DATA_DIR` | ztrati se pri restartu |
|
||||
| Rezim | Kdy | Nasledek |
|
||||
| ---------- | ------------------------------------------------- | ---------------------------- |
|
||||
| `postgres` | je `DATABASE_URL`, migrace prosly a je cim sifrovat | data se neztraci |
|
||||
| `file` | neni databaze, je `DATA_DIR` | prezije restart, ne redeploy |
|
||||
| `memory` | neni ani `DATA_DIR` | ztrati se pri restartu |
|
||||
|
||||
Beh automatizaci uz existuje, ale je **synchronni v requestu**: webhook ceka,
|
||||
nez cely strom dobehne, a pri padu procesu se rozdelany beh ztrati. Neni fronta
|
||||
ani opakovani. Co to znamena pro vetsi provoz a co s tim, je zmerene
|
||||
v [19-kapacita-200-firem.md](19-kapacita-200-firem.md), navrh fronty
|
||||
v [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md).
|
||||
Rezim je jeden pro vsechna uloziste vcetne konektoru. Driv se mohlo stat, ze
|
||||
konektory jely z databaze a tickety ze souboru.
|
||||
|
||||
Beh automatizaci jde pres frontu a worker: webhook odpovi 202 a strom se
|
||||
vykona na pozadi, pri chybe se opakuje jen to, co muze pominout. Fronta je ale
|
||||
**v pameti jednoho procesu**: vic instanci by si vzalo tentyz beh, nad
|
||||
Postgresem to chce `SKIP LOCKED`. Popis je
|
||||
v [20-fronta-a-runtime.md](20-fronta-a-runtime.md), rozbor kapacity
|
||||
v [19-kapacita-200-firem.md](19-kapacita-200-firem.md).
|
||||
|
||||
Limity requestu (prihlaseni, kontakt, pozvanky) jsou v pameti jedne instance,
|
||||
stejne jako stream. Pri vice instancich by kazda pocitala zvlast.
|
||||
|
||||
Obsah verejneho webu je ukazkovy. Nazev firmy, reference, tym i cisla jsou
|
||||
vymyslene a pred ostrym pouzitim se musi nahradit. Firemni udaje jsou na jednom
|
||||
@@ -99,21 +116,17 @@ co ukazat i na prazdne instanci.
|
||||
|
||||
## Dalsi krok
|
||||
|
||||
Nejuzitecnejsi pristavek je runtime. Strom uz nese vsechno potrebne: spoustec
|
||||
s parametry, podminky a u ticketu i kanalu nastavena pole se sablonami. Chybi
|
||||
jen to, co ho vykona. Do te doby je ulozena automatizace popis zameru, ne provoz.
|
||||
Runtime je hotovy: fronta, worker jako pool, opakovani jen u chyb, ktere
|
||||
mohou pominout, incident z koncove chyby. Dalsi krok je **vic instanci**, tedy
|
||||
vyber z fronty nad Postgresem se `SKIP LOCKED`, sdileny kanal pro stream
|
||||
a sdileny citac limitu requestu.
|
||||
|
||||
Vedle toho zbyva prevest na `inputs` i ostatni konektory a doplnit odkazy
|
||||
na vystup predchoziho kroku, ne jen na spoustec. Podrobnosti
|
||||
Vedle toho zbyva prevest na `inputs` i ostatni konektory. Podrobnosti
|
||||
v [05-dashboard-a-builder.md](05-dashboard-a-builder.md).
|
||||
|
||||
Za rozmysleni stoji evidence bugs a wishes. Zamerne to nejsou tickety,
|
||||
duvod je v [06-tickety.md](06-tickety.md).
|
||||
|
||||
Prvni cast navrhu uz je hotova: vykonna cast konektoru, tedy skripty
|
||||
s manifestem a kontrolou parametru, viz [11-skripty-konektoru.md](11-skripty-konektoru.md).
|
||||
Runner je pripraveny, chybi nad nim fronta.
|
||||
|
||||
Datove modely a prava z [09-navrh-rozsireni.md](09-navrh-rozsireni.md) jsou
|
||||
hotove, popis stavu je v [17-nastaveni-a-prava.md](17-nastaveni-a-prava.md).
|
||||
Navrh k rozhodnuti zustava [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md)
|
||||
|
||||
@@ -31,28 +31,44 @@ image jen `dist`, takze staci jedna slozka.
|
||||
| `src/config.ts` | cteni environment variables, normalizace `ROOT_PATH` |
|
||||
| `src/openapi.ts` | OpenAPI definice vcetne `servers` s prefixem proxy |
|
||||
| `src/types.ts` | typy uzivatele a JWT payloadu |
|
||||
| `src/middleware/auth.ts` | `requireAuth`, `requireRole` |
|
||||
| `src/events/bus.ts` | sbernice udalosti, ze ktere cerpa SSE stream |
|
||||
| `src/shared/` | ciste typove moduly API, jediny zdroj typu pro server i web |
|
||||
| `src/middleware/auth.ts` | `requireAuth`, `requireRole`, `requirePlatformAdmin` |
|
||||
| `src/middleware/asyncHandler.ts` | `wrap`, `safeRouter`: odchyceni odmitnute promise v handleru |
|
||||
| `src/middleware/tenant.ts` | `attachAccess`, `tenantOrDeny`, `scopeOrDeny`: firma requestu na jednom miste |
|
||||
| `src/middleware/rateLimit.ts` | limit requestu v pameti, 429 s `Retry-After` |
|
||||
| `src/middleware/validation.ts` | `validationError`, jeden tvar chyby validace |
|
||||
| `src/lib/secure.ts` | `timingSafeEqualString` pro tokeny v adrese |
|
||||
| `src/net/guard.ts` | kontrola adresy, cteni tela s limitem, popis chyby site - pro vsechno, co vola ven |
|
||||
| `src/events/bus.ts` | sbernice udalosti, ze ktere cerpa SSE stream, udalost nese firmu |
|
||||
| `src/routes/auth.ts` | prihlaseni, odhlaseni, kdo jsem |
|
||||
| `src/routes/dashboard.ts` | data portalu, katalog konektoru, CRUD automatizaci |
|
||||
| `src/routes/stream.ts` | SSE stream zmen |
|
||||
| `src/routes/simulate.ts` | vyvolani provoznich udalosti |
|
||||
| `src/routes/dashboard.ts` | data portalu, tickety, behy, CRUD automatizaci |
|
||||
| `src/routes/ticketActions.ts` | akce nad ticketem vcetne vestavenych, pravo za firmu ticketu |
|
||||
| `src/routes/settings.ts` | CRUD entit pres `crud.ts`, uzivatele, ARES |
|
||||
| `src/routes/ares.ts` | firma z registru ARES, jen spravce platformy |
|
||||
| `src/routes/connectors.ts` | konektory firmy, overeni, nastroje MCP |
|
||||
| `src/routes/stream.ts` | SSE stream zmen, filtr podle firem uzivatele |
|
||||
| `src/routes/webhook.ts` | verejny prijem dat do automatizace |
|
||||
| `src/routes/contact.ts` | poptavkovy formular z webu |
|
||||
| `src/ares/client.ts` | klient verejneho API ARES |
|
||||
| `src/data/store/` | tri rezimy uloziste, `withCache`, `withMirror`, `initStores` |
|
||||
| `src/data/snapshot.ts` | atomicky zapis JSONu pro rezim `file` |
|
||||
| `src/data/ticketStore.ts` | tickety, jejich resitele, log prubehu, prehled vytizeni |
|
||||
| `src/data/people.ts` | resitele ticketu - oddeleni od uzivatelu portalu |
|
||||
| `src/data/tenants.ts` | firmy, ktere portal pouzivaji |
|
||||
| `src/data/tenants.ts` | firmy, ktere portal pouzivaji, vcetne udaju z ARES |
|
||||
| `src/data/access.ts` | kdo co vidi - jedno misto pro cely portal |
|
||||
| `src/data/widgets.ts` | katalog widgetu prehledu |
|
||||
| `src/data/dashboardLayouts.ts` | rozlozeni dashboardu za dvojici uzivatel a firma |
|
||||
| `src/data/incidentStore.ts` | incidenty vcetne zmen a udalosti |
|
||||
| `src/data/incidentStore.ts` | incidenty vcetne zmen a udalosti, filtr na firmu povinny |
|
||||
| `src/data/automationStore.ts` | automatizace, strom akci, tokeny webhooku |
|
||||
| `src/data/connectors.ts` | katalog konektoru, jejich spousteču a akci |
|
||||
| `src/data/services.ts` | katalog sluzeb, jejich spousteču a akci |
|
||||
| `src/data/conditions.ts` | typy parametru a operatory podminek |
|
||||
| `src/data/templates.ts` | sablony `{{parametr}}` v nastaveni kroku |
|
||||
| `src/data/flowScope.ts` | co je videt v kterem miste stromu |
|
||||
| `src/data/users.ts` | demo uzivatele |
|
||||
| `src/data/users.ts` | uzivatele portalu, demo ucty |
|
||||
| `src/data/mock.ts` | souhrn pro prehled a casova rada grafu |
|
||||
| `src/runtime/` | fronta, worker, executor stromu, vestavene kroky, sandbox skriptu firmy |
|
||||
| `src/scripts/` | skripty konektoru: registr, runner, HTTP, pomocne funkce |
|
||||
| `src/mcp/` | klient MCP, prihlaseni, dialekty, `errors.ts` se spolecnou chybou prihlaseni |
|
||||
|
||||
## Mapa kodu - web
|
||||
|
||||
@@ -62,16 +78,25 @@ image jen `dist`, takze staci jedna slozka.
|
||||
| `web/src/App.tsx` | routovani, portal se nacita lazy |
|
||||
| `web/src/index.css` | design tokeny a vlastni utility Tailwindu |
|
||||
| `web/src/config/brand.ts` | vsechny firemni udaje na jednom miste |
|
||||
| `web/src/lib/api.ts` | fetch wrapper, sprava tokenu, skladani adres |
|
||||
| `web/src/lib/api.ts` | fetch wrapper, sprava tokenu, skladani adres, `auth:expired` na 401 |
|
||||
| `web/src/lib/eventStream.ts` | cteni SSE streamu pres fetch |
|
||||
| `web/src/lib/useApiQuery.ts` | nacitani dat vcetne obnoveni pri udalosti |
|
||||
| `web/src/lib/useApiQuery.ts` | nacitani dat, cache, spolecny debounce, `refreshing` misto odmontovani |
|
||||
| `web/src/lib/collections.tsx` | klientsky sklad ciselniku za firmu, opravovany z udalosti |
|
||||
| `web/src/lib/ticketEvents.ts` | oprava seznamu ticketu z `payload.ticket` bez dotazu |
|
||||
| `web/src/lib/useSubmit.ts` | odeslani formulare: `saving`, chyba, reset na jednom miste |
|
||||
| `web/src/lib/options.ts` | pevne ciselniky (priority) |
|
||||
| `web/src/lib/useUnsavedChanges.ts` | varovani pri odchodu z rozepsaneho formulare |
|
||||
| `web/src/lib/flow.ts` | ciste funkce nad stromem automatizace |
|
||||
| `web/src/components/dashboard/` | shell portalu, dlazdice, graf, stream, simulace |
|
||||
| `web/src/components/dashboard/flow/` | strom akci, vyber kroku, nastaveni poli akce |
|
||||
| `web/src/types/` | fasada nad `src/shared` (alias `@shared/*`), zadne vlastni typy API |
|
||||
| `web/src/components/ui/` | zakladni prvky, `Chip` |
|
||||
| `web/src/components/ui/form/` | `Field`, `Input`, `Select`, `Textarea`, `controlClass`: jedna sada trid |
|
||||
| `web/src/components/dashboard/` | shell portalu, dlazdice, graf, stream, `TicketCard` |
|
||||
| `web/src/components/dashboard/flow/` | strom akci: `FlowCanvas` a karty `ActionCard`, `ConditionCard`, `ForeachCard`, `StepControls` |
|
||||
| `web/src/components/dashboard/TicketTrace.tsx` | log ticketu jako strom |
|
||||
| `web/src/components/dashboard/TicketWorkload.tsx` | prehled, kdo co ma u sebe |
|
||||
| `web/src/components/home/` | sekce homepage |
|
||||
| `web/src/pages/` | jedna stranka je jeden soubor |
|
||||
| `web/src/pages/dashboard/connectors/` | casti stranky Konektory: karta, editor, log, nastroje |
|
||||
|
||||
## Klicova rozhodnuti
|
||||
|
||||
@@ -88,8 +113,22 @@ Cenou je rucni parsovani a rucni znovupripojeni v `web/src/lib/eventStream.ts`.
|
||||
**Ceske cesty v URL.** `/sluzby`, `/o-nas`, `/prihlaseni`, `/dashboard/tickety`.
|
||||
Kod zustava anglicky.
|
||||
|
||||
**Data v pameti.** Vedome zjednoduseni prototypu. Uloziste jsou oddelena od rout,
|
||||
takze napojeni na databazi znamena prepsat soubory v `src/data/`, ne endpointy.
|
||||
**Uloziste je za rozhranim.** Tri rezimy (Postgres, soubor, pamet), jedno
|
||||
rozhrani a **jedno rozhodnuti** v `initStores`. Routy nevedi, ktery rezim jede.
|
||||
Podrobnosti v [14-databaze.md](14-databaze.md).
|
||||
|
||||
**Typy API jsou jednou.** Ciste typove moduly v `src/shared` ctou server i web,
|
||||
web pres alias `@shared/*`. Kopie typu na klientovi se jednou rozejde se
|
||||
serverem a prekladac to nepozna. `web/src/types/` je jen fasada, ktera je
|
||||
re-exportuje; ucet uzivatele bere i `AuthContext` ze `@shared/users`.
|
||||
|
||||
**Kazdy async handler je odchyceny.** Routy vznikaji pres `safeRouter`,
|
||||
odmitnuta promise skonci jako 500 s logem, ne padem procesu. Bez toho stacil
|
||||
jeden zapomenuty `try` a AppFactory restartovala container.
|
||||
|
||||
**Firma requestu se pocita jednou.** `attachAccess` da do `req.access`, co
|
||||
uzivatel smi, a `tenantOrDeny` z toho odvodi firmu. Kazda route, ktera si to
|
||||
pocitala sama, to delala trochu jinak.
|
||||
|
||||
**Filtr na firmu je povinny argument.** `listTickets`, `listPeople`
|
||||
i `listAutomations` vyzaduji `tenantIds`. Zapomenuty filtr tak neznamena "vse",
|
||||
|
||||
+93
-11
@@ -85,6 +85,9 @@ Vyzaduji `Authorization: Bearer <token>`:
|
||||
| GET | `/api/dashboard/settings/people-overview` |
|
||||
| GET | `/api/dashboard/settings/users-overview` |
|
||||
| PATCH | `/api/dashboard/settings/users/:id/password` |
|
||||
| GET | `/api/dashboard/settings/ares/companies` |
|
||||
| GET | `/api/dashboard/settings/ares/companies/:ico/persons` |
|
||||
| POST | `/api/dashboard/settings/ares/tenants` |
|
||||
| POST | `/api/admin/impersonate` |
|
||||
| POST | `/api/admin/impersonate/stop` |
|
||||
| GET | `/api/admin/impersonate/candidates` |
|
||||
@@ -102,20 +105,35 @@ fabrika (`src/routes/crud.ts`):
|
||||
Jednotny pro cele API:
|
||||
|
||||
```json
|
||||
{ "error": "validation_error", "message": "Zadejte platny e-mail." }
|
||||
{
|
||||
"error": "validation_error",
|
||||
"message": "Zadejte platny e-mail.",
|
||||
"issues": [{ "field": "email", "message": "Zadejte platny e-mail." }]
|
||||
}
|
||||
```
|
||||
|
||||
| HTTP | `error` | Kdy |
|
||||
| ---- | --------------------- | ------------------------------------------- |
|
||||
| 400 | `validation_error` | vstup neprosel schematem |
|
||||
| 400 | `validation_error` | vstup neprosel schematem, `issues` po polich |
|
||||
| 401 | `unauthorized` | chybi nebo neplatny token |
|
||||
| 401 | `invalid_credentials` | spatny e-mail nebo heslo |
|
||||
| 403 | `forbidden` | nedostatecna role |
|
||||
| 404 | `not_found` | zaznam nebo endpoint neexistuje |
|
||||
| 403 | `forbidden` | nedostatecne pravo v dane firme |
|
||||
| 404 | `not_found` | zaznam nebo endpoint neexistuje, nebo je cizi firmy |
|
||||
| 409 | ruzne | operace nedava v danem stavu smysl |
|
||||
| 429 | `too_many_requests` | prekrocen limit requestu, hlavicka `Retry-After` |
|
||||
| 500 | `internal_error` | neodchycena chyba, detail jen mimo produkci |
|
||||
|
||||
`message` je vzdy cesky a je urcena k zobrazeni uzivateli.
|
||||
`message` je vzdy cesky a je urcena k zobrazeni uzivateli. Chybu validace
|
||||
sklada `validationError` v `src/middleware/validation.ts`, aby `issues` mely
|
||||
vsude stejny tvar a formular umel chybu ukazat u pole.
|
||||
|
||||
Limity (`src/middleware/rateLimit.ts`) jsou jen na verejnych endpointech, kde
|
||||
se da hadat: prihlaseni 20 pokusu za 15 minut, kontakt 5 za hodinu, prijeti
|
||||
pozvanky 5 za 15 minut. Pocita se podle adresy klienta, proto ma Express
|
||||
`trust proxy` = 1 - bez toho by vsichni za Caddy sdileli jeden limit.
|
||||
|
||||
Kazdy asynchronni handler je obaleny (`safeRouter` v `src/middleware/asyncHandler.ts`).
|
||||
Odmitnuta promise je 500 s logem, ne pad procesu.
|
||||
|
||||
## Autentizace
|
||||
|
||||
@@ -139,6 +157,21 @@ Typy udalosti: `ticket.created`, `ticket.updated`, `ticket.assigned`,
|
||||
`automation.created`, `automation.updated`, `automation.deleted`,
|
||||
`automation.run`, `webhook.received`.
|
||||
|
||||
K tomu udalosti entit `tenant`, `user`, `role`, `person`, `group`,
|
||||
`ticketType`, `action`, `widget`, `connector`, `feature` s priponou
|
||||
`.created`, `.updated`, `.deleted`. Payload je `{ id, <druh>: zaznam }`,
|
||||
u smazani jen `{ id }`. Publikuje je `crudRouter` (volba `event`), routy
|
||||
konektoru a PUT features. Klient z nich opravuje sklad ciselniku bez dotazu.
|
||||
|
||||
**Kazda udalost nese `tenantId`** (`null` = cela platforma). Stream posila jen
|
||||
udalosti firem, do kterych uzivatel patri, a to i v historii po pripojeni.
|
||||
Udalost s `payload.userId` jde jen tomu cloveku. Spravce platformy vidi vse.
|
||||
Driv videl kazdy prihlaseny udalosti vsech firem - nazev ticketu cizi firmy
|
||||
v bubline je unik dat, i kdyz se na ticket nedostane.
|
||||
|
||||
`ticket.updated`, `ticket.assigned` a `ticket.resolved` nesou
|
||||
v `payload.ticket` cely ticket, aby klient opravil seznam na miste.
|
||||
|
||||
Klient se pripojuje pres fetch s hlavickou `Authorization`, ne pres EventSource.
|
||||
Duvod je v [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md).
|
||||
|
||||
@@ -166,8 +199,9 @@ curl -X POST https://services.csbot.cz/apps/<app-id>/webhook/<token> \
|
||||
-d '{"customer":"Nordis","score":18}'
|
||||
```
|
||||
|
||||
Prototyp pozadavek prijme, zvaliduje a zapocita do metrik, ale strom akci
|
||||
nevykona - runtime neexistuje.
|
||||
Token se porovnava v konstantnim case (`timingSafeEqualString`
|
||||
v `src/lib/secure.ts`), stejne jako token prijmu a kod pozvanky. V logu
|
||||
requestu je z tokenu videt jen prvnich sest znaku.
|
||||
|
||||
## Firmy a pohledy
|
||||
|
||||
@@ -192,6 +226,11 @@ bez vysvetleni.
|
||||
Odpoved nese vedle `items` jeste `meId`. Klient podle nej pozna, ktere tickety
|
||||
jsou jeho, a jestli ma vubec smysl nabizet filtr "moje".
|
||||
|
||||
**Strankovani.** `/tickets` a `/runs` berou `limit` a `offset`, `limit` nejvys
|
||||
500. Celkovy pocet je v hlavicce `X-Total-Count`, u ticketu i v tele jako
|
||||
`total`. Bez `limit` se vraci vse jako driv (u behu poslednich 50), aby se
|
||||
nerozbily stavajici odkazy. Klient cte hlavicku pres `apiFetchWithMeta`.
|
||||
|
||||
Filtrovani dela **server**, ne klient. Seznam a prehled vytizeni tak nikdy
|
||||
neukazuji jina cisla. Vyhledavaci pole v portalu je jina vec - to jen dohledava
|
||||
v uz nactenem seznamu.
|
||||
@@ -246,7 +285,26 @@ selhala**. Selhani akce neni chyba API. V odpovedi je `ok`, `summary`, `detail`
|
||||
s celym chybovym hlasenim a `durationMs`. Cely prubeh se zapise do logu ticketu.
|
||||
|
||||
Vestavene akce (`type`, `tags`, `group`, `assign`, `status`, `comment`) jsou
|
||||
zvlast: meni ticket sam, ne cizi sluzbu, a kazda ma vlastni pravo.
|
||||
zvlast: meni ticket sam, ne cizi sluzbu, a kazda ma vlastni pravo. Vsechny
|
||||
vcetne `claim` jdou pres `builtinAction` v `src/routes/ticketActions.ts`, kde
|
||||
se pravo pta za firmu ticketu a ticket se nejdriv najde pres strop
|
||||
viditelnosti (`visibleTicketOrDeny`). Driv mely `assign`, `status` a `comment`
|
||||
vlastni handlery a kazdy se ptal jinak.
|
||||
|
||||
## Firma z registru ARES
|
||||
|
||||
Jen spravce platformy (`/api/dashboard/settings/ares`). Popis rozhodnuti je
|
||||
v [07-firmy-a-prava.md](07-firmy-a-prava.md).
|
||||
|
||||
| Endpoint | Co vraci |
|
||||
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `GET ares/companies?query=` | same cislice (1 az 8) hledaji IC presne, jinak nazev, nejvys 10. U firmy, ktera uz v portalu je, `existingTenantId` |
|
||||
| `GET ares/companies/{ico}/persons` | soucasni statutari a prokura z verejneho rejstriku, u kazdeho navrzeny e-mail `IC-poradi@placeholder.cz` |
|
||||
| `POST ares/tenants` | zalozi firmu a ucty vybranych osob, vraci firmu a seznam uctu |
|
||||
|
||||
Chyba registru je `ares_error` s kodem podle toho, co ARES vratil - neni to
|
||||
chyba naseho API a nema se opakovat automaticky. Adresa registru je
|
||||
`ARES_BASE_URL`.
|
||||
|
||||
## Prijem udalosti do ticketu
|
||||
|
||||
@@ -284,9 +342,33 @@ a kdy se to zkusi znovu.
|
||||
## Prava a navigace
|
||||
|
||||
`GET /api/dashboard/access` vraci `permissions` (efektivni prava po slouceni
|
||||
roli), `nav` (zalozky, ktere ma volajici videt) a `platformAdmin`. Klient podle
|
||||
toho kresli, ale **nic si nedovozuje** - kdo co smi, rozhoduje server u kazdeho
|
||||
requestu znovu.
|
||||
roli), `nav` (zalozky, ktere ma volajici videt), `platformAdmin` a `roleNames`
|
||||
(nazvy roli v prepnute firme, pro popisek u uctu). Klient podle toho kresli,
|
||||
ale **nic si nedovozuje** - kdo co smi, rozhoduje server u kazdeho requestu
|
||||
znovu.
|
||||
|
||||
### Kdo co smi, po routach
|
||||
|
||||
Pravo se vzdy pta **za firmu zaznamu**, ne za prepnutou firmu. Cizi firma je
|
||||
404, chybejici pravo 403.
|
||||
|
||||
| Co | Pravo |
|
||||
| ----------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| firmy CRUD, ARES | spravce platformy |
|
||||
| uzivatele CRUD | spravce platformy, nebo `user.manage` jen v ramci sve firmy |
|
||||
| pozvanky | `user.manage`, role jen z te firmy |
|
||||
| konektory create, update, delete, test | `connector.manage` |
|
||||
| automatizace create, update, delete, regenerate | `automation.edit` |
|
||||
| `/services`, `/connectors/services` | clenstvi ve firme |
|
||||
| assign, status, comment, claim na ticketu | prava vestavene akce za firmu ticketu plus strop viditelnosti |
|
||||
| `/api/admin/impersonate*` | `impersonate` |
|
||||
| `/api/admin/audit` | `audit.view` |
|
||||
| `/storage`, `/scripts` s cestami na serveru | cesty jen spravci platformy, ostatni dostanou odpoved bez nich |
|
||||
|
||||
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 - ten ucet neni jen jeho. Podrobnosti
|
||||
v [07-firmy-a-prava.md](07-firmy-a-prava.md).
|
||||
|
||||
`GET /api/dashboard/settings/catalog` vraci katalog prav a modulu, aby formular
|
||||
role nemel seznam prav napsany v kodu klienta.
|
||||
|
||||
@@ -25,10 +25,49 @@ Portal drzi jedno SSE spojeni pro celou aplikaci. Zajistuje ho
|
||||
- Prichozi udalosti ukazuje `EventToasts` jako bubliny vpravo dole.
|
||||
- Data se obnovuji sama. `useApiQuery` ma volitelny `refetchOn` se seznamem typu
|
||||
udalosti, po kterych se ma dotaz zopakovat. Vice udalosti tesne po sobe se
|
||||
slouci do jednoho nacteni.
|
||||
slouci do jednoho nacteni - debounce 150 ms je **spolecny pro vsechny
|
||||
hooky**, takze jedna udalost je jedna vlna requestu, ne pet v peti chvilich.
|
||||
|
||||
Pri vypadku se stream znovu pripojuje s exponencialne rostoucim odstupem
|
||||
az do 15 sekund, aby pri vypadku serveru neubijel provoz.
|
||||
az do 15 sekund, aby pri vypadku serveru neubijel provoz. Na 401 a 403 se
|
||||
pripojovat prestane: token vyprsel a klient vyvola `auth:expired`, po kterem
|
||||
se portal odhlasi a Login rekne, ze prihlaseni vyprselo.
|
||||
|
||||
### Obnova neodmontuje stranku
|
||||
|
||||
`useApiQuery` vraci `loading` jen **do prvnich dat**, potom uz `refreshing`.
|
||||
`DataState` pri `refreshing` nechava deti vykreslene a jen ukaze, ze bezi
|
||||
obnova. Prvni verze prepnula `loading` pri kazde udalosti ze streamu a
|
||||
`DataState` vykreslil spinner misto obsahu - pri behu automatizace se stranka
|
||||
nekolikrat za sekundu odmontovala a namontovala, vcetne ztraty kurzoru
|
||||
v rozepsanem poli.
|
||||
|
||||
```ts
|
||||
useApiQuery<T>(path, { refetchOn?, body?, enabled?, patchOn? })
|
||||
-> { data, loading, refreshing, error, total, reload }
|
||||
```
|
||||
|
||||
Dotazy sdili cache modulu a deduplikaci bezicich requestu (klic je firma,
|
||||
cesta a telo), takze dve komponenty se stejnym dotazem se ptaji jednou.
|
||||
`patchOn` opravi data v cache primo z udalosti: `lib/ticketEvents.ts` bere
|
||||
`payload.ticket` z `ticket.updated` a vymeni radek v seznamu bez dotazu.
|
||||
|
||||
### Ciselniky jsou v klientskem skladu
|
||||
|
||||
Lide, skupiny, typy ticketu, sluzby, konektory a pristup se nectou stranka po
|
||||
strance, ale ze skladu `lib/collections.tsx`: `useCollection(key)`,
|
||||
`useAccess()`, `useCollectionSelector`. Kolekce se nacte pri prvnim pouziti,
|
||||
vymaze se pri prepnuti firmy a odhlaseni a **opravuje se z udalosti entit**
|
||||
(`person.updated` a podobne): zaznam z payloadu se vlozi nebo smaze, a kdyz
|
||||
payload zaznam nenese, nacte se ta jedna kolekce znovu.
|
||||
|
||||
Rozhodnuti majitele produktu je stredni cesta: ciselniky do skladu, **tickety,
|
||||
behy a statistiky zustavaji dotazy na server** se strankovanim. Jsou velke,
|
||||
meni se porad a strop viditelnosti pocita server - klientska kopie by je
|
||||
ukazovala jinak nez prehled vytizeni.
|
||||
|
||||
Filtry seznamu ticketu jsou v URL. Nalez jde poslat kolegovi a tlacitko zpet
|
||||
vrati predchozi filtr; driv byl filtr stav stranky a po obnoveni zmizel.
|
||||
|
||||
## Simulace provozu
|
||||
|
||||
@@ -102,6 +141,15 @@ Vetve ANO a NE jsou vedle sebe jen tehdy, kdyz je na to v dane karte misto.
|
||||
Rozhoduje **sirka karty, ne sirka okna** - pouzivaji se container queries
|
||||
(`@container` a `@2xl:grid-cols-2` v `FlowCanvas.tsx`).
|
||||
|
||||
Karty jsou rozdelene po druhu kroku: `flow/ActionCard.tsx`,
|
||||
`ConditionCard.tsx`, `ForeachCard.tsx`, `StepControls.tsx`, spolecne typy
|
||||
v `canvasTypes.ts`. `FlowCanvas.tsx` uz jen sklada. Karty jsou v `memo`,
|
||||
callbacky se predavaji podle ID kroku a `collectScopes` je memoizovane -
|
||||
u stromu o padesati krocich byl driv kazdy stisk klavesy v poli prekreslenim
|
||||
vseho.
|
||||
|
||||
Odchod z rozepsaneho stromu hlida `lib/useUnsavedChanges.ts`.
|
||||
|
||||
Duvod: kazde zanoreni pulí dostupnou sirku. S beznym `lg:grid-cols-2` vypadal
|
||||
strom na sirokem monitoru dobre v prvni urovni a ve treti uz mel karty siroke
|
||||
par desitek pixelu, takze se popisy lamaly po jednom slove. Container query se
|
||||
@@ -157,8 +205,10 @@ je vypise. Rozdelana prace se nikdy nezahazuje.
|
||||
|
||||
## Pridani konektoru
|
||||
|
||||
1. Pridat zaznam do `connectors` v `src/data/connectors.ts` vcetne `triggers`
|
||||
a `actions`.
|
||||
1. Pridat zaznam do katalogu v `src/data/services.ts` vcetne `triggers`
|
||||
a `actions`. ID operace musi byt v ramci sluzby unikatni,
|
||||
`checkOperationIds()` duplicitu pri nacteni zaloguje - druha by tise
|
||||
prekryla prvni.
|
||||
2. Pokud pouziva novou ikonu, doplnit klic do `web/src/lib/connectorIcons.ts`.
|
||||
Musi existovat v `lucide-react`.
|
||||
3. Pokud patri do nove kategorie, doplnit ji do `connectorCategories` a do typu
|
||||
@@ -173,11 +223,8 @@ Builder i katalog ji vezmou automaticky.
|
||||
| Chybi | Poznamka |
|
||||
| ---------------------------- | --------------------------------------------------------- |
|
||||
| `inputs` u zbylych konektoru | zatim ticket, kanaly, CRM a AI, ostatni maji jen `fields` |
|
||||
| Vazba logu ticketu na beh | log plni simulace, ne vykonany strom |
|
||||
| Kombinovane podminky | jedna podminka je jedno porovnani, AND a OR jen vnorenim |
|
||||
| Beh automatizaci | ulozeny strom se nevykonava |
|
||||
| Historie behu a logy | prazdne, chybi runtime |
|
||||
| Drag and drop | presouvani je zatim tlacitky nahoru a dolu |
|
||||
| Strankovani v portalu | server `limit` a `offset` umi, seznam ticketu si zatim bere vse |
|
||||
|
||||
## Co je videt v kterem kroku
|
||||
|
||||
|
||||
@@ -132,10 +132,19 @@ rozlezlo po routach, driv nebo pozdeji vznikne endpoint, ktery filtr zapomene.
|
||||
"tenants": [{ "id": "tnt_automia", "name": "Automia" }],
|
||||
"defaultTenantId": "tnt_automia",
|
||||
"canAssignOthers": true,
|
||||
"personId": "ppl_uhlir"
|
||||
"personId": "ppl_uhlir",
|
||||
"roleNames": ["Správce"]
|
||||
}
|
||||
```
|
||||
|
||||
Pristup se pocita **jednou na request** (`attachAccess`
|
||||
v `src/middleware/tenant.ts`, vysledek v `req.access`) a routy si z nej berou
|
||||
firmu pres `tenantOrDeny`. Kazda route, ktera si to pocitala sama, to delala
|
||||
o neco jinak a jedna z nich spatne.
|
||||
|
||||
Stream udalosti tutez informaci pouziva k filtru: uzivatel dostane jen udalosti
|
||||
svych firem, viz [04-api.md](04-api.md).
|
||||
|
||||
Klient podle toho kresli prepinac. **Nesmi si to dovozovat sam** - jinak by se
|
||||
prava pocitala na dvou mistech a jednou se rozejdou.
|
||||
|
||||
@@ -197,15 +206,57 @@ Heslo je u vsech `demo1234`.
|
||||
Druhy ucet je ten zajimavy: ukazuje prepinac firem i to, ze prava se lisi
|
||||
podle toho, ktera firma je prave zvolena.
|
||||
|
||||
## Kdo koho zaklada
|
||||
|
||||
Rozhodnuti z revize v zari 2026: **firmy zaklada spravce platformy, lidi ve
|
||||
firme spravuje spravce firmy.** Je to hranice mezi nasim pravem a zakaznickym
|
||||
a drzi se vsude, kde se neco zaklada:
|
||||
|
||||
| Co | Kdo smi | Kde se to kontroluje |
|
||||
| -------------------------- | ------------------------------------------------------------ | ---------------------------------------- |
|
||||
| firma | jen spravce platformy (`platformOnly` u CRUD firem) | `src/routes/settings.ts` |
|
||||
| firma z registru ARES | jen spravce platformy | `src/routes/ares.ts` |
|
||||
| uzivatel | spravce platformy, nebo `user.manage` jen ve sve firme | `src/routes/settings.ts` |
|
||||
| pozvanka | `user.manage`, role jen z te firmy | `src/routes/invites.ts` |
|
||||
| konektor | `connector.manage` | `src/routes/connectors.ts`, `dashboard.ts` |
|
||||
| automatizace | `automation.edit` za firmu automatizace | `src/routes/dashboard.ts` |
|
||||
| akce nad ticketem | pravo akce za firmu ticketu a strop viditelnosti | `src/routes/ticketActions.ts` |
|
||||
|
||||
Spravce firmy s `user.manage` ma **jen svou firmu**: nenastavi `platformAdmin`,
|
||||
neprida cizi clenstvi, nesahne na spravce platformy a nesmaze cloveka, ktery
|
||||
je i v jine firme. Posledni bod neni formalita - ucet ve dvou firmach neni jen
|
||||
jeho a smazat ho znamena vzit pristup i te druhe.
|
||||
|
||||
Driv u vetsiny rout stacilo clenstvi ve firme. Clen s roli `viewer` tak mohl
|
||||
zalozit konektor nebo smazat automatizaci. Prava v katalogu byla, jen se na ne
|
||||
routy neptaly.
|
||||
|
||||
### Firma z registru ARES
|
||||
|
||||
Zalozit firmu rucne znamena opsat nazev, IC, DIC a adresu a pak zvlast
|
||||
zakladat ucty lidem, kteri ji povedou. Proto `Nastaveni`, firma z ARES:
|
||||
spravce platformy zada IC nebo nazev, vybere firmu a z verejneho rejstriku
|
||||
dostane **soucasne cleny statutarniho organu a prokuru**. Vybrani dostanou
|
||||
ucet s roli `role_admin` v nove firme a nahodnym heslem.
|
||||
|
||||
Rejstrik e-maily nezna. Kazda osoba proto dostane zastupnou adresu
|
||||
`IC-poradi@placeholder.cz`, pokud spravce nezada skutecnou. **Zastupna adresa
|
||||
se musi nahradit** - s ni se clovek neprihlasi a nedostane pozvanku. Portal ji
|
||||
pozna (`isPlaceholderEmail`), aby slo upozornit.
|
||||
|
||||
Firma nese `ico`, `dic`, `address` a `legalForm`; IC je unikatni a CRUD firem
|
||||
to hlida, takze tataz firma nevznikne dvakrat. Hledani v ARES u uz zalozene
|
||||
firmy vrati `existingTenantId` misto tlacitka Zalozit.
|
||||
|
||||
Od te chvile je to na spravci firmy: skupiny, vedouci, clenove, pozvanky.
|
||||
Spravce platformy do firmy nesaha, pokud nemusi.
|
||||
|
||||
## Co chybi
|
||||
|
||||
| Chybi | Poznamka |
|
||||
| ------------------------- | ------------------------------------------------ |
|
||||
| Sprava clenstvi z portalu | memberships jdou zmenit jen v kodu |
|
||||
| Pozvanky uzivatelu | zadny onboarding |
|
||||
| Tenant u incidentu | incidenty jsou zatim spolecne, nefiltruji se |
|
||||
| Tenant u konektoru | katalog je spolecny, napojeni se zatim neeviduje |
|
||||
| Audit pristupu | odepreni se jen loguje, nikde se neuklada |
|
||||
| Nahrada zastupnych adres | portal je pozna, ale nenuti spravce je vymenit |
|
||||
|
||||
## Strop viditelnosti
|
||||
|
||||
|
||||
@@ -19,8 +19,10 @@ Dokud se neklikne na Ulozit, nic se neuklada. Zrusit vrati puvodni rozlozeni.
|
||||
Ne za uzivatele. Clovek ve dvou firmach chce v kazde videt neco jineho
|
||||
a smichat mu to dohromady by bylo horsi nez zadne nastaveni.
|
||||
|
||||
Klic je `${userId}:${tenantId}`, uloziste je `src/data/dashboardLayouts.ts`.
|
||||
Kdo si dashboard jeste neupravil, dostane vychozi rozlozeni a `custom: false`.
|
||||
Klic je `${userId}:${tenantId}`, uloziste je `src/data/dashboardLayouts.ts`
|
||||
(pres `withMirror`, prezije restart). Kdo si dashboard jeste neupravil,
|
||||
dostane vychozi rozlozeni a `custom: false`. Ulozene rozlozeni si drzi
|
||||
`createdAt` i po uprave.
|
||||
|
||||
## Katalog widgetu
|
||||
|
||||
@@ -54,6 +56,18 @@ deset stejnych dotazu na server.
|
||||
Prehled drzi ctyri dotazy (souhrn, tickety, incidenty, vytizeni) a rozdava je
|
||||
vsem widgetum. Dlazdice, ktera zadna data nepotrebuje, o nich proste nevi.
|
||||
|
||||
Totez plati na serveru: `POST /widget-data` nacte seznam ticketu **jednou na
|
||||
request** a kazdy widget si z nej filtruje svoje. Driv sel `listTickets` za
|
||||
kazdy widget zvlast, tedy desetkrat za otevreni prehledu.
|
||||
|
||||
Pri udalosti ze streamu se dotazy obnovi na pozadi (`refreshing`) a dlazdice
|
||||
zustavaji vykreslene. Driv se cely prehled pri kazde udalosti odmontoval
|
||||
a ukazal spinner, viz [05-dashboard-a-builder.md](05-dashboard-a-builder.md).
|
||||
|
||||
Ukazkove widgety "Moje tickety" a "Fronta bez resitele" filtruji
|
||||
`closed: false`, ne podle nazvu stavu - stav je volny retezec a slovnik
|
||||
`defaultStatuses` je cesky (Novy, V reseni, Ceka na klienta, Vyreseno).
|
||||
|
||||
## Mrizka
|
||||
|
||||
Sest sloupcu, sirky mapuji na `col-span`: tretina 2, polovina 3, cela 6.
|
||||
@@ -96,4 +110,3 @@ Nabidka i rozlozeni ho vezmou automaticky.
|
||||
| Nastaveni jednotlivych widgetu | napr. kolik radku ukazat, za jake obdobi |
|
||||
| Vlastni metriky | katalog je pevny, nejde si nadefinovat vlastni |
|
||||
| Sdilene rozlozeni pro firmu | kazdy si upravuje jen to svoje |
|
||||
| Databaze | rozlozeni je v pameti, restart je vrati na vychozi |
|
||||
|
||||
@@ -132,7 +132,7 @@ export async function run(inputs, ctx) { /* ... */ }
|
||||
| `ctx.http` | `get`, `post`, `patch`, `put`, `del`, `postForm` nad adresou napojeni |
|
||||
| `ctx.util` | pomocne funkce, viz nize |
|
||||
| `ctx.log` | radek do logu behu, vzdy zredigovany a zkraceny |
|
||||
| `ctx.config` | necitliva cast nastaveni napojeni |
|
||||
| `ctx.config` | necitliva cast nastaveni napojeni, tajna pole **nikdy** (`scriptConfig`) |
|
||||
| `ctx.idempotencyKey` | stabilni pres vsechny pokusy tehoz kroku |
|
||||
| `ctx.fail` | koncova chyba, neopakuje se |
|
||||
| `ctx.retry` | docasna chyba, ma smysl zkusit znovu |
|
||||
@@ -175,6 +175,20 @@ z nich by to resil spatne.
|
||||
| `text`, `num`, `bool`, `date` | prevody s fallbackem |
|
||||
| `round(value, decimals)` | zaokrouhleni, uctuje se v halerich |
|
||||
| `need(value, label)` | vrati hodnotu, nebo skonci citelnou chybou |
|
||||
| `day(value)` | datum bez casu, `YYYY-MM-DD`, jinak prazdny retezec |
|
||||
| `list(body, ...names)` | seznam z odpovedi: pole primo, nebo pod danym klicem, `data`, `content`, `items`, `results`. Jinak `null` |
|
||||
| `addresses(value)` | adresy z pole "Prijemci" oddelene carkou nebo strednikem, bez prazdnych |
|
||||
| `quote(value)` | hodnota v jednoduchych uvozovkach pro filtr OData nebo SQL, apostrof zdvojeny |
|
||||
|
||||
Ctyri posledni pribyly v zari 2026, kdyz se ukazalo, ze osm skriptu ma kazdy
|
||||
svou verzi. Sablona `scripts/_sablona.js` je vsechny vypisuje, aby se nehledaly
|
||||
v kodu serveru.
|
||||
|
||||
`ctx.config` je slozene z `scriptConfig` v `src/scripts/connections.ts`:
|
||||
z nastaveni napojeni vynecha kazde pole, jehoz hodnota je mezi tajnymi. Skript
|
||||
tedy heslo SMTP ani tajemstvi OAuth nedostane ani omylem, i kdyz je runtime
|
||||
(SMTP, MCP) potrebuje - ty si je berou z `serviceConfig`, ke kteremu skript
|
||||
nema pristup.
|
||||
|
||||
## Chyby: opakovatelne a koncove
|
||||
|
||||
@@ -225,6 +239,20 @@ chyby, projde nahradou znamych tajnych hodnot za hvezdicky.
|
||||
|
||||
Neni to volitelne dolazeni, je to soucast zapisu.
|
||||
|
||||
Redaktor (`createRedactor`) maskuje tajemstvi ve **ctyrech tvarech**:
|
||||
`Bearer abc`, hole `abc`, URL-encoded a JSON-escaped. Cizi sluzby vraceji
|
||||
prijaty token v chybe i uvnitr adresy nebo v zaescapovanem JSONu, a tam by
|
||||
hola hodnota nesedela.
|
||||
|
||||
Zkracovani ma jednu konstantu, `DETAIL_BYTES` v `src/scripts/util.ts`
|
||||
(`SCRIPT_ERROR_DETAIL_BYTES`, vychozi 8 kB). Detail chyby MCP mel driv vlastnich
|
||||
600 znaku a prave u nej byla cela odpoved potreba nejvic.
|
||||
|
||||
Cteni tela odpovedi jde pres `readBodyLimited` v `src/net/guard.ts`: cte
|
||||
**proudem a usekne se u limitu**. Prvni verze nacetla cele telo a teprve pak
|
||||
ho zmerila, takze `SCRIPT_MAX_RESPONSE_BYTES` chranil zaznam behu, ale ne
|
||||
pamet procesu.
|
||||
|
||||
## Bezpecnostni hranice a co jeste chybi
|
||||
|
||||
Skripty ve slozce jsou **nase**, prosly gitem a code review. Bezi proto v procesu
|
||||
@@ -241,6 +269,21 @@ u zakaznickych ne - ti musi bezet v izolovanem enginu ve vlastnim vlakne.
|
||||
Podrobnosti v [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md), sekce
|
||||
o skriptech.
|
||||
|
||||
### Vlastni skripty firmy bezi jinde
|
||||
|
||||
Skript, ktery si napise spravce firmy v portalu (krok "Vlastni skript"), neni
|
||||
tenhle druh skriptu. Je to **cisty prevod hodnot** bez site a bezi
|
||||
v `node:vm` (`src/runtime/sandbox.ts`) s casovym limitem. Od zari 2026 se
|
||||
`utils` i `input` stavi **uvnitr kontextu vm**, ne v hostiteli: funkce
|
||||
hostitele predane dovnitr nesly s sebou svuj `constructor` a pres
|
||||
`constructor('return process')` se z nich dalo dostat ven. Zkompilovane
|
||||
skripty se drzi v LRU cache (100) podle otisku kodu, aby se stejny skript
|
||||
nekompiloval pri kazdem behu.
|
||||
|
||||
Porad plati, co je v komentari toho souboru: `node:vm` je izolace proti
|
||||
nehode, ne proti utocnikovi. Skript pise spravce firmy, ktery jeji data uz
|
||||
vidi.
|
||||
|
||||
## Napojeni do katalogu
|
||||
|
||||
Skript se domeri do katalogu sluzeb jako akce s `implementation: 'script'`
|
||||
@@ -330,9 +373,8 @@ Katalog, builder i stranka skriptu si ho vezmou samy. Nic se nerestartuje.
|
||||
|
||||
| Chybi | Poznamka |
|
||||
| ---------------------- | ---------------------------------------------------------- |
|
||||
| Skripty od zakazniku | potrebuji sandbox a vlastni vlakno, viz vyse |
|
||||
| Skripty od zakazniku s pristupem ven | dnes jen prevod hodnot ve `vm`, volani ven chce vlastni proces |
|
||||
| Verzovani skriptu | uprava prepise soubor, historie je jen v gitu |
|
||||
| Vykonavani ze stromu | runner je hotovy, ale runtime automatizaci neni |
|
||||
| Skripty jako spoustece | zatim jen akce, spoustec potrebuje runtime |
|
||||
| Skripty jako spoustece | zatim jen akce |
|
||||
| Metriky pro widgety | manifest to zatim nezna, viz bod 4 navrhu |
|
||||
| Ulozeni uprav mimo git | portal zapisuje do souboru v containeru, redeploy je vrati |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -15,7 +15,11 @@ Volající nikdy nezjišťuje, jestli běží Postgres, soubor, nebo pamět.
|
||||
| Co | Kde | K čemu |
|
||||
| ------------------------------------------ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `defineStore<T>(kind)` | `src/data/store/index.ts` | Založí úložiště pro nový druh záznamu. Jeden řádek na entitu. |
|
||||
| `initStores({databaseReady})` | `src/data/store/index.ts` | Vybere režim. Volá se jednou při startu, nikde jinde. |
|
||||
| `initStores({databaseReady})` | `src/data/store/index.ts` | Vybere režim pro **všechna** úložiště včetně konektorů. Volá se jednou při startu, nikde jinde. |
|
||||
| `refreshCache(kind)`, `refreshAllCaches()` | `src/data/store/cached.ts` | Registr cache. Po zápisu obnovit jen tu entitu, ne všechny. |
|
||||
| `listByTenant(tenantIds, sortBy)` | `src/data/store/cached.ts` | Filtr na firmu plus řazení nad cache. Místo sedmi kopií `filter` + `sort` v modulech entit. |
|
||||
| `nowIso`, `minutesAgo`, `highestNumber`, `writableOrWarn` | `src/data/store/types.ts` | Drobnosti pro moduly úložišť: časová značka, čas před N minutami, nejvyšší číslo ID pro čítač, varování při zápisu do úložiště jen pro čtení. |
|
||||
| `mergeValues(current, patch)` | `src/data/connectors/types.ts` | Sloučení hodnot konektoru při `PATCH`: prázdný řetězec maže, chybějící klíč nechává. Jedna implementace pro soubor i Postgres. |
|
||||
| `flushStores()` | `src/data/store/index.ts` | Dopíše rozepsané zápisy. Jen při ukončení procesu. |
|
||||
| `withCache(store)` | `src/data/store/cached.ts` | Kopie v paměti pro **konfigurační** entity, které se čtou při každém requestu (uživatelé, role, firmy). Čte se synchronně, obnovuje se po zápisu. |
|
||||
| `withMirror(store)` | `src/data/store/mirror.ts` | Opačný směr než `withCache`: data se mění v paměti a po každé změně se celý záznam zapíše. Pro **provozní** data (tickety, automatizace, incidenty, rozložení). |
|
||||
@@ -38,7 +42,17 @@ Volající nikdy nezjišťuje, jestli běží Postgres, soubor, nebo pamět.
|
||||
| `navFor(...)` | `src/data/tenantFeatures.ts` | Průnik toho, co firma má, a toho, na co má člověk právo. Navigace chodí ze serveru. |
|
||||
| `recordAudit(input)` | `src/data/audit.ts` | Zápis do auditu. Nevrací chybu a nečeká se - rozbitý audit nesmí rozbít aplikaci. |
|
||||
| `enqueue(input)` | `src/runtime/queue.ts` | Zařadí běh. Klíč proti dvojímu zařazení drží jeden běh na jednu událost. |
|
||||
| `claimBatch(limit)` | `src/runtime/queue.ts` | Vezme další práci, spravedlivě po firmách. Místo, kde nad Postgresem musí být SKIP LOCKED. |
|
||||
| `claimBatch(limit, active)` | `src/runtime/queue.ts` | Vezme další práci, spravedlivě po firmách, přeskočí to, co už běží. Místo, kde nad Postgresem musí být SKIP LOCKED. |
|
||||
| `touchClaim(item)` | `src/runtime/queue.ts` | Tlukot běžícího běhu. Bez něj se dlouhý běh po 30 minutách považuje za zaseknutý a vykoná se podruhé. |
|
||||
| `findOpenIncident(source, tenantIds)` | `src/data/incidentStore.ts` | Otevřený incident téže příčiny v téže firmě. Stejná chyba nezakládá druhý. |
|
||||
| `attachAccess`, `tenantOrDeny`, `optionalTenantOrDeny`, `scopeOrDeny` | `src/middleware/tenant.ts` | Přístup jednou na request do `req.access`, firma requestu z něj. Route si to nepočítá sama. |
|
||||
| `safeRouter()`, `wrap(handler)` | `src/middleware/asyncHandler.ts` | Router, ve kterém odmítnutá promise skončí jako 500 s logem. Každá nová route vzniká tady. |
|
||||
| `rateLimit({name, windowMs, max})` | `src/middleware/rateLimit.ts` | Limit requestů v paměti, 429 s `Retry-After`. Jen na veřejných endpointech, kde se dá hádat. |
|
||||
| `validationError(res, zodError, message?)` | `src/middleware/validation.ts` | Jeden tvar chyby validace: z chyby zodu udělá `issues` po polích. |
|
||||
| `timingSafeEqualString(a, b)` | `src/lib/secure.ts` | Porovnání tokenu v konstantním čase. Webhook, příjem, pozvánky. |
|
||||
| `assertAllowedUrl`, `readBodyLimited`, `readJsonLimited`, `describeFetchError` | `src/net/guard.ts` | Vše, co volá ven: zákaz vnitřní sítě, čtení těla proudem s limitem, čitelný popis chyby sítě. HTTP skriptů, MCP, SMTP i ARES. |
|
||||
| `publishOutputs(...)` | `src/runtime/executor.ts` | Zápis výstupů kroku do kontextu, jednou pro vestavěné kroky i skripty. Holé jméno nepřepíše, co už v kontextu je. |
|
||||
| `lookupCompany`, `searchCompanies`, `listCompanyPersons`, `placeholderEmail`, `isPlaceholderEmail` | `src/ares/client.ts` | Registr ARES: firma podle IČ nebo názvu, statutáři, zástupná adresa a její rozpoznání. |
|
||||
| `onTicketEvent(kind, ticket)` | `src/runtime/triggers.ts` | Změna ticketu zařadí navázané automatizace, včetně ochrany proti smyčce. |
|
||||
| `withRun(marker, work)` | `src/runtime/context.ts` | Označí, který běh práci způsobil. Bez toho automatizace spouští sama sebe. |
|
||||
| `findBuiltinStep(...)` | `src/runtime/builtinSteps.ts` | Kroky, které sahají do našeho úložiště, ne ven přes HTTP. |
|
||||
@@ -50,7 +64,7 @@ Volající nikdy nezjišťuje, jestli běží Postgres, soubor, nebo pamět.
|
||||
| `getAgentStats(...)` | `src/data/ticketStore.ts` | Výkon řešitelů: odbavené, mediány časů, vrácené, fronta. Používá to widget i detail osoby, aby čísla seděla. |
|
||||
| `findByExternalId(...)` | `src/data/ticketStore.ts` | Ticket firmy podle externího ID. Klíč je dvojice firma a ID. |
|
||||
| `findByIntakeToken(token)` | `src/data/tenants.ts` | Firma podle tokenu příjmu. Určuje i to, v jakém rozsahu je externí ID unikátní. |
|
||||
| `refreshCaches()` | `src/data/bootstrap.ts` | Obnoví všechny kopie v paměti. Volá se po zápisu, který je může změnit. |
|
||||
| `refreshCaches()`, `refreshEntity(kind)` | `src/data/bootstrap.ts` | Obnoví všechny kopie v paměti, nebo jen jednu entitu. Route nastavení volá `bootstrapDataRefresh(route)` v `src/data/refresh.ts`, která vybere tu jednu. |
|
||||
| `bootstrapData({databaseReady})` | `src/data/bootstrap.ts` | Seznam všech entit a provozních dat. **Nová entita se přidává tady**, ne rozesetě po modulech. |
|
||||
|
||||
## Skripty a konektory (server)
|
||||
@@ -61,8 +75,11 @@ Viz [11-skripty-konektoru.md](11-skripty-konektoru.md).
|
||||
| ------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `runScript(id, inputs, ctx)` | `src/scripts/runner.ts` | Spustí skript. **Nikdy nevyhodí výjimku**, chybu vrací jako výsledek s celým hlášením. |
|
||||
| `validateValues(...)` | `src/scripts/values.ts` | Jedna kontrola pro vstupy i výstupy skriptu podle manifestu. |
|
||||
| `scriptUtil` | `src/scripts/util.ts` | Nádobíčko pro skripty: `pick`, `first`, `num`, `date`, `need`, `get`, `applyRules`, `fillJson`. Skript nemá sahat na nic jiného. |
|
||||
| `createRedactor(...)` | `src/scripts/util.ts` | Vyškrtá tajemství z textu **před** logováním. Používá se u všeho, co jde do logu. |
|
||||
| `scriptUtil` | `src/scripts/util.ts` | Nádobíčko pro skripty: `pick`, `first`, `num`, `date`, `day`, `list`, `addresses`, `quote`, `need`, `get`, `applyRules`, `fillJson`. Skript nemá sahat na nic jiného. |
|
||||
| `pick`, `pickText`, `jwtExpiry`, `parseBool`, `parseNumber` | `src/scripts/util.ts` | Totéž pro server: pole bez ohledu na velikost písmen, `exp` z JWT, převody. Než napíšeš `Number(x)` s kontrolou `NaN`, je to tady. |
|
||||
| `DETAIL_BYTES`, `truncate(value)` | `src/scripts/util.ts` | Jeden limit na zkracování detailu chyby pro všechny vrstvy. Žádné vlastní `slice(0, 600)`. |
|
||||
| `createRedactor(...)` | `src/scripts/util.ts` | Vyškrtá tajemství z textu **před** logováním, i v URL-encoded a JSON-escaped tvaru. Používá se u všeho, co jde do logu. |
|
||||
| `scriptConfig(target)` | `src/scripts/connections.ts` | Nastavení napojení bez tajných polí. Jediné, co skript dostane jako `ctx.config`. |
|
||||
| `applyRules`, `fillJson` | `src/scripts/mapping.ts` | Transformace dat: pole na pole s převody, nebo objekt na objekt. Viz [13-transformace-dat.md](13-transformace-dat.md). |
|
||||
| `getPath(obj, path)` | `src/scripts/mapping.ts` | Čtení `zakaznik.adresa.mesto` z neznámého objektu. |
|
||||
| `resolveTarget(...)` | `src/scripts/connections.ts` | Z konektoru poskládá adresu a hlavičky. Přístupové údaje nikam jinam nevedou. |
|
||||
@@ -92,7 +109,18 @@ Viz [11-skripty-konektoru.md](11-skripty-konektoru.md).
|
||||
| `MappingEditor` | `components/dashboard/flow/MappingEditor.tsx` | Editor transformací v obou režimech (pole na pole, JSON). |
|
||||
| `DataState` | `components/dashboard/DataState.tsx` | Načítání, chyba, prázdno. Ať to každá stránka nekreslí po svém. |
|
||||
| `apiFetch<T>` | `lib/api.ts` | Jediná cesta na API: base path, token, `ApiError` s celým hlášením ze serveru. |
|
||||
| `useApiQuery<T>` | `lib/useApiQuery.ts` | Načtení dat do stránky včetně `reload`. S `body` pošle POST (dávkové načtení), s `enabled: false` se neptá vůbec. |
|
||||
| `useApiQuery<T>` | `lib/useApiQuery.ts` | Načtení dat do stránky včetně `reload`, `refreshing` a `total`. S `body` pošle POST, s `enabled: false` se neptá, `patchOn` opraví data z události bez dotazu. |
|
||||
| `useCollection(key)`, `useAccess()`, `useCollectionSelector` | `lib/collections.tsx` | Číselníky za firmu (lidé, skupiny, typy, služby, konektory, přístup) ze sdíleného skladu, opravované z událostí. Ne `apiFetch` na číselník ze stránky. |
|
||||
| `patchTicketList(...)` | `lib/ticketEvents.ts` | Oprava seznamu ticketů z `payload.ticket` v události. Použít jako `patchOn`. |
|
||||
| `apiFetchWithMeta<T>` | `lib/api.ts` | Jako `apiFetch`, ale vrací i `X-Total-Count`. Pro stránkované seznamy. |
|
||||
| `useSubmit(fn)` | `lib/useSubmit.ts` | Odeslání formuláře: `saving`, chyba, reset. Dvanáct řádků, které si dřív psal každý formulář zvlášť. |
|
||||
| `useUnsavedChanges(dirty)` | `lib/useUnsavedChanges.ts` | Varování při odchodu z rozepsaného formuláře nebo stromu. |
|
||||
| `priorities`, `priorityLabel` | `lib/options.ts` | Pevné číselníky. Stavy a kanály se berou ze serveru (`/widget-data/options`). |
|
||||
| `plural(count, forms)` | `lib/format.ts` | Skloňování počtu (1 ticket, 2 tickety, 5 ticketů). |
|
||||
| `Field`, `Input`, `Select`, `Textarea` | `components/ui/form/` | Formulářové prvky s jednou sadou tříd (`controlClass`). Vlastní `inputClass` ve stránce je chyba. |
|
||||
| `Chip` | `components/ui/Chip.tsx` | Štítek. |
|
||||
| `TicketCard` | `components/dashboard/TicketCard.tsx` | Karta ticketu pro dlaždice a mobil, varianta `compact` pro widgety. |
|
||||
| `useMediaQuery(query)` | `lib/useMediaQuery.ts` | Tabulka nebo karty podle šířky. `TicketTable` podle toho kreslí obojí, druhá komponenta není. |
|
||||
| `cn(...)` | `lib/cn.ts` | Skládání tříd. Podmíněné třídy nikdy ručně přes šablonu. |
|
||||
| `format*` | `lib/format.ts` | Čísla, procenta, datum, relativní čas, trvání. Formátování se nepíše v komponentě. |
|
||||
| `serviceIcon(key)` | `lib/serviceIcons.ts` | Klíč ikony ze serveru na komponentu. Server neposílá komponenty. |
|
||||
|
||||
@@ -24,6 +24,11 @@ Nová entita v nastavení pak znamená: `defineStore` v modulu entity, jeden ř
|
||||
v `bootstrap.ts`, jeden `crudRouter` v `settings.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**,
|
||||
@@ -37,6 +42,36 @@ 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 |
|
||||
| pozvanky | `user.manage`, role jen z te firmy |
|
||||
| konektory (zalozeni, uprava, smazani, test) | `connector.manage` |
|
||||
| automatizace (zalozeni, uprava, smazani, novy token) | `automation.edit` |
|
||||
| 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](07-firmy-a-prava.md).
|
||||
|
||||
### 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`. 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](04-api.md).
|
||||
|
||||
## Navigace chodí ze serveru
|
||||
|
||||
Co uživatel vidí za záložky, je průnik dvou věcí:
|
||||
@@ -119,9 +154,9 @@ takže ho nemůže ani omylem prodloužit.
|
||||
|
||||
| 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, obnova 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. |
|
||||
| Audit | přímo do úložiště | Jen se připisuje, nikdy nečte při každém requestu. |
|
||||
| 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](14-databaze.md). |
|
||||
|
||||
Čítače ID se při startu dopočítají z uložených záznamů, takže nový ticket
|
||||
|
||||
@@ -84,7 +84,23 @@ odmítnout ho kvůli tomu by znamenalo, že webhook nejde zapojit.
|
||||
těla. V portálu je u adresy vidět totéž včetně metody, a kopíruje se **celá
|
||||
adresa včetně domény**.
|
||||
|
||||
## Opakování a vzdání se
|
||||
## Worker je pool
|
||||
|
||||
`CONCURRENCY` (4) behu naraz, ale **nezavisle na sobe**. Worker drzi mnozinu
|
||||
`active`, kazde kolo si vezme `CONCURRENCY - active.size` behu
|
||||
(`claimBatch(limit, active)` to, co uz bezi, preskoci) a spusti je bez cekani
|
||||
na ostatni. Prvni verze brala davku a cekala, az dobehne cela: jeden pomaly
|
||||
krok MCP na deset minut blokoval tri prazdne sloty.
|
||||
|
||||
Bezici beh posila kazdou minutu **tlukot** (`touchClaim`). Za zaseknuty se
|
||||
povazuje az 30 minut od posledniho tlukotu (`STUCK_AFTER_MS` v `queue.ts`),
|
||||
ne od vzeti z fronty. Driv to bylo deset minut od vzeti, takze beh s dlouhou
|
||||
ulohou MCP se vratil do fronty a **vykonal se podruhe**, i kdyz porad bezel.
|
||||
|
||||
Planovac zarazuje behy s triggerem `poll`, ne `manual`, aby slo v seznamu
|
||||
behu poznat, co spustil clovek a co cas.
|
||||
|
||||
## Opakovani a vzdani se
|
||||
|
||||
| Pokus | Kdy |
|
||||
| ----- | --------- |
|
||||
@@ -94,12 +110,52 @@ adresa včetně domény**.
|
||||
| 4. | za 10 min |
|
||||
| 5. | za hodinu |
|
||||
|
||||
Pak běh skončí jako `failed` a zůstane k nahlédnutí. Nemaže se: bez záznamu
|
||||
by nikdo nezjistil, že se něco nestalo.
|
||||
Pak beh skonci jako `failed` a zustane k nahlednuti. Nemaze se: bez zaznamu
|
||||
by nikdo nezjistil, ze se neco nestalo.
|
||||
|
||||
**Marná chyba se neopakuje vůbec.** Chybějící skript nebo neexistující skupina
|
||||
za minutu existovat nezačne, takže se běh rovnou vzdá. Opakuje se jen to, co
|
||||
může pominout: nedostupná služba, timeout.
|
||||
**Opakuje se jen to, co samo rekne `retryable`.** Vychozi je "ne". Pravidlo
|
||||
je stejne ve vsech vrstvach a je napsane v komentari nad `StepResult`
|
||||
v `executor.ts`:
|
||||
|
||||
| Opakuje se | Konci hned a zaklada incident |
|
||||
| ------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| chyba spojeni, timeout pred odeslanim | 401, 403, 404 |
|
||||
| 5xx a 429 od cizi sluzby | spatny vstup, chybejici vystup, `ctx.fail` |
|
||||
| vypadek uloziste konektoru | chybejici nebo pozastavena automatizace, chybejici skupina |
|
||||
| MCP: chyba spojeni, 408, 425, 429, 502, 503, 504 | MCP: timeout uz odeslaneho `tools/call`, chyba v kodu |
|
||||
|
||||
Driv se opakovala **kazda** chyba skriptu, petkrat za 72 minut. Spatny vstup
|
||||
tak petkrat zopakoval tutez hlasku a u kroku, ktery neni idempotentni, mohl
|
||||
cizi sluzbu zavolat podruhe. Timeout uz odeslaneho volani MCP je proto
|
||||
neopakovatelny: MCP nema idempotencni klic a jestli druhy pokus znamena druhou
|
||||
objednavku, vi jen server, ktery neni nas.
|
||||
|
||||
## Dva stropy na velikost behu
|
||||
|
||||
| Strop | Co pocita |
|
||||
| -------------------- | --------------------------------------------------- |
|
||||
| `MAX_STEPS = 50` | kroky ve stromu, kontroluje se pri ulozeni |
|
||||
| `MAX_ACTIONS = 1000` | vykonane kroky vcetne pruchodu smyckou, za behu |
|
||||
|
||||
Jeden strop nestacil: smycka se dvema kroky nad 26 polozkami je 52 vykonanych
|
||||
kroku a beh padal na limitu 50, i kdyz strom mel kroky ctyri. Kdyz se strop
|
||||
vycerpa, beh se zastavi a rekne to; tise useknout smycku by vypadalo jako
|
||||
uspech.
|
||||
|
||||
## Podminka nad datem
|
||||
|
||||
Pole typu `date` a hodnoty, ktere vypadaji jako ISO datum, se v podmince
|
||||
porovnavaji pres `Date.parse`. Driv slo vsechno pres `Number()`, ISO retezec
|
||||
vysel jako `NaN` a `gt` i `lt` nad datem byly **vzdycky nesplnene**, bez chyby
|
||||
a bez radku v logu.
|
||||
|
||||
## Vystupy kroku
|
||||
|
||||
`publishOutputs()` v `executor.ts` je jedno misto pro vestavene kroky
|
||||
i skripty. Vystup se zapise pod jmenem kroku (`st_x.status`) vzdycky, **hole
|
||||
jmeno (`status`) jen kdyz v kontextu jeste neni**. Nastroj MCP, ktery vraci
|
||||
`status`, driv prepsal `status` spoustece a podminka za nim se ptala na
|
||||
spatnou hodnotu.
|
||||
|
||||
## Incident z chyby
|
||||
|
||||
@@ -118,6 +174,12 @@ informace pro zákazníka.
|
||||
Stejná příčina nezakládá druhý incident, dokud je první otevřený. Jinak by
|
||||
deset stejných chyb znamenalo deset incidentů a nikdo by se v tom nevyznal.
|
||||
|
||||
Incident patri **firme behu**. `findOpenIncident(source, tenantIds)` hleda jen
|
||||
v ni a krok `incident/create` predava `tenantId` - prvni verze zakladala
|
||||
globalni incident, ktery videly vsechny firmy. Neocekavana vyjimka v kroku
|
||||
(chyba v kodu) je taky koncova: neopakuje se a zaklada incident, protoze za
|
||||
minutu nezmizi.
|
||||
|
||||
## Ochrana proti smyčce
|
||||
|
||||
Automatizace navázaná na změnu ticketu ticket změní, čímž se spustí znovu.
|
||||
@@ -205,8 +267,7 @@ Ověřeno 19 kontrolami proti běžícímu serveru.
|
||||
to musí být `SELECT ... FOR UPDATE SKIP LOCKED`, jinak si dva workery
|
||||
vezmou tentýž běh. Místo je označené v `runtime/queue.ts`.
|
||||
- **Strop souběžných volání na dvojici firma a služba** a vypnutí služby po
|
||||
sérii chyb. Timeout a rozlišení "zkusit znovu / marné" už ve
|
||||
`scripts/http.ts` je.
|
||||
sérii chyb. Rozliseni "zkusit znovu / marne" uz plati ve vsech vrstvach.
|
||||
- **Dlouhé čekání** (pošli e-mail za tři dny) přes běh naplánovaný na později.
|
||||
Krok `flow/pause` umí nejvýš minutu, protože blokuje běh.
|
||||
|
||||
|
||||
+14
-14
@@ -1,6 +1,7 @@
|
||||
# 23 - Jazyky
|
||||
|
||||
Rozdelane. Mechanismus je hotovy a overeny, prevod obsahu bezi po castech.
|
||||
Mechanismus je hotovy a overeny. Verejny web je prelozeny cely, portal za
|
||||
prihlasenim jen ve spolecnych castech.
|
||||
|
||||
## Jak to funguje
|
||||
|
||||
@@ -51,19 +52,18 @@ v nadpisu.
|
||||
|
||||
## Co uz je prelozene
|
||||
|
||||
| Cast | Stav |
|
||||
| ----------------------------------- | ------------------------------------ |
|
||||
| Hlavicka a mobilni menu | hotovo |
|
||||
| Hero vcetne snimku portalu | hotovo |
|
||||
| Pas s logy klientu | hotovo |
|
||||
| Cisla | hotovo |
|
||||
| Zaverecna vyzva | hotovo |
|
||||
| Paticka | castecne, sloupce odkazu zatim cesky |
|
||||
| Sekce Produkty, Reference, Postup | ne |
|
||||
| Stranky O nas, Sluzby, Kontakt, 404 | ne |
|
||||
| Portal za prihlasenim | ne |
|
||||
| Cast | Stav |
|
||||
| ----------------------------------------- | --------------------------------------------- |
|
||||
| Hlavicka, navigace a mobilni menu | hotovo |
|
||||
| Homepage vcetne Produktu, Referenci a Postupu | hotovo |
|
||||
| Paticka | hotovo |
|
||||
| Stranky O nas, Sluzby, Kontakt, 404 | hotovo |
|
||||
| Prihlaseni | hotovo, vcetne hlasky o vyprselem prihlaseni |
|
||||
| `DataState` a `ErrorBoundary` | hotovo - stavy nacitani a chyby jsou vsude |
|
||||
| Portal za prihlasenim | ne, stranky dashboardu maji texty v kodu |
|
||||
|
||||
Prevod zbytku je mechanicky: text ven do `cs.ts`, klic do `en.ts`, v komponente
|
||||
Pro prelozene casti je `en.ts` **uplna**, nikde nespada na cestinu. Zbytek je
|
||||
mechanicky: text ven do `cs.ts`, klic do `en.ts`, v komponente
|
||||
`const t = useT()` a `{t('klic')}`. Zadna dalsi prace na vrstve uz potreba neni.
|
||||
|
||||
## Co se neprekada
|
||||
@@ -78,7 +78,7 @@ Claim se **prekada** (`brand.claim`), protoze to je text, ne udaj.
|
||||
|
||||
| Chybi | Poznamka |
|
||||
| ---------------------- | ----------------------------------------------------------------- |
|
||||
| Prevod zbytku obsahu | viz tabulka vys |
|
||||
| Stranky portalu | viz tabulka vys |
|
||||
| Jazyk v adrese | `/en/sluzby` misto volby v prohlizeci. Chce to kvuli vyhledavacum |
|
||||
| Preklad dat ze serveru | nazvy sluzeb, stavu a chybovych hlasek chodi z API cesky |
|
||||
| Format cisel a datumu | `toLocaleString` se zatim vola natvrdo s `cs-CZ` |
|
||||
|
||||
@@ -202,6 +202,41 @@ vedet, jak s nimi portal nalozil, nez zacne hledat chybu jinde.
|
||||
token**, ne pred kazdym volanim. Server drzi sezeni u tokenu, takze po jeho
|
||||
vymene se to musi zopakovat, jinak odpovi, ze relace neni inicializovana.
|
||||
|
||||
Plati to i pro server **bez prihlaseni** - i ten ma sezeni a handshake stoji
|
||||
dve volani. Prvni verze cachovala jen podle tokenu, takze server bez tokenu
|
||||
dostaval handshake pred kazdym krokem.
|
||||
|
||||
`Mcp-Session-Id`, ktery server vyda pri handshaku, se **uklada s handshakem**
|
||||
a posila v kazdem dalsim volani (u dialektu, ktere hlavicku pouzivaji).
|
||||
Sezeni bez ID by server nepoznal a kazde volani by zacinalo znovu. Kdyz
|
||||
server na preskoceny handshake odpovi 400 nebo 404, znamena to podle
|
||||
specifikace "sezeni neznam": handshake se zopakuje **jednou** a volani se
|
||||
posle znovu. Vic nez jednou ne - kdyby to nepomohlo, je chyba jinde a smycka
|
||||
by ji jen schovala.
|
||||
|
||||
Prihlaseni OAuth u obecne sluzby sdili **jednu rozdelanou operaci na
|
||||
konektor**, stejne jako EasyWeb: dva behy nad tymz napojenim udelaji jedno
|
||||
prihlaseni, ne dve.
|
||||
|
||||
## Co se opakuje a co ne
|
||||
|
||||
| Situace | Opakuje se |
|
||||
| ----------------------------------------------- | ---------- |
|
||||
| spojeni se nenavazalo, DNS, TLS | ano |
|
||||
| 408, 425, 429, 502, 503, 504 | ano |
|
||||
| timeout **uz odeslaneho** `tools/call` | **ne** |
|
||||
| 401, 403, 404, chyba prihlaseni | ne |
|
||||
| `isError` v odpovedi nastroje | ne |
|
||||
|
||||
Timeout po odeslani je ta zvlastni radka. MCP nema idempotencni klic, takze
|
||||
druhy pokus by nastroj provedl podruhe - a jestli to znamena druhou
|
||||
objednavku, vi jen server, ktery neni nas. Rozhoduje `McpFailure.retryable`,
|
||||
stejne pravidlo jako v [20-fronta-a-runtime.md](20-fronta-a-runtime.md).
|
||||
|
||||
Chyby prihlaseni maji spolecneho predka `AuthFailure` v `src/mcp/errors.ts`,
|
||||
`EasyWebAuthError` z nej dedi. Krok tak pozna chybu prihlaseni jednou
|
||||
kontrolou a detail je vzdy zredigovany, at prisel odkudkoliv.
|
||||
|
||||
## Odpoved muze byt stream
|
||||
|
||||
Server si sam vybira, jestli odpovi JSON telem, nebo SSE streamem, a **streamem
|
||||
@@ -348,11 +383,14 @@ a krok to rekne misto toho, aby predstiral selhani.
|
||||
ostatni tajne hodnoty.
|
||||
- **Cizi napojeni se chova jako neexistujici.** Krok si konektor nacita pres
|
||||
filtr na firmu, takze strom s cizim ID konektoru selze.
|
||||
- **Krok se neopakuje.** MCP nema idempotencni klic, takze druhy pokus po
|
||||
timeoutu by nastroj provedl podruhe - a jestli to znamena druhou objednavku,
|
||||
vi jen server, ktery neni nas.
|
||||
- **Odeslane volani se neopakuje.** MCP nema idempotencni klic, takze druhy
|
||||
pokus po timeoutu by nastroj provedl podruhe. Opakuje se jen to, co selhalo
|
||||
pred odeslanim nebo co server odmitl docasne, viz vyse.
|
||||
- Plati stejny strop na velikost odpovedi jako u skriptu
|
||||
(`SCRIPT_MAX_RESPONSE_BYTES`), a to i u streamu, kde se pocita prubezne.
|
||||
Cte se pres `readBodyLimited` z `src/net/guard.ts`, stejne jako u HTTP
|
||||
skriptu a SMTP. Detail chyby ma stejny limit jako vsechno ostatni
|
||||
(`DETAIL_BYTES`), driv mel vlastnich 600 znaku.
|
||||
- Seznamy se strankuji nejvys stokrat, volani nastroje dvacetkrat.
|
||||
|
||||
## Co se **nedela**
|
||||
@@ -373,6 +411,8 @@ a krok to rekne misto toho, aby predstiral selhani.
|
||||
| Rozdily serveru | `src/mcp/dialect.ts` |
|
||||
| Protokol | `src/mcp/client.ts` |
|
||||
| Prihlaseni obecne | `src/mcp/auth.ts` |
|
||||
| Chyby prihlaseni | `src/mcp/errors.ts` |
|
||||
| Sit a limity tela | `src/net/guard.ts` |
|
||||
| Klice EasyWebu | `src/mcp/easyweb/crypto.ts` |
|
||||
| Zarizeni u konektoru | `src/mcp/easyweb/device.ts` |
|
||||
| Tokeny EasyWebu | `src/mcp/easyweb/session.ts` |
|
||||
@@ -382,7 +422,7 @@ a krok to rekne misto toho, aby predstiral selhani.
|
||||
| Nacteni nastroju | `src/routes/connectors.ts` |
|
||||
| Vykonna cast kroku | `src/runtime/builtinSteps.ts`, `runMcpTool` |
|
||||
| Ulozeni u konektoru | `src/data/connectors/*`, migrace `004` |
|
||||
| Portal | `web/src/pages/dashboard/Connectors.tsx` |
|
||||
| Portal | `web/src/pages/dashboard/Connectors.tsx` a `connectors/{ConnectorCard,ConnectorEditor,ConnectorLogs,ConnectorTools}.tsx` |
|
||||
|
||||
## Co jeste chybi
|
||||
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
[07-firmy-a-prava.md](07-firmy-a-prava.md),
|
||||
- sekce 6, kontrakt webhooku,
|
||||
- z prvni sekce dlazdice "Moje tickety" a "Fronta bez resitele" vcetne noveho
|
||||
vychoziho rozlozeni.
|
||||
vychoziho rozlozeni,
|
||||
- sekce 2, formularova vrstva (zari 2026, viz zacatek te sekce),
|
||||
- ze sekce 3 strankovani na serveru: `/tickets` bere `limit` a `offset`
|
||||
a vraci `X-Total-Count`, viz [04-api.md](04-api.md).
|
||||
|
||||
Zbyva widget akci, "Zaciname", formularova vrstva a hledani. Az se cast udela,
|
||||
Zbyva widget akci, "Zaciname" a hledani. Az se cast udela,
|
||||
prepise se do prislusneho souboru dokumentace a odsud zmizi - stejne pravidlo
|
||||
jako u [09-navrh-rozsireni.md](09-navrh-rozsireni.md).
|
||||
|
||||
@@ -170,9 +173,23 @@ coz je cil, to sedi.
|
||||
|
||||
---
|
||||
|
||||
## 2 - Formularova vrstva
|
||||
## 2 - Formularova vrstva (hotovo)
|
||||
|
||||
### Cim to je
|
||||
**Naprogramovano v zari 2026**, presne podle navrhu nize:
|
||||
|
||||
| Navrh | Kde to je |
|
||||
| ------------------------------- | ---------------------------------------------------------------- |
|
||||
| `Field`, `Input`, `Select`, `Textarea` | `components/ui/form/`, tridy v `controlClass.ts` |
|
||||
| `useSubmit` | `lib/useSubmit.ts` |
|
||||
| ciselniky ven | `lib/options.ts` (priority), stavy a kanaly z `/widget-data/options` |
|
||||
| kompaktni karta ticketu | `components/dashboard/TicketCard.tsx`, varianta `compact` |
|
||||
| stitek | `components/ui/Chip.tsx` |
|
||||
| sklonovani poctu | `plural()` v `lib/format.ts` |
|
||||
|
||||
Zmizelo 15 kopii trid vstupniho pole. Navrh zustava nize jako zduvodneni,
|
||||
proc to vypada tak, jak vypada.
|
||||
|
||||
### Cim to bylo
|
||||
|
||||
V `components/ui/` je Badge, Button, Card, Container, Modal, PageHeader, Section
|
||||
a Spinner. **Zadny formularovy prvek.** Takze si ho kazda stranka pise znovu.
|
||||
@@ -278,12 +295,14 @@ Klientsky filtr pres ctyri veci: `id`, `subject`, `customer.company`,
|
||||
polich**, ve **stitcich** ani v **externim ID**. To posledni je zrovna to, cim se
|
||||
dohledava hovor: clovek ma `CAbc75a8...` a chce ten ticket. Dnes ho nenajde.
|
||||
|
||||
A funguje to jen proto, ze `GET /api/dashboard/tickets` vraci **vsechny tickety
|
||||
firmy najednou**, bez limitu a bez strankovani. Pri par desitkach to nevadi, pri
|
||||
deseti tisicich jsou to megabajty do prohlizece pri kazdem otevreni seznamu.
|
||||
A funguje to jen proto, ze seznam v portalu si bere **vsechny tickety firmy
|
||||
najednou**. Server uz strankovani umi (`limit`, `offset`, `X-Total-Count`),
|
||||
klient ho zatim nepouziva. Pri par desitkach to nevadi, pri deseti tisicich
|
||||
jsou to megabajty do prohlizece pri kazdem otevreni seznamu.
|
||||
|
||||
Hledani pres modal proto znamena **serverovy endpoint**, a je to zaroven
|
||||
prilezitost prestat posilat vsechno.
|
||||
chvile, kdy seznam prejde na strankovani - jinak by klientske hledani
|
||||
a serverove ukazovaly jina cisla.
|
||||
|
||||
### Tvar
|
||||
|
||||
@@ -576,14 +595,10 @@ prestane stacit.
|
||||
|
||||
## Poradi praci
|
||||
|
||||
1. **Formularove primitivy** a rozdeleni dvou formularu (ticket, helpdesk). Nic
|
||||
dalsiho na nich nestoji, ale stoji na nich vsechno ostatni v rozhrani.
|
||||
2. **"Moje tickety" a "Fronta bez resitele".** Formulare nepotrebuji, jsou skoro
|
||||
zadarmo a jsou hned videt v provozu.
|
||||
3. **Viditelnost.** Model, strop v `resolveScope`, vynuceni v jedne ceste ke
|
||||
ticketum, panel skupiny. Delat to pred hledanim, ne po nem, jinak se hledani
|
||||
pise dvakrat.
|
||||
4. **Modal hledani** a serverovy endpoint se strankovanim.
|
||||
1. **Formularove primitivy** - hotovo.
|
||||
2. **"Moje tickety" a "Fronta bez resitele"** - hotovo.
|
||||
3. **Viditelnost** - hotovo, viz [07-firmy-a-prava.md](07-firmy-a-prava.md).
|
||||
4. **Modal hledani** a prechod seznamu na strankovani, ktere server uz umi.
|
||||
5. **Widget akci** a "Zaciname". Sahne uz jen na hotove formulare.
|
||||
|
||||
Postgres kdykoliv mezi tim, nezavisle na ostatnim.
|
||||
|
||||
@@ -2,6 +2,252 @@
|
||||
|
||||
Nejnovejsi nahore.
|
||||
|
||||
## 2026-09-09 - Revize projektu: prava, vykon, runtime, portal a ARES
|
||||
|
||||
Velka sada oprav napric celym projektem. Zadna nova obrazovka, ale skoro kazda
|
||||
vrstva se zmenila v tom, **co dela pri zatezi a pri chybe**. K tomu jedna nova
|
||||
funkce: zalozeni firmy podle registru ARES. Zaznam je dlouhy schvalne - tohle
|
||||
je misto, kde se za pul roku hleda, proc se neco chova tak, jak se chova.
|
||||
|
||||
### Uloziste: jeden rezim pro vsechno
|
||||
|
||||
Rozhodnuti o rezimu (`postgres`, `file`, `memory`) delal `connectorStore.ts`
|
||||
pro konektory a `initStores` pro zbytek, kazdy podle svych podminek. Mohlo se
|
||||
stat, ze konektory jely z databaze a tickety ze souboru. Ted rozhoduje
|
||||
**jen `initStores` v `src/data/store/index.ts`**: Postgres jen kdyz je
|
||||
`DATABASE_URL`, migrace prosly a je cim sifrovat (`SECRETS_KEY`), jinak soubor
|
||||
nebo pamet pro vsechna uloziste vcetne konektoru. `connectorStore` uz jen vola
|
||||
`initStores`.
|
||||
|
||||
Dalsi opravy v ulozisti, kazda ma za sebou konkretni problem:
|
||||
|
||||
| Co | Proc |
|
||||
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| audit se maze davkou (`removeMany`) | orezavani mazalo jen radky platformy, audit firem rostl donekonecna. Bezi po 50 zapisech nebo nejvys jednou za minutu |
|
||||
| `markRead` pres `updateMany` | notifikace delaly jeden zapis a jednu obnovu cache na kazdy zaznam |
|
||||
| `persist` / `flushPersist` u ticketu | vic zmen tehoz ticketu v jednom tiku je jeden zapis, ne pet |
|
||||
| `withMirror` radi zapisy za sebou | dva `put` tehoz zaznamu mohl Postgres potvrdit v opacnem poradi a v tabulce zustala starsi verze. Ted je na kazde ID retez promise |
|
||||
| `issues` a `stepCount` ulozene | `listAutomations` validoval vsechny stromy pri kazdem cteni. Ted se spocitaji pri ulozeni (`withDerived`) a jednou pri startu |
|
||||
| registr cache (`refreshCache(kind)`) | route nastaveni obnovovala vsechny cache, ted jen tu entitu, do ktere psala (`bootstrapDataRefresh(route)`) |
|
||||
| `listByTenant(tenantIds, sortBy)` | sedm kopii filtr + razeni v modulech entit |
|
||||
| mapy misto `find` | `withCache.byId` je `Map`, ticketStore ma `ticketsById` a `ticketsByExternal`, vytizeni a statistiky se seskupi jednim pruchodem |
|
||||
| `create` v lokalnim ulozisti hazi na duplicitu | Postgres to delal, soubor tise prepsal |
|
||||
| `snapshot.ts` prepise soubor jen pri ENOENT | jina chyba cteni (prava, plny disk) driv znamenala start s prazdnymi daty a **prepsani souboru prazdnym obsahem**. Ted se zapisy zamknou a zaloguje se to |
|
||||
| `listIncidents(tenantIds)` povinne | stejne pravidlo jako u ticketu, incident byl posledni seznam bez filtru |
|
||||
|
||||
Slovnik stavu ticketu je sjednoceny na cesky `defaultStatuses` (Novy, V reseni,
|
||||
Ceka na klienta, Vyreseno) a ukazkove widgety filtruji `closed: false`, ne
|
||||
podle nazvu stavu. Ukazkove tickety TK-4817 a TK-4812 vznikaji jen se
|
||||
`SEED_DEMO=1`. V `services.ts` byla dvakrat operace `set-status`, druha tise
|
||||
prekryvala prvni; `checkOperationIds()` to ted pri nacteni zaloguje.
|
||||
|
||||
Spolecne pomocne funkce, aby se nepsaly po modulech: `nowIso`, `minutesAgo`,
|
||||
`highestNumber`, `writableOrWarn` v `store/types.ts`, `mergeValues`
|
||||
v `connectors/types.ts`.
|
||||
|
||||
### Runtime: worker je pool a opakuje se jen to, co muze pominout
|
||||
|
||||
Worker bral davku ctyr behu a cekal, az dobehnou vsechny. Jeden pomaly beh
|
||||
tak blokoval tri volne sloty. A beh delsi nez deset minut se povazoval za
|
||||
zaseknuty, vratil se do fronty a **vykonal se podruhe**. Ted:
|
||||
|
||||
- `active` je mnozina bezicich ID, kazde kolo si vezme `CONCURRENCY - active.size`
|
||||
behu a spusti je bez cekani na ostatni,
|
||||
- `claimBatch(limit, active)` preskakuje to, co uz bezi,
|
||||
- beh kazdou minutu posle tlukot (`touchClaim`) a za zaseknuty se povazuje az
|
||||
30 minut od posledniho tlukotu (`STUCK_AFTER_MS`), ne od vzeti z fronty.
|
||||
|
||||
**Opakovani.** Kazda chyba skriptu se opakovala petkrat za 72 minut, i 403
|
||||
a spatny vstup. Ted se krok opakuje jen kdyz sam rekne `retryable`: chyba
|
||||
spojeni, timeout, 5xx a 429, vypadek uloziste konektoru. 401, 403, 404,
|
||||
validace a `ctx.fail` konci hned a zakladaji incident. U MCP jsou opakovatelne
|
||||
chyby spojeni a 408, 425, 429, 502, 503, 504; **timeout uz odeslaneho
|
||||
`tools/call` opakovatelny neni**, protoze MCP nema idempotencni klic a nastroj
|
||||
by se provedl podruhe. Pravidlo je v komentari nad `StepResult`
|
||||
v `executor.ts`, aby ho nasel kazdy, kdo pise novy druh kroku.
|
||||
|
||||
Dalsi zmeny v behu:
|
||||
|
||||
| Co | Proc |
|
||||
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `MAX_ACTIONS = 1000` | `MAX_STEPS = 50` pocita staticky strom. Smycka se dvema kroky nad 26 polozkami narazila na 50 a beh spadl. Vykonane kroky maji vlastni strop |
|
||||
| datum v podmince pres `Date.parse` | `gt` a `lt` nad datem prevadely ISO retezec cislem, vyslo NaN a podminka byla vzdycky nesplnena |
|
||||
| `publishOutputs()` spolecne | vestavene kroky a skripty publikovaly vystupy kazdy jinak. Hole jmeno se zapise jen kdyz v kontextu jeste neni - nastroj MCP vracejici `status` prepisoval `status` spoustece |
|
||||
| sandbox stavi `utils` i `input` uvnitr vm | funkce hostitele prosakovaly do skriptu firmy a `constructor('return process')` z nich utekl ven. Zkompilovane skripty se cachuji (LRU 100) |
|
||||
| chybejici nebo pozastavena automatizace | neopakovatelna chyba plus incident, driv se to zkouselo dokola |
|
||||
| `incident/create` nese `tenantId` | krok zakladal globalni incident, ktery videly vsechny firmy |
|
||||
| planovac zarazuje s triggerem `poll` | bylo `manual`, takze se v behu nedalo poznat, ze to spustil planovac |
|
||||
|
||||
### Sit a tajemstvi
|
||||
|
||||
Novy `src/net/guard.ts` sdruzuje to, co melo kazde volani ven zvlast:
|
||||
`assertAllowedUrl` (zakaz vnitrni site), `describeFetchError`,
|
||||
`readBodyLimited` a `readJsonLimited`. Telo se **cte proudem a usekne se
|
||||
u limitu** - driv se nacetlo cele a teprve pak zmerilo, takze limit nechranil
|
||||
pamet. Pouziva to HTTP skriptu, klient MCP, prihlaseni MCP i SMTP.
|
||||
|
||||
Redaktor masky navic maskuje tajemstvi v **URL-encoded a JSON-escaped** tvaru,
|
||||
protoze cizi sluzby je v chybach vraceji i tak. `ctx.config` skriptu uz nikdy
|
||||
neobsahuje tajna pole (`scriptConfig`). `EasyWebAuthError` dedi z `AuthFailure`
|
||||
(novy `src/mcp/errors.ts`), takze se chyby prihlaseni poznaji jednou
|
||||
kontrolou a detail je vzdy zredigovany.
|
||||
|
||||
Klient MCP: handshake se cachuje i pro server bez prihlaseni, `Mcp-Session-Id`
|
||||
se uklada s handshakem a posila znovu, 400 nebo 404 po preskocenem handshaku
|
||||
vyvola jeden novy handshake. OAuth prihlaseni sdili rozdelanou operaci na
|
||||
konektor. V `delay()` unikal posluchac abortu.
|
||||
|
||||
`src/scripts/util.ts` dostal `pick`, `pickText`, `jwtExpiry`, `parseBool`,
|
||||
`parseNumber` a jednu konstantu `DETAIL_BYTES` na zkracovani (MCP mel 600
|
||||
znaku, zbytek 8 kB). `ctx.util` skriptu ma navic `day`, `list`, `addresses`,
|
||||
`quote`; sablona `scripts/_sablona.js` je vypisuje a osm skriptu je pouziva.
|
||||
|
||||
### API: prava se kontroluji za firmu a u kazde route
|
||||
|
||||
Prava byla ve vetsine rout jen "je prihlaseny" nebo "je clen firmy". Ted:
|
||||
|
||||
| Route | Kdo smi |
|
||||
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| firmy CRUD | jen spravce platformy |
|
||||
| uzivatele CRUD | spravce platformy vse. Spravce firmy (`user.manage`) jen lidi sve firmy, nenastavi `platformAdmin`, neprida clenstvi jinde, nesahne na spravce platformy a nesmaze cloveka, ktery je i v jine firme |
|
||||
| pozvanky | `user.manage` a jen role te firmy |
|
||||
| konektory create, update, delete, test | `connector.manage` |
|
||||
| automatizace create, update, delete, regenerate | `automation.edit` za firmu automatizace |
|
||||
| `/services`, `/connectors/services` | clenstvi ve firme, cizi firma je 404 |
|
||||
| assign, status, comment, claim | `builtinAction` v `ticketActions.ts`, pravo za firmu ticketu a strop viditelnosti (`visibleTicketOrDeny`) |
|
||||
| `/api/admin/*` | `impersonate` a `audit.view` se ted opravdu kontroluji |
|
||||
|
||||
Nove middleware, kazde s jednim ukolem:
|
||||
|
||||
| Soubor | Co |
|
||||
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `middleware/asyncHandler.ts` | `wrap`, `safeRouter`: odmitnuta promise v handleru driv zabila proces. `unhandledRejection` se loguje, `uncaughtException` loguje a ukonci |
|
||||
| `middleware/rateLimit.ts` | klouzave okno v pameti: login 20 za 15 min, kontakt 5 za hodinu, prijeti pozvanky 5 za 15 min. 429 s `Retry-After` |
|
||||
| `middleware/tenant.ts` | `attachAccess` spocita pristup jednou na request do `req.access`; `tenantOrDeny`, `optionalTenantOrDeny`, `scopeOrDeny` misto kopii v routach |
|
||||
| `middleware/validation.ts` | `validationError`, jeden tvar `{ error, message, issues: [{ field, message }] }` |
|
||||
| `lib/secure.ts` | `timingSafeEqualString` pro tokeny webhooku, prijmu a pozvanek |
|
||||
|
||||
V `index.ts`: log requestu maskuje tokeny za `/webhook/`, `/webhook/ticket/`
|
||||
a `/invites/`; bezpecnostni hlavicky (nosniff, `X-Frame-Options SAMEORIGIN`,
|
||||
`Referrer-Policy`, `Permissions-Policy`); `trust proxy` = 1, aby limit
|
||||
pocital s adresou klienta a ne proxy; rozpoznani API 404 bere
|
||||
`config.rootPath` misto natvrdo `/apps/`. Dockerfile instaluje `npm ci`.
|
||||
|
||||
Vykon: data widgetu nactou seznam ticketu jednou na request, ne za kazdy
|
||||
widget; `/tickets` a `/runs` berou `limit` a `offset` (nejvys 500) a vraceji
|
||||
`X-Total-Count`, `/tickets` i `total` v tele; `hashPassword` je asynchronni,
|
||||
aby bcrypt neblokoval smycku; `/people/:id` prochazi tickety jednou.
|
||||
|
||||
`GET /api/dashboard/access` vraci `roleNames`, aby klient nehadal popisek role.
|
||||
`/storage` a `/scripts` vraceji cesty na serveru jen spravci platformy.
|
||||
V `openapi.ts` pribylo 22 chybejicich cest, `/whoami` je opraveny a `features`
|
||||
uz nejsou popsane jako CRUD.
|
||||
|
||||
### Udalosti nesou firmu
|
||||
|
||||
`DashboardEvent.tenantId` (null = cela platforma). Stream SSE filtruje zive
|
||||
udalosti i historii podle firem uzivatele, udalost s `payload.userId` jde jen
|
||||
tomu cloveku. Driv videl kazdy prihlaseny udalosti vsech firem.
|
||||
|
||||
Nove udalosti entit `tenant|user|role|person|group|ticketType|action|widget|connector|feature`
|
||||
s `.created|.updated|.deleted`, publikuje je `crudRouter` (volba `event`),
|
||||
routy konektoru a PUT features. Payload je `{ id, <druh>: zaznam }`, u smazani
|
||||
`{ id }`. Udalosti ticketu `ticket.updated`, `ticket.assigned`,
|
||||
`ticket.resolved` nesou v `payload.ticket` cely ticket, takze klient opravi
|
||||
seznam na miste a nemusi se ptat znovu.
|
||||
|
||||
### Portal: obnova bez odmontovani a klientsky sklad ciselniku
|
||||
|
||||
Kazda udalost ze streamu odmontovala stranku: `useApiQuery` prepnul `loading`
|
||||
a `DataState` vykreslil spinner misto deti. Ted je `loading` jen do prvnich
|
||||
dat, potom `refreshing`, a deti zustavaji. Hooky sdili jeden debounce 150 ms,
|
||||
cache modulu a deduplikaci bezicich dotazu (klic firma + cesta + telo).
|
||||
`patchOn` opravi data v cache z udalosti (`lib/ticketEvents.ts` bere
|
||||
`payload.ticket`). Rozhrani:
|
||||
|
||||
```ts
|
||||
useApiQuery<T>(path, { refetchOn?, body?, enabled?, patchOn? })
|
||||
-> { data, loading, refreshing, error, total, reload }
|
||||
```
|
||||
|
||||
**Rozhodnuti majitele produktu: stredni cesta.** Ciselniky (lide, skupiny,
|
||||
typy ticketu, sluzby, konektory, pristup) jsou v klientskem skladu
|
||||
`lib/collections.tsx`: nacitaji se line pri prvnim pouziti, mazou se pri
|
||||
prepnuti firmy a odhlaseni, opravuji se z udalosti entit (upsert nebo smazani
|
||||
ze zaznamu v payloadu, jinak jedno nacteni te kolekce). Tickety, behy
|
||||
a statistiky **zustavaji dotazy na server** se strankovanim - jsou velke
|
||||
a meni se porad. Hooky: `useCollection(key)`, `useAccess()`,
|
||||
`useCollectionSelector`.
|
||||
|
||||
Dalsi opravy klienta:
|
||||
|
||||
| Co | Proc |
|
||||
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
|
||||
| 401 maze token a vyvola `auth:expired` | po vyprseni tokenu portal ukazoval prazdne stranky. Login rekne "Prihlaseni vyprselo", stream se prestane pripojovat na 401 a 403 |
|
||||
| `restore()` maze token jen na 401 | vypadek site pri startu odhlasoval |
|
||||
| `onClose` modalu v ref | fokus se pri kazdem prekresleni vracel na zacatek |
|
||||
| toast ma jeden casovac | dva toasty za sebou si rusily odpocet |
|
||||
| zrusene asynchronni efekty | odpoved pro uz odmontovanou stranku prepisovala stav te nove |
|
||||
| tiche `catch` nahrazene chybou | pravidlo "zadna ticha selhani" platilo na serveru, na klientovi ne vsude |
|
||||
| filtry ticketu v URL | nalez slo poslat kolegovi a vratit se pres zpet |
|
||||
| detail ticketu neblokuje chyba `/people` | jeden padly dotaz na ciselnik schoval cely ticket |
|
||||
| `MappingEditor` stabilni klice radku | smazani radku prekreslilo vsechny nasledujici a ztratil se kurzor |
|
||||
| `lib/useUnsavedChanges.ts` | odchod z rozepsaneho builderu bez varovani |
|
||||
| `DashboardLayout` lazy, sourcemapy vypnute | verejny web nenacital kod portalu, produkce neposila zdrojaky |
|
||||
| `TicketTable` tabulka nebo karty | `useMediaQuery` misto duplicitni komponenty |
|
||||
|
||||
**Builder.** `collectScopes` memoizovane, karty v `memo`, callbacky podle ID
|
||||
kroku; `FlowCanvas` je rozdeleny do `flow/{ActionCard,ConditionCard,ForeachCard,StepControls}.tsx`
|
||||
a `canvasTypes.ts`. Stranka Konektory je rozdelena do
|
||||
`pages/dashboard/connectors/{ConnectorCard,ConnectorEditor,ConnectorLogs,ConnectorTools}.tsx`.
|
||||
Pred tim byl kazdy stisk klavesy ve strome o padesati krocich prekresleni
|
||||
vseho.
|
||||
|
||||
**Formularova vrstva** podle navrhu v dokumentu 25, sekce 2, je hotova:
|
||||
`components/ui/form/{controlClass,Field,Input,Select,Textarea}.tsx`,
|
||||
`lib/useSubmit.ts`, `lib/options.ts`, `components/ui/Chip.tsx`,
|
||||
`components/dashboard/TicketCard.tsx` (kompaktni varianta), `plural()`
|
||||
v `lib/format.ts`. Zmizelo 15 kopii trid vstupniho pole.
|
||||
|
||||
**Jazyky.** Verejne stranky (Postup, Produkty, Reference, O nas, Kontakt, 404,
|
||||
Prihlaseni, Sluzby, paticka, navigace) plus `DataState` a `ErrorBoundary` jdou
|
||||
pres i18n a `en.ts` je pro ne uplna.
|
||||
|
||||
**Sdilene typy**: ciste typove moduly v `src/shared/*.ts` (16 souboru,
|
||||
vcetne `users.ts` pro ucet a clenstvi) jsou jediny zdroj typu API.
|
||||
`web/src/types/dashboard.ts`, `events.ts` i `AuthContext` je re-exportuji
|
||||
pres alias `@shared/*` (`web/tsconfig.json` paths a `vite.config.ts` alias).
|
||||
Pri prevodu se nasly rozjete tvary, vsechny vyresene ve prospech serveru:
|
||||
webovy `Ticket` nemel `createdById`, `Access.roleNames` bylo nepovinne,
|
||||
`Person` nemel `enabled`, `Incident` nemel `tenantId` ani `source`, `Service`
|
||||
neznal kategorii `transformace`, seznam operatoru podminky u typu `list`
|
||||
na webu nemel `contains`, takze builder nenabizel podminku nad stitky, ktera
|
||||
na serveru funguje. Serverovy ulozeny tvar (`StoredTicket`, `Connector`
|
||||
s `values`) zustava na serveru; web dostava `PublicConnector` jako `Connector`.
|
||||
Pravidlo od ted: novy typ odpovedi patri do `src/shared`, web ho nekopiruje.
|
||||
|
||||
### Nova funkce: firma z registru ARES
|
||||
|
||||
Zalozit firmu znamenalo opsat nazev, IC, DIC a adresu rucne a pak zvlast
|
||||
zakladat ucty. Ted je to na `/api/dashboard/settings/ares`, jen pro spravce
|
||||
platformy:
|
||||
|
||||
| Endpoint | Co |
|
||||
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `GET ares/companies?query=` | podle IC presne, jinak podle nazvu. U firmy, ktera uz v portalu je, vraci `existingTenantId` |
|
||||
| `GET ares/companies/{ico}/persons` | soucasni clenove statutarniho organu a prokura z verejneho rejstriku, kazdy s navrzenym e-mailem `IC-poradi@placeholder.cz` |
|
||||
| `POST ares/tenants` | zalozi firmu s `ico`, `dic`, `address`, `legalForm` z ARES a ucty vybranych osob, vsechny s roli `role_admin` v nove firme, nahodne heslo, audit `tenant.create.ares` |
|
||||
|
||||
Zaznam firmy ma nove nepovinne `ico`, `dic`, `address`, `legalForm`; CRUD
|
||||
firem hlida unikatni IC. Adresa registru je `ARES_BASE_URL`, vychozi
|
||||
`https://ares.gov.cz/ekonomicke-subjekty-v-be/rest`. Klient ARES pouziva
|
||||
tentyz `net/guard.ts` jako vsechno ostatni, co vola ven.
|
||||
|
||||
**Rozhodnuti o rolich:** spravce platformy zaklada firmy, spravce firmy pak
|
||||
spravuje skupiny, vedouci a cleny uvnitr firmy. Rejstrik nezna e-maily, proto
|
||||
zastupne adresy - **spravce je musi nahradit skutecnymi**, jinak se ti lide
|
||||
neprihlasi a nedostanou pozvanku.
|
||||
|
||||
## 2026-09-08 - Otevrena stranka sekala prehravani videa
|
||||
|
||||
Pri otevrenem portalu zacalo vedle nej sekat prehravani videa, po zavreni
|
||||
|
||||
Reference in New Issue
Block a user