diff --git a/README.md b/README.md index 9b89372..8ae8a25 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ npm run dev ## Povinne endpointy -| Cesta | Ucel | -| --------------- | --------------------------------------- | -| `/health` | Health check pro AppFactory, vraci 200 | -| `/docs` | Swagger UI | -| `/openapi.json` | OpenAPI definice | +| Cesta | Ucel | +| --------------- | -------------------------------------- | +| `/health` | Health check pro AppFactory, vraci 200 | +| `/docs` | Swagger UI | +| `/openapi.json` | OpenAPI definice | Verejne pres proxy jako `/apps//health` a `/apps//docs`. diff --git a/documentation/01-prehled-a-stav.md b/documentation/01-prehled-a-stav.md index a3b8bce..ffe43ed 100644 --- a/documentation/01-prehled-a-stav.md +++ b/documentation/01-prehled-a-stav.md @@ -10,56 +10,61 @@ React aplikaci ze slozky `dist/public`. ## Stav -| Oblast | Stav | Poznamka | -| --------------------------------- | ------ | ----------------------------------------------------- | -| Verejny web | hotovo | homepage, sluzby, o nas, kontakt, 404 | -| Prihlaseni | hotovo | JWT, demo ucty | -| Dashboard | hotovo | prehled, tickety, incidenty, automatizace, nastaveni | -| Zivy dashboard pres SSE | hotovo | zmeny se projevi bez obnoveni stranky | -| Katalog sluzeb | hotovo | 29 sluzeb, 7 kategorii vcetne Obecne | -| Builder automatizaci | hotovo | strom akci, vetveni podminkou | -| Webhook s registrovanou adresou | hotovo | token generuje server, verejny endpoint validuje data | -| Tickety na konkretni lidi | hotovo | resitel, filtr moje, prehled vytizeni tymu | -| Prijem udalosti do ticketu | hotovo | webhook na firmu, externi ID unikatni za firmu | -| Udalosti na ticketu | hotovo | dalsi zprava se navesi na tentyz ticket | -| Statistiky resitelu | hotovo | odbaveno, mediany casu, vracene, fronta | -| Pohledy tabulka a dlazdice | hotovo | tickety i lide | -| Stranka Lide a detail osoby | hotovo | vykon a co ma u sebe | -| Log ticketu ve strome | hotovo | vcetne toho, co ktera sluzba vratila | -| Kanaly do ticketu | hotovo | WhatsApp, e-mail, hlas a formular jako spoustece | -| Parametry od sluzby | hotovo | katalog je deklaruje, server je dosazuje pri ulozeni | -| Nastaveni poli akci | castecne | ticket, e-mail a WhatsApp ano, ostatni jen napoveda | -| Obsah ticketu a sablony | hotovo | `{{parametr}}` ze spoustece do poli akce | -| Vystupy kroku a predvalidace | hotovo | podminka se umi zeptat, co vratil predchozi krok | -| Kanaly WhatsApp, FB, Instagram | hotovo | vcetne vzorovych automatizaci na prijem | -| Firmy a prava | hotovo | tri pohledy, uzivatel muze byt ve vic firmach | -| Nastavitelny dashboard | hotovo | widgety, sirky a poradi, ulozene za uzivatele a firmu | -| Skripty konektoru | hotovo | manifest, kontrola parametru, hot reload, iDoklad | -| Konektory za firmu | hotovo | pristupove udaje v konektoru, overeni napojeni | -| Transformace dat | hotovo | pravidla i sablona JSON, kroky si predavaji struktury | -| Prace nad celym modelem | hotovo | ukazka tela, cesty v sablonach, smycka nad seznamem | -| Vlastni skripty firmy | hotovo | prevod dat v JS, v logu vstup i vystup | -| Sprava clenstvi z portalu | hotovo | firmy a role v Nastaveni, lide a pozvanky v Lidech | -| Role a prava jako data | hotovo | 26 prav v katalogu, vlastni role za firmu | -| Zalozky a limity za firmu | hotovo | navigace chodi ze serveru, ne z kodu klienta | -| Osoby a skupiny resitelu | hotovo | ticket lze prehodit na skupinu, ne jen na cloveka | -| Prevzeti ticketu ze skupiny | hotovo | kdo ma cas, si praci vezme sam | -| Pozvanky do firmy | hotovo | odkaz s kodem, heslo si nastavi pozvany | -| Typy ticketu a vlastni pole | hotovo | typ rozhoduje, ktere akce se na ticketu ukazou | -| Vydefinovane akce na ticketu | hotovo | vazba na typ nebo tag, telo je operace, strom, skript | -| Vlastni widgety | hotovo | vcetne zdroje z konektoru a vykonu resitelu | -| Telo akce jako strom | hotovo | tentyz editor jako automatizace | -| Audit a prepnuti na jiny ucet | hotovo | prepnuti je vychozi jen pro cteni, vse v auditu | -| Bugs a wishes | chybi | vyvojarska agenda, samostatna evidence vedle ticketu | -| Beh automatizaci | hotovo | fronta, worker, opakovani, ochrana proti smycce | -| Prijem udalosti do fronty | hotovo | webhook odpovi 202, praci dela worker | -| Pravidelne dotazovani sluzeb | hotovo | planovac pro postu a zpravy, perioda u spoustece | -| Upozorneni na pridelenou praci | hotovo | cislo u zalozky a hlaska v portalu | -| Incident z chyby | hotovo | popis pro klienta, podrobnosti pro admina | -| Uloziste konektoru | hotovo | Postgres, nebo JSON soubor. Udaje vzdy sifrovane | -| Uloziste pro zbytek | hotovo | tickety, automatizace, incidenty, rozlozeni, entity | -| Monetizace a cena za krok | navrh | popis v 16-monetizace.md, neni naprogramovane | -| Odesilani e-mailu z formulare | chybi | poptavka se zatim jen loguje | +| Oblast | Stav | Poznamka | +| ------------------------------- | -------- | ------------------------------------------------------- | +| Verejny web | hotovo | homepage, sluzby, o nas, kontakt, 404 | +| Prihlaseni | hotovo | JWT, demo ucty | +| Dashboard | hotovo | prehled, tickety, incidenty, automatizace, nastaveni | +| Zivy dashboard pres SSE | hotovo | zmeny se projevi bez obnoveni stranky | +| Katalog sluzeb | hotovo | 34 sluzeb, 7 kategorii vcetne Obecne | +| Builder automatizaci | hotovo | strom akci, vetveni podminkou | +| Webhook s registrovanou adresou | hotovo | token generuje server, verejny endpoint validuje data | +| Tickety na konkretni lidi | hotovo | resitel, filtr moje, prehled vytizeni tymu | +| Prijem udalosti do ticketu | hotovo | webhook na firmu, externi ID unikatni za firmu | +| Udalosti na ticketu | hotovo | dalsi zprava se navesi na tentyz ticket | +| Statistiky resitelu | hotovo | odbaveno, mediany casu, vracene, fronta | +| Pohledy tabulka a dlazdice | hotovo | tickety i lide | +| Stranka Lide a detail osoby | hotovo | vykon a co ma u sebe | +| Log ticketu ve strome | hotovo | vcetne toho, co ktera sluzba vratila | +| Kanaly do ticketu | hotovo | WhatsApp, e-mail, hlas a formular jako spoustece | +| Parametry od sluzby | hotovo | katalog je deklaruje, server je dosazuje pri ulozeni | +| Nastaveni poli akci | castecne | ticket, e-mail a WhatsApp ano, ostatni jen napoveda | +| Obsah ticketu a sablony | hotovo | `{{parametr}}` ze spoustece do poli akce | +| Vystupy kroku a predvalidace | hotovo | podminka se umi zeptat, co vratil predchozi krok | +| Kanaly WhatsApp, FB, Instagram | hotovo | vcetne vzorovych automatizaci na prijem | +| Firmy a prava | hotovo | tri pohledy, uzivatel muze byt ve vic firmach | +| Nastavitelny dashboard | hotovo | widgety, sirky a poradi, ulozene za uzivatele a firmu | +| Skripty konektoru | hotovo | manifest, kontrola parametru, hot reload, iDoklad | +| Napojeni na realne sluzby | hotovo | 13 sluzeb ma bezici aplikaci, udaje a overeni | +| Odesilani souboru ze skriptu | hotovo | `ctx.http.postForm`, obsah jako Base64 | +| OpenAI pod vlastnim klicem | hotovo | dotaz, soubor, prepis zvuku, seznam modelu | +| Konektory za firmu | hotovo | pristupove udaje v konektoru, overeni napojeni | +| Transformace dat | hotovo | pravidla i sablona JSON, kroky si predavaji struktury | +| Prace nad celym modelem | hotovo | ukazka tela, cesty v sablonach, smycka nad seznamem | +| Vlastni skripty firmy | hotovo | prevod dat v JS, v logu vstup i vystup | +| Sprava clenstvi z portalu | hotovo | firmy a role v Nastaveni, lide a pozvanky v Lidech | +| Role a prava jako data | hotovo | 26 prav v katalogu, vlastni role za firmu | +| Zalozky a limity za firmu | hotovo | navigace chodi ze serveru, ne z kodu klienta | +| Osoby a skupiny resitelu | hotovo | ticket lze prehodit na skupinu, ne jen na cloveka | +| Prevzeti ticketu ze skupiny | hotovo | kdo ma cas, si praci vezme sam | +| Pozvanky do firmy | hotovo | odkaz s kodem, heslo si nastavi pozvany | +| Typy ticketu a vlastni pole | hotovo | typ rozhoduje, ktere akce se na ticketu ukazou | +| Vydefinovane akce na ticketu | hotovo | vazba na typ nebo tag, telo je operace, strom, skript | +| Vlastni widgety | hotovo | vcetne zdroje z konektoru a vykonu resitelu | +| Telo akce jako strom | hotovo | tentyz editor jako automatizace | +| Audit a prepnuti na jiny ucet | hotovo | prepnuti je vychozi jen pro cteni, vse v auditu | +| Bugs a wishes | chybi | vyvojarska agenda, samostatna evidence vedle ticketu | +| Beh automatizaci | hotovo | fronta, worker, opakovani, ochrana proti smycce | +| Prijem udalosti do fronty | hotovo | webhook odpovi 202, praci dela worker | +| Pravidelne dotazovani sluzeb | hotovo | planovac pro postu a zpravy, perioda u spoustece | +| Upozorneni na pridelenou praci | hotovo | cislo u zalozky a hlaska v portalu | +| Incident z chyby | hotovo | popis pro klienta, podrobnosti pro admina | +| Uloziste konektoru | hotovo | Postgres, nebo JSON soubor. Udaje vzdy sifrovane | +| Uloziste pro zbytek | hotovo | tickety, automatizace, incidenty, rozlozeni, entity | +| Monetizace a cena za krok | navrh | popis v 16-monetizace.md, neni naprogramovane | +| 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 | ## Znama omezeni @@ -67,11 +72,11 @@ 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` 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 | 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 @@ -115,25 +120,25 @@ pro frontu, beh kroku a rozpocet na 150 klientu, a ## Dokumentace -| Soubor | O cem | -| --- | --- | -| [02-appfactory-proxy.md](02-appfactory-proxy.md) | beh za reverse proxy, ROOT_PATH, health | -| [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md) | kde co je | -| [04-api.md](04-api.md) | endpointy a to, co ze Swaggeru neni videt | -| [05-dashboard-a-builder.md](05-dashboard-a-builder.md) | editor automatizaci | -| [06-tickety.md](06-tickety.md) | model ticketu a log prubehu | -| [07-firmy-a-prava.md](07-firmy-a-prava.md) | firmy, pohledy, kdo co vidi | -| [08-dashboard-widgety.md](08-dashboard-widgety.md) | nastavitelny prehled | -| [09-navrh-rozsireni.md](09-navrh-rozsireni.md) | puvodni navrh rozsireni | -| [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md) | **navrh**: fronta, beh kroku, kapacita | -| [11-skripty-konektoru.md](11-skripty-konektoru.md) | vykonna cast sluzeb | -| [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md) | sluzba, konektor, viditelnost | -| [13-transformace-dat.md](13-transformace-dat.md) | pole na pole a JSON na JSON | -| [14-databaze.md](14-databaze.md) | tri rezimy uloziste, migrace, sifrovani | -| [15-rejstrik-funkci.md](15-rejstrik-funkci.md) | k cemu je jaka funkce a komponenta | -| [16-monetizace.md](16-monetizace.md) | **navrh**: cena za krok a balicky | -| [17-nastaveni-a-prava.md](17-nastaveni-a-prava.md) | prava, typy, akce, widgety, prepnuti uctu | -| [18-ticketovaci-system.md](18-ticketovaci-system.md) | udalosti, externi ID, statistiky, pohledy | -| [19-kapacita-200-firem.md](19-kapacita-200-firem.md) | zmereno, co zvladne soucasny stav | -| [20-fronta-a-runtime.md](20-fronta-a-runtime.md) | fronta, worker, spoustece, ochrana proti smycce | -| [99-zmeny.md](99-zmeny.md) | zaznam zmen, nejnovejsi nahore | +| Soubor | O cem | +| ---------------------------------------------------------------- | ----------------------------------------------- | +| [02-appfactory-proxy.md](02-appfactory-proxy.md) | beh za reverse proxy, ROOT_PATH, health | +| [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md) | kde co je | +| [04-api.md](04-api.md) | endpointy a to, co ze Swaggeru neni videt | +| [05-dashboard-a-builder.md](05-dashboard-a-builder.md) | editor automatizaci | +| [06-tickety.md](06-tickety.md) | model ticketu a log prubehu | +| [07-firmy-a-prava.md](07-firmy-a-prava.md) | firmy, pohledy, kdo co vidi | +| [08-dashboard-widgety.md](08-dashboard-widgety.md) | nastavitelny prehled | +| [09-navrh-rozsireni.md](09-navrh-rozsireni.md) | puvodni navrh rozsireni | +| [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md) | **navrh**: fronta, beh kroku, kapacita | +| [11-skripty-konektoru.md](11-skripty-konektoru.md) | vykonna cast sluzeb | +| [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md) | sluzba, konektor, viditelnost | +| [13-transformace-dat.md](13-transformace-dat.md) | pole na pole a JSON na JSON | +| [14-databaze.md](14-databaze.md) | tri rezimy uloziste, migrace, sifrovani | +| [15-rejstrik-funkci.md](15-rejstrik-funkci.md) | k cemu je jaka funkce a komponenta | +| [16-monetizace.md](16-monetizace.md) | **navrh**: cena za krok a balicky | +| [17-nastaveni-a-prava.md](17-nastaveni-a-prava.md) | prava, typy, akce, widgety, prepnuti uctu | +| [18-ticketovaci-system.md](18-ticketovaci-system.md) | udalosti, externi ID, statistiky, pohledy | +| [19-kapacita-200-firem.md](19-kapacita-200-firem.md) | zmereno, co zvladne soucasny stav | +| [20-fronta-a-runtime.md](20-fronta-a-runtime.md) | fronta, worker, spoustece, ochrana proti smycce | +| [99-zmeny.md](99-zmeny.md) | zaznam zmen, nejnovejsi nahore | diff --git a/documentation/02-appfactory-proxy.md b/documentation/02-appfactory-proxy.md index a5c7ff0..04fa62d 100644 --- a/documentation/02-appfactory-proxy.md +++ b/documentation/02-appfactory-proxy.md @@ -77,12 +77,12 @@ AppFactory. Z aplikacniho repozitare se infrastruktura nemeni, viz AGENTS.md. ## Povinne endpointy -| Verejna cesta | Vraci | -| ------------------------------ | -------------------------------------- | -| `/apps//health` | `{"status":"ok","uptimeSec":N}` | -| `/apps//docs` | presmeruje na `/docs/` | -| `/apps//docs/` | Swagger UI | -| `/apps//openapi.json` | OpenAPI definice | +| Verejna cesta | Vraci | +| ----------------------------- | ------------------------------- | +| `/apps//health` | `{"status":"ok","uptimeSec":N}` | +| `/apps//docs` | presmeruje na `/docs/` | +| `/apps//docs/` | Swagger UI | +| `/apps//openapi.json` | OpenAPI definice | Presmerovani z `/docs` na `/docs/` je nutne. Bez koncoveho lomitka by se relativni odkazy Swagger UI na CSS a JS skladaly o uroven vys a nenacetly by se. diff --git a/documentation/03-architektura-a-mapa-kodu.md b/documentation/03-architektura-a-mapa-kodu.md index e1e5a02..53ac957 100644 --- a/documentation/03-architektura-a-mapa-kodu.md +++ b/documentation/03-architektura-a-mapa-kodu.md @@ -2,13 +2,13 @@ ## Technologie -| Vrstva | Technologie | -| ------- | ---------------------------------------------------- | -| Server | Node.js 20, Express 4, TypeScript, ESM | -| Web | React 18, Vite 6, TypeScript, Tailwind 4, React Router 6 | -| Auth | JWT (jsonwebtoken), hesla bcrypt | -| Validace| zod | -| Docs | swagger-ui-express nad rucne psanou OpenAPI definici | +| Vrstva | Technologie | +| -------- | -------------------------------------------------------- | +| Server | Node.js 20, Express 4, TypeScript, ESM | +| Web | React 18, Vite 6, TypeScript, Tailwind 4, React Router 6 | +| Auth | JWT (jsonwebtoken), hesla bcrypt | +| Validace | zod | +| Docs | swagger-ui-express nad rucne psanou OpenAPI definici | Jeden `package.json`. Runtime zavislosti jsou v `dependencies`, nastroje pro build webu v `devDependencies` - runtime image je pak instaluje pres `--omit=dev`. @@ -25,53 +25,53 @@ image jen `dist`, takze staci jedna slozka. ## Mapa kodu - server -| Cesta | K cemu je | -| --------------------------- | -------------------------------------------------------- | -| `src/index.ts` | vstupni bod: middleware, mount routeru, statika, SPA, Swagger | -| `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/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/webhook.ts` | verejny prijem dat do automatizace | -| `src/routes/contact.ts` | poptavkovy formular z webu | -| `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/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/automationStore.ts` | automatizace, strom akci, tokeny webhooku | -| `src/data/connectors.ts` | katalog konektoru, 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/mock.ts` | souhrn pro prehled a casova rada grafu | +| Cesta | K cemu je | +| ------------------------------ | ------------------------------------------------------------- | +| `src/index.ts` | vstupni bod: middleware, mount routeru, statika, SPA, Swagger | +| `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/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/webhook.ts` | verejny prijem dat do automatizace | +| `src/routes/contact.ts` | poptavkovy formular z webu | +| `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/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/automationStore.ts` | automatizace, strom akci, tokeny webhooku | +| `src/data/connectors.ts` | katalog konektoru, 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/mock.ts` | souhrn pro prehled a casova rada grafu | ## Mapa kodu - web -| Cesta | K cemu je | -| ---------------------------------- | -------------------------------------------------- | -| `web/src/main.tsx` | vstupni bod, `basename` routeru podle prefixu proxy | -| `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/eventStream.ts` | cteni SSE streamu pres fetch | -| `web/src/lib/useApiQuery.ts` | nacitani dat vcetne obnoveni pri udalosti | -| `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/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 | +| Cesta | K cemu je | +| ------------------------------------------------- | --------------------------------------------------- | +| `web/src/main.tsx` | vstupni bod, `basename` routeru podle prefixu proxy | +| `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/eventStream.ts` | cteni SSE streamu pres fetch | +| `web/src/lib/useApiQuery.ts` | nacitani dat vcetne obnoveni pri udalosti | +| `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/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 | ## Klicova rozhodnuti diff --git a/documentation/04-api.md b/documentation/04-api.md index 90cd594..c8be750 100644 --- a/documentation/04-api.md +++ b/documentation/04-api.md @@ -7,17 +7,17 @@ co ze Swaggeru neni videt. Verejne: -| Metoda | Cesta | Popis | -| ------ | ------------------- | --------------------------------------- | -| GET | `/health` | liveness, nezavisi na databazi | -| GET | `/health/ready` | readiness, 503 pri nedostupne databazi | -| GET | `/docs` | Swagger UI | -| GET | `/openapi.json` | OpenAPI definice | -| POST | `/api/auth/login` | prihlaseni, vraci JWT | -| POST | `/api/contact` | poptavka z webu | -| POST | `/webhook/:token` | prijem dat do automatizace | -| POST | `/webhook/ticket/:token` | prijem udalosti do ticketu | -| GET | `/webhook/ticket/:token` | napoveda k prijmu | +| Metoda | Cesta | Popis | +| ------ | ------------------------ | -------------------------------------- | +| GET | `/health` | liveness, nezavisi na databazi | +| GET | `/health/ready` | readiness, 503 pri nedostupne databazi | +| GET | `/docs` | Swagger UI | +| GET | `/openapi.json` | OpenAPI definice | +| POST | `/api/auth/login` | prihlaseni, vraci JWT | +| POST | `/api/contact` | poptavka z webu | +| POST | `/webhook/:token` | prijem dat do automatizace | +| POST | `/webhook/ticket/:token` | prijem udalosti do ticketu | +| GET | `/webhook/ticket/:token` | napoveda k prijmu | Vyzaduji `Authorization: Bearer `: @@ -105,14 +105,14 @@ Jednotny pro cele API: { "error": "validation_error", "message": "Zadejte platny e-mail." } ``` -| HTTP | `error` | Kdy | -| ---- | --------------------- | --------------------------------------- | -| 400 | `validation_error` | vstup neprosel schematem | -| 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 | -| 409 | ruzne | operace nedava v danem stavu smysl | +| HTTP | `error` | Kdy | +| ---- | --------------------- | ------------------------------------------- | +| 400 | `validation_error` | vstup neprosel schematem | +| 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 | +| 409 | ruzne | operace nedava v danem stavu smysl | | 500 | `internal_error` | neodchycena chyba, detail jen mimo produkci | `message` je vzdy cesky a je urcena k zobrazeni uzivateli. @@ -150,12 +150,12 @@ Verejny endpoint bez prihlaseni. Autorizuje neuhodnutelny token v adrese, Token generuje **vyhradne server**. Hodnota `webhookToken` poslana klientem se ignoruje, jinak by si sel nastavit predvidatelnou adresu. -| Situace | Odpoved | -| ----------------------------------- | ------- | -| vse v poradku | 202 | -| neznamy token | 404 | -| automatizace je pozastavena | 409 | -| chybi povinny parametr, spatny typ | 400 | +| Situace | Odpoved | +| ---------------------------------- | ------- | +| vse v poradku | 202 | +| neznamy token | 404 | +| automatizace je pozastavena | 409 | +| chybi povinny parametr, spatny typ | 400 | Parametry navic se neodmitaji, jen loguji. Odesilatele bezne posilaji i vlastni data a odmitat je by rozbijelo integrace. @@ -217,13 +217,13 @@ uz adresu zname, tak ji, at ji clovek nemusi psat. Nic o tom, kdo ve firme je. `POST /api/invites/:kod/accept` s telem `{"name", "email", "password"}`: -| Situace | Co se stane | -| --- | --- | -| ucet neexistuje | zalozi se a pripoji k firme | -| ucet existuje, heslo sedi | jen se pripoji k firme | -| ucet existuje, heslo nesedi | 401 | -| pozvanka je na jinou adresu | 403 | -| pozvanka uz byla pouzita nebo vyprsela | 409 s konkretnim duvodem | +| Situace | Co se stane | +| -------------------------------------- | --------------------------- | +| ucet neexistuje | zalozi se a pripoji k firme | +| ucet existuje, heslo sedi | jen se pripoji k firme | +| ucet existuje, heslo nesedi | 401 | +| pozvanka je na jinou adresu | 403 | +| pozvanka uz byla pouzita nebo vyprsela | 409 s konkretnim duvodem | Overeni hesla u existujiciho uctu neni formalita: bez nej by kdokoliv s odkazem pripojil cizi adresu ke sve firme a videl by jeji data. diff --git a/documentation/05-dashboard-a-builder.md b/documentation/05-dashboard-a-builder.md index 9c210be..d472aa7 100644 --- a/documentation/05-dashboard-a-builder.md +++ b/documentation/05-dashboard-a-builder.md @@ -170,14 +170,14 @@ Builder i katalog ji vezmou automaticky. ## Co chybi -| 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 | +| 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 | ## Co je videt v kterem kroku @@ -220,13 +220,13 @@ parametry. Podminka se totiz pta na parametr, ne na cestu. ### Odkaz muze byt cesta -| Odkaz | Co vrati | -| --- | --- | -| `{{callSid}}` | deklarovany parametr, jako driv | -| `{{data.order.code}}` | hodnotu z prijateho tela | -| `{{data.order.items[0].name}}` | prvni polozku seznamu | -| `{{st_faktura.invoiceId}}` | vystup kroku `st_faktura` | -| `{{item.amount}}` | polozku uvnitr smycky | +| Odkaz | Co vrati | +| ------------------------------ | ------------------------------- | +| `{{callSid}}` | deklarovany parametr, jako driv | +| `{{data.order.code}}` | hodnotu z prijateho tela | +| `{{data.order.items[0].name}}` | prvni polozku seznamu | +| `{{st_faktura.invoiceId}}` | vystup kroku `st_faktura` | +| `{{item.amount}}` | polozku uvnitr smycky | Overuje se jen **prvni cast** odkazu. Zbytek je cesta a tu predem overit nejde - co presne prijde v tele, vime az pri behu. Diky tomu ploche odkazy funguji dal diff --git a/documentation/06-tickety.md b/documentation/06-tickety.md index 746ec8e..b46520b 100644 --- a/documentation/06-tickety.md +++ b/documentation/06-tickety.md @@ -88,10 +88,10 @@ prace se nerozdava shora. Proc obojí vedle sebe: -| Situace | Co se hodi | -| --- | --- | +| Situace | Co se hodi | +| --------------------------------- | ----------------------------- | | havarie, musi to nekdo hned resit | automat prideli nejvolnejsimu | -| bezny dotaz, lidi maji ruzne dny | necha se ve fronte skupiny | +| bezny dotaz, lidi maji ruzne dny | necha se ve fronte skupiny | Proto je prepinac **Priradit rovnou nejvolnejsimu** na kroku automatizace (`ticket/assign-group`), ne na skupine: tataz skupina potrebuje obe chovani, @@ -170,12 +170,12 @@ Odesilatele umi poslat stejnou zpravu i osmdesatkrat za minutu. Kdyz prijde **presne totez co posledne** (stejny typ, zdroj, popisek a stejna data), nezaklada se dalsi radek: pricte se k pocitadlu u te predchozi. -| Co se stane | Proc | -| --- | --- | -| `repeats` +1, `lastAt` = ted | osmdesat radku znamena, ze v historii nikdo nic nenajde | -| ticket se **nemeni** | jinak by duplikat rozblikal dashboard a spustil automatizaci na zmenu ticketu | -| do logu se nezapisuje nic | log ma ukazovat zmeny, ne to, ze se nezmenilo nic | -| udalost se **nezahazuje** | bez pocitadla by nikdo nezjistil, ze proti nam neco tluce ve smycce | +| Co se stane | Proc | +| ---------------------------- | ----------------------------------------------------------------------------- | +| `repeats` +1, `lastAt` = ted | osmdesat radku znamena, ze v historii nikdo nic nenajde | +| ticket se **nemeni** | jinak by duplikat rozblikal dashboard a spustil automatizaci na zmenu ticketu | +| do logu se nezapisuje nic | log ma ukazovat zmeny, ne to, ze se nezmenilo nic | +| udalost se **nezahazuje** | bez pocitadla by nikdo nezjistil, ze proti nam neco tluce ve smycce | Porovnava se jen s posledni udalosti. "Objednavka pripravena" muze legitimne prijit znovu za hodinu, kdyz se mezitim stalo neco jineho - to je novy fakt. @@ -204,20 +204,20 @@ ze se neco stalo, a nerekne co. Kategorie `servicedesk`, driv byl pod Nastroji. Ma obe strany: -| Spoustec | Kdy | -| ------------------ | ------------------------------------------------------ | -| `created` | zalozen ticket, at uz z kanalu nebo rucne | -| `unknown-customer` | k ticketu se nepodarilo dohledat firmu | -| `assigned` | ticket dostal konkretniho cloveka | -| `status-changed` | prechod do jineho stavu vcetne vyreseni | +| Spoustec | Kdy | +| ------------------ | ----------------------------------------- | +| `created` | zalozen ticket, at uz z kanalu nebo rucne | +| `unknown-customer` | k ticketu se nepodarilo dohledat firmu | +| `assigned` | ticket dostal konkretniho cloveka | +| `status-changed` | prechod do jineho stavu vcetne vyreseni | -| Akce | Co dela | -| --------------- | --------------------------------------------- | -| `create` | zalozi pozadavek | -| `assign` | preda ticket cloveku | -| `set-status` | posune stav | -| `link-customer` | doplni firmu z CRM | -| `comment` | zapise komentar do logu | +| Akce | Co dela | +| --------------- | ----------------------- | +| `create` | zalozi pozadavek | +| `assign` | preda ticket cloveku | +| `set-status` | posune stav | +| `link-customer` | doplni firmu z CRM | +| `comment` | zapise komentar do logu | Typicky retez, ktery z toho jde postavit: @@ -236,15 +236,15 @@ Akce "Zalozit ticket" ma nastavitelna pole. Klikni na krok ve strome a vyplnis je primo tam. Hodnota je **sablona**: `{{nazev}}` se nahradi parametrem spoustece. -| Pole | Typicka hodnota u WhatsApp | -| ------------ | --------------------------------- | -| Predmet | `Zprava od {{profileName}}` | -| Obsah | `{{text}}` | -| Firma | necha se prazdne, doplni CRM krok | -| Kontakt | `{{profileName}}` | -| Odpoved na | `{{phone}}` | -| Priorita | vyber ze seznamu | -| Resitel | vyber ze seznamu lidi | +| Pole | Typicka hodnota u WhatsApp | +| ---------- | --------------------------------- | +| Predmet | `Zprava od {{profileName}}` | +| Obsah | `{{text}}` | +| Firma | necha se prazdne, doplni CRM krok | +| Kontakt | `{{profileName}}` | +| Odpoved na | `{{phone}}` | +| Priorita | vyber ze seznamu | +| Resitel | vyber ze seznamu lidi | Nabidka parametru je pod poli. Kliknuti vlozi `{{nazev}}` na pozici kurzoru, takze se nemusi psat rucne a neudela se preklep. @@ -317,12 +317,12 @@ automatizace na kanal, smerovani je jedna spolecna nad vsemi tickety. V `automationStore.ts` jsou nasazene presne v tomhle rozdeleni: -| Automatizace | Co ukazuje | -| -------------------------------- | --------------------------------------------- | -| WhatsApp: zprava do ticketu | predvalidace v CRM a vetveni podle vysledku | -| Facebook: zprava do ticketu | prijem bez predvalidace, nemame podle ceho hledat | -| E-mail: pozadavky do ticketu | dohledani podle `{{from}}`, plus odpoved zadavateli | -| Smerovani ticketu na resitele | jedna spolecna logika nad vsemi tickety | +| Automatizace | Co ukazuje | +| ----------------------------- | --------------------------------------------------- | +| WhatsApp: zprava do ticketu | predvalidace v CRM a vetveni podle vysledku | +| Facebook: zprava do ticketu | prijem bez predvalidace, nemame podle ceho hledat | +| E-mail: pozadavky do ticketu | dohledani podle `{{from}}`, plus odpoved zadavateli | +| Smerovani ticketu na resitele | jedna spolecna logika nad vsemi tickety | Prijmove automatizace zamerne **neprirazuji resitele**. Nechavaji ticket ve fronte a smerovani si ho prevezme. Podminka `assigned neni splneno` na zacatku @@ -402,12 +402,12 @@ to rozhodnout vedome, ne omylem. Varianty od nejlevnejsi: ## Co chybi -| Chybi | Poznamka | -| ---------------------------- | ------------------------------------------------------- | -| Bugs a wishes | vyvojarska agenda, samostatna evidence | -| Skutecny beh automatizaci | sablony se ukladaji, ale nikdo je nevyhodnocuje | -| `inputs` u zbylych konektoru | zatim ticket, kanaly, CRM a AI, ostatni maji jen napovedu | -| Napojeni logu na beh | `automationId` je odkaz, historie behu ale neexistuje | -| Odpoved zakaznikovi z detailu| akce `send` u kanalu se z portalu nevola | -| SLA a eskalace | zadne lhuty, `capacity` je jen orientacni | -| Databaze | data v pameti, restart je vrati na vychozi sadu | +| Chybi | Poznamka | +| ----------------------------- | --------------------------------------------------------- | +| Bugs a wishes | vyvojarska agenda, samostatna evidence | +| Skutecny beh automatizaci | sablony se ukladaji, ale nikdo je nevyhodnocuje | +| `inputs` u zbylych konektoru | zatim ticket, kanaly, CRM a AI, ostatni maji jen napovedu | +| Napojeni logu na beh | `automationId` je odkaz, historie behu ale neexistuje | +| Odpoved zakaznikovi z detailu | akce `send` u kanalu se z portalu nevola | +| SLA a eskalace | zadne lhuty, `capacity` je jen orientacni | +| Databaze | data v pameti, restart je vrati na vychozi sadu | diff --git a/documentation/07-firmy-a-prava.md b/documentation/07-firmy-a-prava.md index e8d3970..b5cbfdd 100644 --- a/documentation/07-firmy-a-prava.md +++ b/documentation/07-firmy-a-prava.md @@ -34,11 +34,11 @@ globalni, nesla by tahle situace vubec zapsat. ## Tri pohledy na tickety -| Pohled | Co ukazuje | Kdo smi | -| -------- | ------------------------- | ------------------------------ | -| `all` | napric vsemi firmami | jen `platformAdmin` | -| `tenant` | cela jedna firma | kdokoliv, kdo do ni patri | -| `mine` | jen tickety prihlaseneho | kdo ma navazaneho resitele | +| Pohled | Co ukazuje | Kdo smi | +| -------- | ------------------------ | -------------------------- | +| `all` | napric vsemi firmami | jen `platformAdmin` | +| `tenant` | cela jedna firma | kdokoliv, kdo do ni patri | +| `mine` | jen tickety prihlaseneho | kdo ma navazaneho resitele | Posilaji se jako query: `?scope=tenant&tenantId=tnt_automia`. @@ -70,12 +70,12 @@ prava pocitala na dvou mistech a jednou se rozejdou. Pozadavek na pohled nebo firmu, na kterou uzivatel nema pravo, vraci **chybu**, ne potichu zuzeny vysledek: -| Situace | Odpoved | -| -------------------------------- | ------- | -| pohled bez opravneni | 403 | -| firma, do ktere nepatri | 404 | -| ucet bez firmy | 403 | -| prirazeni ostatnim bez prava | 403 | +| Situace | Odpoved | +| ---------------------------- | ------- | +| pohled bez opravneni | 403 | +| firma, do ktere nepatri | 404 | +| ucet bez firmy | 403 | +| prirazeni ostatnim bez prava | 403 | Duvod: kdyby se pozadavek na cizi firmu jen prepnul na vlastni, uzivatel by koukal na cizi cisla v domneni, ze jsou spravna. To je horsi nez chyba. @@ -92,6 +92,16 @@ tedy 404, ne 403 - z odpovedi nemá jit poznat, ze takove ID vubec existuje. Vyjimka je verejny webhook. Ten se autorizuje tokenem v adrese, ne prihlasenim, takze si automatizaci najde pres vsechny firmy. +## Jedina vyjimka: helpdesk + +Ticket patri jedne firme a to plati dal. U pozadavku z helpdesku ale figuruji +dve: vlastnikem je ta, ktera ho resi, a v `helpdeskSourceId` je ta, ktera ho +poslala. Zadavatel se k nemu dostane **jen pres helpdesk** a jen ke svym +pozadavkum; bezny seznam ticketu zustava vlastnikovi. + +Neni to obchazeni hranice, je to druha cesta dovnitr s vlastnim scopem. Popis +je v [18-ticketovaci-system.md](18-ticketovaci-system.md). + ## Prirazeni jen v ramci firmy `assignTicket` odmitne resitele z jine firmy. Jinak by ticket zmizel z prehledu @@ -104,21 +114,21 @@ ale nemuze ho poslat kolegovi - to hlida `canAssignOthers`. Heslo je u vsech `demo1234`. -| E-mail | Kdo je | -| ------------------------- | ----------------------------------------------- | -| `admin@automia.cz` | spravce platformy, vidi vsechny tri firmy | -| `karel.vomacka@automia.cz`| agent v Automii, admin u Nordisu - dve firmy | -| `martin.kriz@automia.cz` | bezny resitel jedne firmy | +| E-mail | Kdo je | +| -------------------------- | -------------------------------------------- | +| `admin@automia.cz` | spravce platformy, vidi vsechny tri firmy | +| `karel.vomacka@automia.cz` | agent v Automii, admin u Nordisu - dve firmy | +| `martin.kriz@automia.cz` | bezny resitel jedne firmy | Druhy ucet je ten zajimavy: ukazuje prepinac firem i to, ze prava se lisi podle toho, ktera firma je prave zvolena. ## 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 | +| 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 | diff --git a/documentation/08-dashboard-widgety.md b/documentation/08-dashboard-widgety.md index 523493e..729eccc 100644 --- a/documentation/08-dashboard-widgety.md +++ b/documentation/08-dashboard-widgety.md @@ -67,12 +67,12 @@ Duvod je stejny jako u stromu automatizaci, viz Server overuje ulozene rozlozeni proti katalogu: -| Situace | Vysledek | -| ----------------------------------- | -------- | -| neznamy widget | 400 | -| sirka, kterou widget nepodporuje | 400 | -| duplicitni ID instance | 400 | -| vic nez 12 widgetu | 400 | +| Situace | Vysledek | +| -------------------------------- | -------- | +| neznamy widget | 400 | +| sirka, kterou widget nepodporuje | 400 | +| duplicitni ID instance | 400 | +| vic nez 12 widgetu | 400 | Neulozit je tady spravne. Klient by dostal zpatky neco, co neumi vykreslit. @@ -90,10 +90,10 @@ Nabidka i rozlozeni ho vezmou automaticky. ## Co chybi -| Chybi | Poznamka | -| -------------------------- | ------------------------------------------------- | -| Drag and drop | poradi se meni sipkami, stejne jako ve strome | -| 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 | +| Chybi | Poznamka | +| ------------------------------ | -------------------------------------------------- | +| Drag and drop | poradi se meni sipkami, stejne jako ve strome | +| 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 | diff --git a/documentation/09-navrh-rozsireni.md b/documentation/09-navrh-rozsireni.md index 0af4621..6a5bbf1 100644 --- a/documentation/09-navrh-rozsireni.md +++ b/documentation/09-navrh-rozsireni.md @@ -28,10 +28,10 @@ Stejne pravidlo plati na widgety (katalog je zdroj pravdy) a na prava Automatizacni stromy a akce jsou **dve samostatne veci** a nemaji spolecnou vrstvu mezi sebou: -| Vec | Spousti | Kde je definovana | -| -------------------- | --------------------------- | ------------------------ | -| Automatizacni strom | udalost, spoustec | seznam automatizaci | -| Akce | clovek kliknutim na ticketu | seznam akci za firmu | +| Vec | Spousti | Kde je definovana | +| ------------------- | --------------------------- | -------------------- | +| Automatizacni strom | udalost, spoustec | seznam automatizaci | +| Akce | clovek kliknutim na ticketu | seznam akci za firmu | **Akce se vaze na typ nebo tag ticketu.** Priklad: "Odeslat do iDokladu" pro objednavku. Na ticketu se pak vykresli CTA vsech akci, ktere na jeho typ nebo tag @@ -110,10 +110,10 @@ precetl. V zadani je otazka, jestli je "objednavka" tag. Odpoved je, ze jsou potreba **oba a jsou to jine veci**: -| Vec | Kolik na ticket | K cemu | -| ---- | --------------- | --------------------------------------------- | -| Typ | prave jeden | vlastni pole a vlastni workflow stavu | -| Tag | libovolne mnoho | volne oznaceni, filtry a widgety | +| Vec | Kolik na ticket | K cemu | +| --- | --------------- | ------------------------------------- | +| Typ | prave jeden | vlastni pole a vlastni workflow stavu | +| Tag | libovolne mnoho | volne oznaceni, filtry a widgety | **Akce se muze vazat na oboji**, ale nasledek se lisi: @@ -180,11 +180,11 @@ se neukaze. Akce neni jen jeden krok. `ActionBody` je proto union a **kazdy druh je jina uroven slozitosti pro tehoz cloveka**: -| Druh | Kdy | UI | -| ----------- | ---------------------------------------------- | --------------- | -| `operation` | odesli tenhle doklad tam | formular | -| `tree` | zkus to, a kdyz se to nepovede, dej to Karlovi | builder | -| `script` | poskladej text v nasem formatu | editor skriptu | +| Druh | Kdy | UI | +| ----------- | ---------------------------------------------- | -------------- | +| `operation` | odesli tenhle doklad tam | formular | +| `tree` | zkus to, a kdyz se to nepovede, dej to Karlovi | builder | +| `script` | poskladej text v nasem formatu | editor skriptu | `tree` pouziva **tentyz model kroku, tentyz builder a tutez validaci** jako automatizace. Neni to druhy strom, je to ten samy strom na jinem miste. Kdyby to @@ -203,11 +203,11 @@ vsechna omezeni z bodu 9: nema sit, nema tajemstvi, vystupy deklaruje dopredu. Akce spustena z ticketu dostane vstupy ve tri skupinach. Pro builder je to totez jako `providedFields` u spoustece, takze se menit nemusi: -| Zdroj | Priklad ID | Priklad v sablone | -| ---------------------- | ----------------------- | --------------------- | -| vestavena pole ticketu | `ticket.subject` | `{{subject}}` | -| vlastni pole typu | `ticket.fld_order_no` | `{{orderNumber}}` | -| doptavaci formular | `form.reason` | `{{reason}}` | +| Zdroj | Priklad ID | Priklad v sablone | +| ---------------------- | --------------------- | ----------------- | +| vestavena pole ticketu | `ticket.subject` | `{{subject}}` | +| vlastni pole typu | `ticket.fld_order_no` | `{{orderNumber}}` | +| doptavaci formular | `form.reason` | `{{reason}}` | Nabidka vstupu se pocita **za typ ticketu**, ne staticky z katalogu. To je jediny novy pripad: katalog dnes vraci pevny seznam. @@ -285,15 +285,15 @@ prava hlida `canAssignOthers`, v detailu ticketu jsou dva selecty. Navrh je **postavit vestavene akce do stejneho seznamu jako vlastni**. Ne jako zvlastni kus UI vedle nich. -| ID | Co dela | Pravo | -| ---------------------- | ------------------------------ | ------------------------- | -| `builtin.assign.self` | vzit ticket na sebe | `ticket.assign.self` | -| `builtin.assign.other` | prehodit na kolegu | `ticket.assign.others` | -| `builtin.assign.group` | prehodit na skupinu | `ticket.assign.group` | -| `builtin.status` | zmenit stav | `ticket.status.change` | -| `builtin.priority` | zmenit prioritu | `ticket.priority.change` | -| `builtin.type` | zmenit typ ticketu | `ticket.type.change` | -| `builtin.reopen` | otevrit vyreseny | `ticket.reopen` | +| ID | Co dela | Pravo | +| ---------------------- | ------------------- | ------------------------ | +| `builtin.assign.self` | vzit ticket na sebe | `ticket.assign.self` | +| `builtin.assign.other` | prehodit na kolegu | `ticket.assign.others` | +| `builtin.assign.group` | prehodit na skupinu | `ticket.assign.group` | +| `builtin.status` | zmenit stav | `ticket.status.change` | +| `builtin.priority` | zmenit prioritu | `ticket.priority.change` | +| `builtin.type` | zmenit typ ticketu | `ticket.type.change` | +| `builtin.reopen` | otevrit vyreseny | `ticket.reopen` | Dva prinosy: pravo se resi jednim mechanismem pro vestavene i vlastni akce, a admin muze vestavenou akci pro nekoho vypnout, aniz by se menil kod. @@ -416,10 +416,10 @@ interface TenantFeatures { **Dve vrstvy s jinym vlastnikem, ktere se nesmi michat:** -| Vrstva | Kdo nastavuje | Znamena | -| --------------- | ------------------- | ------------------------------------ | -| `TenantFeatures`| my, provozovatel | co ma firma zaplacene a zapnute | -| `Role` | admin te firmy | kdo z jejich lidi to smi | +| Vrstva | Kdo nastavuje | Znamena | +| ---------------- | ---------------- | ------------------------------- | +| `TenantFeatures` | my, provozovatel | co ma firma zaplacene a zapnute | +| `Role` | admin te firmy | kdo z jejich lidi to smi | Efektivni viditelnost je prunik. Vypnuty modul neexistuje ani pro admina te firmy - nema si ho jak zapnout, protoze ho nema. Kdyby to byla jedna vrstva, @@ -611,13 +611,13 @@ funkci ulozist** a prepsat jim vnitrek. Routy se menit nemaji. ### Volby -| Vec | Navrh | Proc | -| ------------- | ------------------------------------ | ------------------------------------------- | -| Databaze | PostgreSQL 16 | JSONB, `SKIP LOCKED`, `LISTEN/NOTIFY` | -| Driver | `pg` | bez nadstavby, pool | -| Dotazy | Drizzle (nebo Kysely) | typovane SQL, ne skryty ORM | -| Migrace | `drizzle-kit`, soubory v repu | deterministicke, dohledatelne v gitu | -| Pripojeni | `DATABASE_URL` jako AppFactory secret| nikdy v kodu, nikdy v logu | +| Vec | Navrh | Proc | +| --------- | ------------------------------------- | ------------------------------------- | +| Databaze | PostgreSQL 16 | JSONB, `SKIP LOCKED`, `LISTEN/NOTIFY` | +| Driver | `pg` | bez nadstavby, pool | +| Dotazy | Drizzle (nebo Kysely) | typovane SQL, ne skryty ORM | +| Migrace | `drizzle-kit`, soubory v repu | deterministicke, dohledatelne v gitu | +| Pripojeni | `DATABASE_URL` jako AppFactory secret | nikdy v kodu, nikdy v logu | Prisny ORM se nedoporucuje. Cely projekt je psany tak, ze server je autorita a filtr na firmu je povinny argument - to se hlida lip nad viditelnym SQL. @@ -777,11 +777,11 @@ a `07-firmy-a-prava.md` vede "Tenant u konektoru" jako chybejici. Priklad ze zadani (vsichni mohou iDoklad, jen firma C vidi Polstryn SAP, firma B iDoklad vidi ale nema napojeni) nejde zapsat mene nez tremi vrstvami: -| Vrstva | Co to je | Kdo to vlastni | -| --------------- | ------------------------------------ | -------------------- | -| **Definice** | ze iDoklad existuje a co umi | my, nebo firma | -| **Zpristupneni**| kdo ho vubec smi videt | my | -| **Napojeni** | ucet firmy s jejimi pristupy | firma | +| Vrstva | Co to je | Kdo to vlastni | +| ---------------- | ---------------------------- | -------------- | +| **Definice** | ze iDoklad existuje a co umi | my, nebo firma | +| **Zpristupneni** | kdo ho vubec smi videt | my | +| **Napojeni** | ucet firmy s jejimi pristupy | firma | ```ts interface ConnectorDefinition { @@ -834,12 +834,12 @@ Dnes je `ConnectorStatus = 'connected' | 'available' | 'planned'` pevne pole v katalogu. Ty tri hodnoty jsou ale presne to, co zadani popisuje, takze staci je **pocitat za firmu**: -| Stav | Kdy | -| ----------- | ------------------------------------------------------- | -| `connected` | firma ma aktivni napojeni | -| `available` | vidi definici, napojeni nema, muze si ho udelat | -| `planned` | definice je na roadmape | -| neviditelny | `restricted` bez zpristupneni - v odpovedi vubec neni | +| Stav | Kdy | +| ----------- | ----------------------------------------------------- | +| `connected` | firma ma aktivni napojeni | +| `available` | vidi definici, napojeni nema, muze si ho udelat | +| `planned` | definice je na roadmape | +| neviditelny | `restricted` bez zpristupneni - v odpovedi vubec neni | `GET /api/dashboard/connectors` tim prestava byt spolecny a zacina byt za firmu. Neviditelna definice se **nevraci se stavem, ale nevraci se vubec**. Firma A nesmi @@ -902,11 +902,11 @@ Runtime v obou pripadech cte z DB, takze se kod nemusi rozdvojovat. Definice ma u kazde operace **implementaci**, a jsou tri druhy: -| Druh | Kde je kod | Pro co | -| --------- | ----------------- | ----------------------------------------- | -| `builtin` | v repu, TypeScript| nase konektory, kde potrebujeme plnou moc | -| `http` | zadny kod | vetsina REST API, i to, co si udela firma | -| `script` | sandbox | prevod dat a divne protokoly | +| Druh | Kde je kod | Pro co | +| --------- | ------------------ | ----------------------------------------- | +| `builtin` | v repu, TypeScript | nase konektory, kde potrebujeme plnou moc | +| `http` | zadny kod | vetsina REST API, i to, co si udela firma | +| `script` | sandbox | prevod dat a divne protokoly | **`http` ma byt vychozi**, i pro nase konektory. Operace je pak zaznam: @@ -975,13 +975,13 @@ Tim se z vyjimky stava normalni konektor. U konektoru i skriptu se musi rozlisit, a kazda ma jineho vlastnika: -| Otazka | Mechanismus | Nastavuje | -| --------------------------------- | -------------------------- | --------------- | -| Kdo to **vidi** | `visibility` plus grant | my | -| Kdo si smi udelat **napojeni** | pravo `connector.manage` | admin firmy | -| Kdo to smi **pouzit ve strome** | pravo `automation.edit` | admin firmy | -| Kdo smi **spustit** rucni akci | pravo `action:` | admin firmy | -| Kdo smi **upravit skript** | pravo `script.edit` | my | +| Otazka | Mechanismus | Nastavuje | +| ------------------------------- | ------------------------ | ----------- | +| Kdo to **vidi** | `visibility` plus grant | my | +| Kdo si smi udelat **napojeni** | pravo `connector.manage` | admin firmy | +| Kdo to smi **pouzit ve strome** | pravo `automation.edit` | admin firmy | +| Kdo smi **spustit** rucni akci | pravo `action:` | admin firmy | +| Kdo smi **upravit skript** | pravo `script.edit` | my | Zvlast posledni radek: uprava skriptu zmeni chovani vseho, co ho pouziva. To neni pravo, ktere se dava vedle prava zakladat tickety. @@ -1067,13 +1067,13 @@ Je to jediny bod celeho navrhu, ktery si rika o dalsi sluzbu vedle Postgresu. ### Widgety z prikladu -| Widget | Zdroj | -| ---------------------------------------- | -------------------------------------------------- | -| Pocet objednavek od-do | `connectorMetric` nad `cn_1`, metrika a obdobi | -| Pocet novych klientu od-do | `connectorMetric` nad `cn_1` | -| Pocet ticketu typu Objednavka | `ticketCount`, filtr `typeIds: [tt_order]` | -| Pocet padlych behu | `runCount`, filtr `status: failed` | -| Padle tickety vuci lidem | `ticketCount` plus `groupBy: 'assignee'` | +| Widget | Zdroj | +| ----------------------------- | ---------------------------------------------- | +| Pocet objednavek od-do | `connectorMetric` nad `cn_1`, metrika a obdobi | +| Pocet novych klientu od-do | `connectorMetric` nad `cn_1` | +| Pocet ticketu typu Objednavka | `ticketCount`, filtr `typeIds: [tt_order]` | +| Pocet padlych behu | `runCount`, filtr `status: failed` | +| Padle tickety vuci lidem | `ticketCount` plus `groupBy: 'assignee'` | Prvni dva se ve firme Delo postavit nedaji, protoze `cn_1` do ni nepatri. Az bude mit Delo svuj iDoklad, postavi si je nad svym napojenim a cisla budou @@ -1088,17 +1088,17 @@ je cekaci krok z bodu 8 skoro zdarma. ## Poradi prace -| Vlna | Co | Zavisi na | -| ---- | ----------------------------------------------------- | --------- | -| 0 | Postgres, prevod ulozist, LISTEN/NOTIFY (bod 7) | - | -| 1 | Konektory do DB: definice, zpristupneni, napojeni, sifrovani udaju (bod 9) | 0 | -| 2 | Definice akci: telo jako operace, strom nebo skript, verzovani | 0, 1 | -| 3 | Typy ticketu, tagy, vlastni pole, rucni akce, vestavene akce (1, 2, 3) | 0, 2 | -| 4 | Role a prava jako data, zalozky ze serveru, Nastaveni klienta, audit, impersonace (5, 6) | 0, 1 | -| 5 | Runtime: fronta, retry, idempotence, fairness, krok `wait` (bod 8) | 0, 2 | -| 6 | Sablony zprav a odesilani e-mailu (bod 8.1) | 0, 5 | -| 7 | Vlastni widgety, seskupovani, sdilene rozlozeni (bod 4) | 0, 1 | -| 8 | Skripty v sandboxu | 1, 5 | +| Vlna | Co | Zavisi na | +| ---- | ---------------------------------------------------------------------------------------- | --------- | +| 0 | Postgres, prevod ulozist, LISTEN/NOTIFY (bod 7) | - | +| 1 | Konektory do DB: definice, zpristupneni, napojeni, sifrovani udaju (bod 9) | 0 | +| 2 | Definice akci: telo jako operace, strom nebo skript, verzovani | 0, 1 | +| 3 | Typy ticketu, tagy, vlastni pole, rucni akce, vestavene akce (1, 2, 3) | 0, 2 | +| 4 | Role a prava jako data, zalozky ze serveru, Nastaveni klienta, audit, impersonace (5, 6) | 0, 1 | +| 5 | Runtime: fronta, retry, idempotence, fairness, krok `wait` (bod 8) | 0, 2 | +| 6 | Sablony zprav a odesilani e-mailu (bod 8.1) | 0, 5 | +| 7 | Vlastni widgety, seskupovani, sdilene rozlozeni (bod 4) | 0, 1 | +| 8 | Skripty v sandboxu | 1, 5 | Zmena proti prvni verzi: **konektory se posunuly na zacatek**. Bez rozdeleni na definici, zpristupneni a napojeni nema smysl delat typy ticketu ani widgety, @@ -1116,21 +1116,21 @@ casto se ukaze, ze skripty nikdo nepotrebuje. Seznam mist, ktera navrh meni a je potreba je hlidat. -| Zmena | Dotkne se | -| -------------------------------------------- | ------------------------------------------------------ | -| `accessFor(user)` -> `accessFor(user, tenantId)` | vsechny routy dashboardu, prava jsou az uvnitr firmy | -| `Membership.role` -> `roleIds` | `types.ts`, `users.ts`, `access.ts`, `middleware/auth.ts` | -| `requireRole` -> `requirePermission` | `src/middleware/auth.ts` a vsechna jeho pouziti | -| `Ticket` dostane `typeId` a `fields` | `ticketStore.ts`, `openapi.ts`, `web/src/types/dashboard.ts`, seznam, detail, simulace | -| Katalog konektoru prestane byt spolecny | `connectors.ts`, `GET /connectors`, `Connectors.tsx` - vraci se za firmu | -| `ConnectorStatus` se prestane cist z katalogu | pocita se z napojeni, dnes je to pevne pole | -| `FlowStep` dostane `connectionId` | `automationStore.ts`, `flow.ts`, validace stromu, builder | -| Zalozky ze serveru | `web/src/components/dashboard/DashboardLayout.tsx`, dnes konstanta | -| `WidgetKind` -> `render` plus `source` | `widgets.ts`, `WidgetCard.tsx`, ulozena rozlozeni potrebuji prevod ID | -| Novy druh kroku `wait` a `call` | `flow.ts`, `flowScope.ts`, `FlowCanvas.tsx`, validace | -| `visibleWhen` potrebuje AND vice podminek | model podminek dnes umi jedno porovnani, viz `conditions.ts` | -| Log ticketu potrebuje redakci tajemstvi | `ticketStore.ts`, zapis `response` do trace | -| Data z pameti do Postgresu | cele `src/data/`, routy zustavaji | +| Zmena | Dotkne se | +| ------------------------------------------------ | -------------------------------------------------------------------------------------- | +| `accessFor(user)` -> `accessFor(user, tenantId)` | vsechny routy dashboardu, prava jsou az uvnitr firmy | +| `Membership.role` -> `roleIds` | `types.ts`, `users.ts`, `access.ts`, `middleware/auth.ts` | +| `requireRole` -> `requirePermission` | `src/middleware/auth.ts` a vsechna jeho pouziti | +| `Ticket` dostane `typeId` a `fields` | `ticketStore.ts`, `openapi.ts`, `web/src/types/dashboard.ts`, seznam, detail, simulace | +| Katalog konektoru prestane byt spolecny | `connectors.ts`, `GET /connectors`, `Connectors.tsx` - vraci se za firmu | +| `ConnectorStatus` se prestane cist z katalogu | pocita se z napojeni, dnes je to pevne pole | +| `FlowStep` dostane `connectionId` | `automationStore.ts`, `flow.ts`, validace stromu, builder | +| Zalozky ze serveru | `web/src/components/dashboard/DashboardLayout.tsx`, dnes konstanta | +| `WidgetKind` -> `render` plus `source` | `widgets.ts`, `WidgetCard.tsx`, ulozena rozlozeni potrebuji prevod ID | +| Novy druh kroku `wait` a `call` | `flow.ts`, `flowScope.ts`, `FlowCanvas.tsx`, validace | +| `visibleWhen` potrebuje AND vice podminek | model podminek dnes umi jedno porovnani, viz `conditions.ts` | +| Log ticketu potrebuje redakci tajemstvi | `ticketStore.ts`, zapis `response` do trace | +| Data z pameti do Postgresu | cele `src/data/`, routy zustavaji | Dve veci k modelu podminek. `visibleWhen` u akce potrebuje spojit vic porovnani, zatim to jde jen vnorenim ve strome. Bud model podminek rozsirit o seznam diff --git a/documentation/10-runtime-a-kapacita.md b/documentation/10-runtime-a-kapacita.md index d94e7e0..7254aba 100644 --- a/documentation/10-runtime-a-kapacita.md +++ b/documentation/10-runtime-a-kapacita.md @@ -12,10 +12,10 @@ Zadani: 150 klientu, kazdy asi 5 systemu, z nich chodi radove desitky udalosti. To je 750 napojeni. "Desitky udalosti" ma dve cteni a **odpoved se mezi nimi podstatne lisi**, takze obe: -| Scenar | Desitky udalosti za | Udalosti/den | Kroku/den | Prumer | Spicka | -| ------ | ------------------- | ------------ | ---------- | ------- | -------- | -| A | den a system | 37 tisic | 375 tisic | 4 kr/s | 30-60/s | -| B | hodinu a system | 450 tisic | 4,5 mil | 52 kr/s | 200-400/s| +| Scenar | Desitky udalosti za | Udalosti/den | Kroku/den | Prumer | Spicka | +| ------ | ------------------- | ------------ | --------- | ------- | --------- | +| A | den a system | 37 tisic | 375 tisic | 4 kr/s | 30-60/s | +| B | hodinu a system | 450 tisic | 4,5 mil | 52 kr/s | 200-400/s | Pocitano s 10 kroky na udalost, coz je stredni automatizace. Spicka vychazi z toho, ze provoz je v osmihodinovem okne a uvnitr nerovnomerny, tedy radove @@ -27,12 +27,12 @@ je metrika kroku za sekundu ta jedina, podle ktere se da neco rict. ### Co je a co neni uzke misto -| Vec | Scenar A | Scenar B | -| ----------------------- | --------------- | ------------------------------ | -| Fronta v Postgresu | par procent | zvladne, ale s davkovym odberem| -| Soubezne HTTP volani | 15 soubezne | 90 soubezne, Node se nezapoti | -| **Zapis `run_step`** | 22 GB/mesic | **270 GB/mesic, nutne zkratit**| -| Limity cizich API | uzke misto | uzke misto | +| Vec | Scenar A | Scenar B | +| -------------------- | ----------- | ------------------------------- | +| Fronta v Postgresu | par procent | zvladne, ale s davkovym odberem | +| Soubezne HTTP volani | 15 soubezne | 90 soubezne, Node se nezapoti | +| **Zapis `run_step`** | 22 GB/mesic | **270 GB/mesic, nutne zkratit** | +| Limity cizich API | uzke misto | uzke misto | Fronta nad Postgresem s `FOR UPDATE SKIP LOCKED` uklidne obslouzi radove 200 az 500 uloh za sekundu na jednom uzlu, kdyz se odebira davkove. Scenar A @@ -56,20 +56,20 @@ ne do infrastruktury. Odhad velikosti: -| Scenar | Postgres | Workeri | Kde to bezi | -| ------ | ------------------ | --------------------------- | ----------- | -| A | 4 vCPU, 16 GB | 2 procesy, 50 soubezne | jeden stroj | +| Scenar | Postgres | Workeri | Kde to bezi | +| ------ | ---------------------- | ------------------------- | ----------- | +| A | 4 vCPU, 16 GB | 2 procesy, 50 soubezne | jeden stroj | | B | 8-16 vCPU, 32 GB, NVMe | 4-6 procesu, 100 soubezne | dva stroje | ## Moznosti u databaze -| Varianta | Verdikt pri 150 klientech | -| -------------------------------- | ------------------------------------------------ | -| Jedno DB, `tenant_id` ve sloupci | **ano, tohle** | +| Varianta | Verdikt pri 150 klientech | +| -------------------------------- | ---------------------------------------------------------------------- | +| Jedno DB, `tenant_id` ve sloupci | **ano, tohle** | | Schema na klienta | ne: 150 x 20 tabulek je 3000 tabulek, migrace se stanou nespolehlivymi | -| Databaze na klienta | ne, ale nechat si dvere otevrene | -| Partitionovani podle klienta | ne, oddily by byly velikostne nesouvisle | -| Partitionovani podle casu | **ano, u pripisovacich tabulek** | +| Databaze na klienta | ne, ale nechat si dvere otevrene | +| Partitionovani podle klienta | ne, oddily by byly velikostne nesouvisle | +| Partitionovani podle casu | **ano, u pripisovacich tabulek** | ### Dvere k oddelene databazi za par korun @@ -340,12 +340,12 @@ co by nekdo cetl - do uspesneho kroku se nikdo nechodi divat. **Retence** podle toho, kdo to cte: -| Data | Jak dlouho | -| --------------------- | ----------------- | -| Vstupy a vystupy kroku| 30 dni | -| Souhrn behu | 12 mesicu | -| Log ticketu | 90 dni | -| Audit | dele, dane pravni potrebou | +| Data | Jak dlouho | +| ---------------------- | -------------------------- | +| Vstupy a vystupy kroku | 30 dni | +| Souhrn behu | 12 mesicu | +| Log ticketu | 90 dni | +| Audit | dele, dane pravni potrebou | Cisla patri do nastaveni za klienta, protoze delsi retence je dobry duvod pro drazsi tarif. @@ -354,13 +354,13 @@ pro drazsi tarif. Ne CPU. Ctyri veci, a kazda odpovida na jinou otazku: -| Metrika | Odpovida na | -| ----------------------------------- | --------------------------------- | -| Hloubka fronty | stiha se to | -| **Vek nejstarsi pripravene ulohy** | je to zahlcene, nebo zaseknute | -| Kroku za sekundu, p95 za konektor | kde to drhne | -| Padle behy za hodinu, podil opakovani| co je rozbite | -| Podil kroku za klienta | kdo je hlucny soused | +| Metrika | Odpovida na | +| -------------------------------------- | ------------------------------ | +| Hloubka fronty | stiha se to | +| **Vek nejstarsi pripravene ulohy** | je to zahlcene, nebo zaseknute | +| Kroku za sekundu, p95 za konektor | kde to drhne | +| Padle behy za hodinu, podil opakovani | co je rozbite | +| Podil kroku za klienta | kdo je hlucny soused | | Zpozdeni inboxu (prijato az rozeslano) | stiha dispatcher | Bez veku nejstarsi ulohy se neda odlisit "je hodne prace" od "nic se nedeje", @@ -370,14 +370,14 @@ a to jsou dva uplne jine problemy se stejnou hloubkou fronty. Aby se to nemuselo rozhodovat dopredu. Do te doby plati navrh vyse. -| Signal | Co udelat | -| ---------------------------------------- | ------------------------------------- | -| Fronta zere nad 30 % CPU databaze | vetsi davky, pak fronta v Redisu (BullMQ) | -| Zapisy `run_step` prevalcuji IO | zkratit obsah, vzorkovat, velka tela do objektoveho uloziste | -| Jeden klient dela nad 30 % provozu | vlastni bazen workeru, pak vlastni databaze | -| Fronta roste kazdy den ve spicce | pridat workery, jsou bezstavove | -| Prevazuji chyby 429 z cizich API | limity za napojeni, pak vyjednat kvoty| -| Cekajici behy jdou do stovek tisic | oddelena fronta pro dlouha cekani, aby nezdrzovala bezny odber | +| Signal | Co udelat | +| ---------------------------------- | -------------------------------------------------------------- | +| Fronta zere nad 30 % CPU databaze | vetsi davky, pak fronta v Redisu (BullMQ) | +| Zapisy `run_step` prevalcuji IO | zkratit obsah, vzorkovat, velka tela do objektoveho uloziste | +| Jeden klient dela nad 30 % provozu | vlastni bazen workeru, pak vlastni databaze | +| Fronta roste kazdy den ve spicce | pridat workery, jsou bezstavove | +| Prevazuji chyby 429 z cizich API | limity za napojeni, pak vyjednat kvoty | +| Cekajici behy jdou do stovek tisic | oddelena fronta pro dlouha cekani, aby nezdrzovala bezny odber | ## Jeden container, dve role diff --git a/documentation/11-skripty-konektoru.md b/documentation/11-skripty-konektoru.md index 6c4d365..9c57b93 100644 --- a/documentation/11-skripty-konektoru.md +++ b/documentation/11-skripty-konektoru.md @@ -43,11 +43,11 @@ nejvyse jednou za sekundu, takze cteni katalogu neznamena stat na kazdy dotaz. Uprava tedy funguje trema cestami a vzdy stejne: -| Kudy | Co se stane | -| -------------------------------- | -------------------------------------------- | -| Editor v portalu | ulozi soubor, registr ho nacte hned | -| Rucni uprava souboru na serveru | registr si zmeny vsimne pri dalsim dotazu | -| Novy soubor ve slozce | objevi se jako nova operace v katalogu | +| Kudy | Co se stane | +| ------------------------------- | ----------------------------------------- | +| Editor v portalu | ulozi soubor, registr ho nacte hned | +| Rucni uprava souboru na serveru | registr si zmeny vsimne pri dalsim dotazu | +| Novy soubor ve slozce | objevi se jako nova operace v katalogu | `POST /api/dashboard/scripts/reload` to jen vynuti hned, bez cekani. @@ -83,17 +83,17 @@ export const manifest = { Parametr je pro vstup i vystup **tentyz tvar**. Kontrola je pak jedna funkce, ne dve skoro stejne, ktere by se casem rozesly. -| Klic | K cemu | -| ----------- | ------------------------------------------------------------- | -| `id` | pouziva se v sablonach jako `{{id}}`, jen pismena a podtrzitka | -| `label` | co vidi uzivatel v builderu | -| `type` | `string`, `number`, `boolean`, `date` | +| Klic | K cemu | +| ----------- | ------------------------------------------------------------------- | +| `id` | pouziva se v sablonach jako `{{id}}`, jen pismena a podtrzitka | +| `label` | co vidi uzivatel v builderu | +| `type` | `string`, `number`, `boolean`, `date` | | `required` | u vstupu: bez hodnoty se skript nespusti. U vystupu: musi ho vratit | -| `hint` | napoveda pod polem | -| `options` | vyber z hodnot, jina neprojde | -| `pattern` | dalsi kontrola regularnim vyrazem (jen `string`) | -| `multiline` | pole na vic radku (jen `string`) | -| `default` | dosadi se, kdyz hodnota chybi a parametr neni povinny | +| `hint` | napoveda pod polem | +| `options` | vyber z hodnot, jina neprojde | +| `pattern` | dalsi kontrola regularnim vyrazem (jen `string`) | +| `multiline` | pole na vic radku (jen `string`) | +| `default` | dosadi se, kdyz hodnota chybi a parametr neni povinny | Schema manifestu je `.strict()`. Preklep v nazvu klice (`outputFileds`) se ohlasi, ne tise ignoruje. @@ -127,50 +127,70 @@ a **zadne pristupove udaje**. export async function run(inputs, ctx) { /* ... */ } ``` -| Na kontextu | K cemu | -| ------------------ | ---------------------------------------------------------- | -| `ctx.http` | `get`, `post`, `patch`, `put`, `del` 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.idempotencyKey` | stabilni pres vsechny pokusy tehoz kroku | -| `ctx.fail` | koncova chyba, neopakuje se | -| `ctx.retry` | docasna chyba, ma smysl zkusit znovu | +| Na kontextu | K cemu | +| -------------------- | --------------------------------------------------------------------- | +| `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.idempotencyKey` | stabilni pres vsechny pokusy tehoz kroku | +| `ctx.fail` | koncova chyba, neopakuje se | +| `ctx.retry` | docasna chyba, ma smysl zkusit znovu | Adresu i autorizacni hlavicky doplnuje runtime podle napojeni. Skript rika `GET /issued-invoices/12` a nic vic. Duvod je v bodu 9 navrhu: kdyby skript znal tajemstvi, staci jeden `ctx.log` a je v logu, ktery vidi klient. +### Odeslani souboru + +`ctx.http.postForm` slozi `multipart/form-data`. Obsah souboru prichazi jako +**Base64 retezec**, protoze parametr skriptu je vzdy hodnota zapsatelna do +JSONu - uklada se do zaznamu behu a binarni data by se tam nevesla. + +```js +await ctx.http.postForm('/files', { + purpose: 'user_data', + file: { filename: 'faktura.pdf', base64: inputs.obsah, contentType: 'application/pdf' }, +}); +``` + +Prazdne polozky se vynechavaji: `null` prevedeny na text by cizi sluzba +dostala jako retezec "null". Hranici (boundary) dopisuje az `fetch` - kdyby si +ji skript nastavoval sam, chybela by v hlavicce a sluzba by telo neprecetla. + +Strop je `SCRIPT_MAX_UPLOAD_BYTES`, vychozi 10 MB. Zamerne nizsi nez u cizich +sluzeb: OpenAI zvladne stovky megabajtu, nas zaznam behu ne. + ### Pomocne funkce Cizi API vraci pokazde jinak. iDoklad pouziva velka pocatecni pismena a nekde obaluje odpoved do `Data`. Bez tehle sady by to kazdy skript resil znovu a jeden z nich by to resil spatne. -| Funkce | Co dela | -| -------------------------- | -------------------------------------------------- | -| `unwrap(body)` | rozbali `{ Data: x }` i `{ data: x }` | -| `pick(obj, ...names)` | prvni existujici pole bez ohledu na velikost pismen | -| `first(value)` | prvni prvek pole, nebo null | -| `text`, `num`, `bool`, `date` | prevody s fallbackem | -| `round(value, decimals)` | zaokrouhleni, uctuje se v halerich | -| `need(value, label)` | vrati hodnotu, nebo skonci citelnou chybou | +| Funkce | Co dela | +| ----------------------------- | --------------------------------------------------- | +| `unwrap(body)` | rozbali `{ Data: x }` i `{ data: x }` | +| `pick(obj, ...names)` | prvni existujici pole bez ohledu na velikost pismen | +| `first(value)` | prvni prvek pole, nebo null | +| `text`, `num`, `bool`, `date` | prevody s fallbackem | +| `round(value, decimals)` | zaokrouhleni, uctuje se v halerich | +| `need(value, label)` | vrati hodnotu, nebo skonci citelnou chybou | ## Chyby: opakovatelne a koncove Rozdeleni je to podstatne. Timeout nebo 503 ma smysl zkusit znovu, spatny vstup nebo 403 ne - opakovat koncovou chybu jen vypali kvotu u cizi sluzby. -| Druh | Kdy | Opakovat | -| ------------ | ------------------------------------------ | -------- | -| `not_found` | skript neexistuje | ne | -| `config` | chybi pristupove udaje, 401, 403 | ne | -| `validation` | vstup neprosel kontrolou | ne | -| `output` | skript nevratil deklarovany vystup | ne | -| `terminal` | 400, 404, jina koncova odpoved sluzby | ne | -| `retryable` | 408, 429, 5xx, chyba spojeni | ano | -| `timeout` | skript nedobehl v limitu | ano | -| `internal` | neocekavana vyjimka ve skriptu | ne | +| Druh | Kdy | Opakovat | +| ------------ | ------------------------------------- | -------- | +| `not_found` | skript neexistuje | ne | +| `config` | chybi pristupove udaje, 401, 403 | ne | +| `validation` | vstup neprosel kontrolou | ne | +| `output` | skript nevratil deklarovany vystup | ne | +| `terminal` | 400, 404, jina koncova odpoved sluzby | ne | +| `retryable` | 408, 429, 5xx, chyba spojeni | ano | +| `timeout` | skript nedobehl v limitu | ano | +| `internal` | neocekavana vyjimka ve skriptu | ne | Runner **nikdy nevyhodi vyjimku**. Vzdy vrati vysledek s `ok`, `outputs`, `logs`, `durationMs`, `httpCalls` a pripadne `error` vcetne `retryable`. Az bude @@ -184,14 +204,15 @@ podle **konektoru** firmy, popis je v [12-sluzby-a-konektory.md](12-sluzby-a-kon Z environment variables uz nechodi zadne pristupove udaje, jen provozni nastaveni: -| Promenna | K cemu | -| --------------------------- | --------------------------------------------------- | +| Promenna | K cemu | +| --------------------------- | ------------------------------------------------------ | | `SERVICES_BASE_URL` | zaklad adres, vychozi `https://services.csbot.cz/apps` | -| `_BASE_URL` | presmerovani jedne sluzby | -| `SCRIPTS_DIR` | jina slozka se skripty | -| `SCRIPT_TIMEOUT_MS` | vychozi strop na beh, 15000 | -| `SCRIPT_MAX_RESPONSE_BYTES` | strop na velikost odpovedi, 1000000 | -| `ALLOW_PRIVATE_TARGETS` | povoli volani na localhost, **jen pro lokalni vyvoj** | +| `_BASE_URL` | presmerovani jedne sluzby, napr. `OPENAI_BASE_URL` | +| `SCRIPTS_DIR` | jina slozka se skripty | +| `SCRIPT_TIMEOUT_MS` | vychozi strop na beh, 15000 | +| `SCRIPT_MAX_RESPONSE_BYTES` | strop na velikost odpovedi, 1000000 | +| `SCRIPT_MAX_UPLOAD_BYTES` | strop na odeslany soubor, 10000000 | +| `ALLOW_PRIVATE_TARGETS` | povoli volani na localhost, **jen pro lokalni vyvoj** | Co ktera sluzba vyzaduje, je v `credentials` u sluzby v `src/data/services.ts`. Hodnoty patri konektoru a zadavaji se v portalu. @@ -235,13 +256,13 @@ V portalu jsou operace se skriptem oznacene ikonou v katalogu sluzeb. ## API -| Metoda | Cesta | Kdo smi | -| ------ | ----------------------------------------- | ---------------- | -| GET | `/api/dashboard/scripts` | prihlaseny | -| GET | `/api/dashboard/scripts/:id` | prihlaseny | -| PUT | `/api/dashboard/scripts/:id` | spravce platformy | -| POST | `/api/dashboard/scripts/:id/test` | spravce platformy | -| POST | `/api/dashboard/scripts/reload` | spravce platformy | +| Metoda | Cesta | Kdo smi | +| ------ | --------------------------------- | ----------------- | +| GET | `/api/dashboard/scripts` | prihlaseny | +| GET | `/api/dashboard/scripts/:id` | prihlaseny | +| PUT | `/api/dashboard/scripts/:id` | spravce platformy | +| POST | `/api/dashboard/scripts/:id/test` | spravce platformy | +| POST | `/api/dashboard/scripts/reload` | spravce platformy | Cteni smi kazdy prihlaseny - builder potrebuje vedet, co skript umi. Uprava meni chovani vseho, co skript pouziva, takze to neni pravo vedle prava zakladat tickety. @@ -255,19 +276,26 @@ v `issues`. Zamerne: test, ktery volani predstira, nerekne nic o tom, jestli skript funguje. Portal na to upozornuje nad tlacitkem. +## Skripty pro dalsi realne sluzby + +RAYNET, CSOB, SAP Business One, PPL, Microsoft 365, Google Workspace, GA4, +Search Console, Google Ads, Sklik, Meta Ads, Prepis hovoru a OpenAI maji svoje +skripty taky. Co ktera sluzba potrebuje a co s ni umime, je +v [21-realne-sluzby.md](21-realne-sluzby.md). + ## Ukazkove skripty pro iDoklad Postavene proti skutecnemu API sluzby na `https://services.csbot.cz/apps/idoklad`. Kazdy ukazuje jiny vzor, at je z ceho vychazet. -| Skript | Vzor | -| --------------------------------- | --------------------------------------------- | -| `idoklad.get-issued-invoice` | jedno volani a prevod odpovedi | -| `idoklad.find-issued-invoice` | predvalidace: nenalezeno **neni** chyba | -| `idoklad.find-contact` | vlastni kontrola vstupu (aspon jedno z dvojice) | -| `idoklad.create-issued-invoice` | dve volani, vzor z `/default` a prepis jen znamych poli | -| `idoklad.register-payment` | akce, ktera meni stav, plus idempotence | -| `idoklad.send-invoice-email` | odpoved nic nevraci, vystup se sklada ze vstupu | +| Skript | Vzor | +| ------------------------------- | ------------------------------------------------------- | +| `idoklad.get-issued-invoice` | jedno volani a prevod odpovedi | +| `idoklad.find-issued-invoice` | predvalidace: nenalezeno **neni** chyba | +| `idoklad.find-contact` | vlastni kontrola vstupu (aspon jedno z dvojice) | +| `idoklad.create-issued-invoice` | dve volani, vzor z `/default` a prepis jen znamych poli | +| `idoklad.register-payment` | akce, ktera meni stav, plus idempotence | +| `idoklad.send-invoice-email` | odpoved nic nevraci, vystup se sklada ze vstupu | ### Proc se u zakladani bere vzor z `/default` @@ -300,11 +328,11 @@ Katalog, builder i stranka skriptu si ho vezmou samy. Nic se nerestartuje. ## Co chybi -| Chybi | Poznamka | -| ---------------------------- | --------------------------------------------------- | -| Skripty od zakazniku | potrebuji sandbox a vlastni vlakno, viz vyse | -| 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 | -| Metriky pro widgety | manifest to zatim nezna, viz bod 4 navrhu | -| Ulozeni uprav mimo git | portal zapisuje do souboru v containeru, redeploy je vrati | +| Chybi | Poznamka | +| ---------------------- | ---------------------------------------------------------- | +| Skripty od zakazniku | potrebuji sandbox a vlastni vlakno, viz vyse | +| 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 | +| Metriky pro widgety | manifest to zatim nezna, viz bod 4 navrhu | +| Ulozeni uprav mimo git | portal zapisuje do souboru v containeru, redeploy je vrati | diff --git a/documentation/12-sluzby-a-konektory.md b/documentation/12-sluzby-a-konektory.md index fc0ac79..f426107 100644 --- a/documentation/12-sluzby-a-konektory.md +++ b/documentation/12-sluzby-a-konektory.md @@ -8,11 +8,11 @@ v [11-skripty-konektoru.md](11-skripty-konektoru.md). Slovo "konektor" driv v kodu znamenalo katalog toho, co umime. Ted znamena napojeni jedne firmy. Rozdeleni je takove: -| Vrstva | Co to je | Kdo to vlastni | -| ----------- | --------------------------------------------------- | -------------- | -| **Sluzba** | ze iDoklad existuje, co umi a co potrebuje k napojeni | my | -| **Skript** | kod, ktery jednu operaci sluzby opravdu vykona | my | -| **Konektor**| ucet firmy vcetne jejich pristupovych udaju | firma | +| Vrstva | Co to je | Kdo to vlastni | +| ------------ | ----------------------------------------------------- | -------------- | +| **Sluzba** | ze iDoklad existuje, co umi a co potrebuje k napojeni | my | +| **Skript** | kod, ktery jednu operaci sluzby opravdu vykona | my | +| **Konektor** | ucet firmy vcetne jejich pristupovych udaju | firma | Sluzba tedy rika "iDoklad chce hlavicky `X-ClientId` a `X-ClientSecret`", konektor rika "a tohle jsou nase". @@ -31,6 +31,37 @@ Sluzba iDoklad definujeme my Krok automatizace pak nese oboji: **kterou operaci** (`serviceId` plus `operationId`) a **pod cim ji zavolat** (`connectorId`). +## Kde sluzba bezi + +Vetsina sluzeb jsou **nase aplikace** za `services.csbot.cz/apps`. Adresa se +sklada ze `SERVICES_BASE_URL` a z `appId`, verejna domena se nikdy nehardcoduje +do logiky (AGENTS.md). + +Ktera sluzba katalogu stoji na ktere bezici aplikaci, je +v [21-realne-sluzby.md](21-realne-sluzby.md). + +Vyjimka je sluzba, ktera **nebezi u nas** - zatim OpenAI. Ta ma misto `appId` +nepovinne pole `baseUrl` s absolutni adresou, protoze cizi domenou nehneme +a skladat ji ze `SERVICES_BASE_URL` by nedavalo smysl. + +Adresu lze prepsat na dvou urovnich: + +| Kudy | Pro koho plati | K cemu | +| ------------------- | -------------- | --------------------------------- | +| `_BASE_URL` | cela instance | brana, napodobenina pri vyvoji | +| adresa u konektoru | jedna firma | vlastni instance nebo brana firmy | + +Nazev promenne vznikne z ID sluzby velkymi pismeny, pomlcka je podtrzitko: +`openai` je `OPENAI_BASE_URL`, `sap-bo` je `SAP_BO_BASE_URL`. + +Treti pripad je sluzba, ktera **nejde pres HTTP**: e-mail. Ma +`transport: 'smtp'`, adresu serveru nese konektor mezi udaji a operaci nevykona +skript, ale vnitrni krok. Podrobnosti v [21-realne-sluzby.md](21-realne-sluzby.md). + +Treti pripad je sluzba, ktera **nejde pres HTTP**: e-mail. Ma +`transport: 'smtp'`, adresu serveru nese konektor mezi udaji a operaci nevykona +skript, ale vnitrni krok. Podrobnosti v [21-realne-sluzby.md](21-realne-sluzby.md). + ## Pristupove udaje patri konektoru, ne prostredi Driv se cetly z environment variables. To bylo spatne: cela instance by mela @@ -50,7 +81,13 @@ Pravidla, ktera se u toho nesmi porusit: - **Volani vzdy dela server.** Z prohlizece by to znamenalo poslat pristupove udaje do prohlizece, a stejne by to neproslo - sluzby kontroluji IP. - **Redakce v logu.** Nez cokoliv skonci v logu nebo v chybe, projde nahradou - znamych tajnych hodnot za hvezdicky. + znamych tajnych hodnot za hvezdicky. Redaguje se **cela hlavicka i sama + hodnota**: u `Authorization: Bearer ` vraci cizi sluzby v chybe jednou + jedno a jednou druhe. +- **Predpona hlavicky patri runtime, ne uzivateli.** Pole udaju smi mit + `prefix` (typicky `Bearer `). Uzivatel vlepi klic tak, jak ho dostal, a slovo + pred nim dopise portal. Kdyby si ho mel psat sam, byl by to zdroj chyb, ktery + neni videt ani zpetne - hodnota se z API nevraci. - **Zmena udaju rusi predchozi overeni.** Konektor se vrati na `untested`, jinak by zelena znacka lhala. @@ -59,17 +96,17 @@ Pravidla, ktera se u toho nesmi porusit: Kategorie `obecne`, priznak `general: true`. Jsou dostupne vsem, nepotrebuji konektor a viditelnost se u nich neresi: -| Sluzba | K cemu | -| ----------------- | ------------------------------------------ | -| Webhook | prijem pozadavku zvenci | -| Planovac | spousteni podle casu | -| Rucni spusteni | tlacitko | -| Webovy formular | odeslani formulare | -| Tickety | servicedesk: zalozit, priradit, komentovat | -| Transformace dat | premapovani a cisteni mezi kroky | -| HTTP pozadavek | zavolani API, ktere vlastni sluzbu nema | -| Pauza | cekani | -| Zapis do logu | zaznam pro ladeni | +| Sluzba | K cemu | +| ---------------- | ------------------------------------------ | +| Webhook | prijem pozadavku zvenci | +| Planovac | spousteni podle casu | +| Rucni spusteni | tlacitko | +| Webovy formular | odeslani formulare | +| Tickety | servicedesk: zalozit, priradit, komentovat | +| Transformace dat | premapovani a cisteni mezi kroky | +| HTTP pozadavek | zavolani API, ktere vlastni sluzbu nema | +| Pauza | cekani | +| Zapis do logu | zaznam pro ladeni | Duvod, proc je to zvlast kategorie a ne jen priznak: v builderu i v katalogu je chce clovek videt pohromade a hned. Nejsou to integrace, jsou to stavebni @@ -85,11 +122,11 @@ interface ServiceVisibility { } ``` -| Mode | Kdo vidi | -| ------------ | ---------------------------------------------- | -| `everyone` | vsichni prihlaseni | -| `restricted` | uvedene firmy a jmenovite uvedeni lide | -| `admin` | jen spravce platformy | +| Mode | Kdo vidi | +| ------------ | -------------------------------------- | +| `everyone` | vsichni prihlaseni | +| `restricted` | uvedene firmy a jmenovite uvedeni lide | +| `admin` | jen spravce platformy | Spravce platformy vidi vzdy vsechno. Obecne sluzby vidi vzdy vsichni. @@ -108,12 +145,12 @@ sluzby. Sluzba proto ma jen `available` nebo `planned` a portal si stav dopocita: -| Co uzivatel vidi | Kdy | -| ------------------ | -------------------------------------------------- | -| Napojeno | obecna sluzba, nebo firma ma aspon jeden konektor | -| Muzete napojit | sluzbu umime, firma konektor nema | -| Na roadmape | `status: 'planned'` | -| nic | sluzbu uzivatel nevidi, v odpovedi neni | +| Co uzivatel vidi | Kdy | +| ---------------- | ------------------------------------------------- | +| Napojeno | obecna sluzba, nebo firma ma aspon jeden konektor | +| Muzete napojit | sluzbu umime, firma konektor nema | +| Na roadmape | `status: 'planned'` | +| nic | sluzbu uzivatel nevidi, v odpovedi neni | `GET /api/dashboard/connectors/services` proto vraci `connectorCount`. @@ -132,8 +169,8 @@ jineho zmenilo. ## Validace stromu -| Situace | Vysledek | -| ------------------------------------------- | --------- | +| Situace | Vysledek | +| -------------------------------------------- | --------- | | Krok odkazuje na neexistujici sluzbu/operaci | chyba 400 | | Krok odkazuje na cizi konektor | chyba 400 | | Konektor patri jine sluzbe nez krok | chyba 400 | @@ -151,6 +188,16 @@ autorizaci. iDoklad ma `/account/agenda`. Kdyz ho sluzba nema, overi se jen `/health`. Odpoved to v `checked` rekne nahlas - test, ktery projde i se spatnymi udaji, by uzivateli rikal nepravdu. +Tak je na tom Prepis hovoru: jeho jedine volani je prepis, ktery se uctuje. + +`verifyPath` smi nest i query (`/company?limit=1`), aby overeni nestahovalo +cely seznam. Do hlasky se query nedava, odrizne se - muze v ni byt tajemstvi. + +U sluzby s `transport: 'smtp'` se `verifyPath` nepouziva vubec: overeni se +**prihlasi na posmovni server** a nic neodesle. + +U sluzby s `transport: 'smtp'` se `verifyPath` nepouziva vubec: overeni se +**prihlasi na posmovni server** a nic neodesle. Neuspesne overeni **neni chyba API**. Vraci se 200 s `ok: false` a popisem, protoze vysledek "nefunguje to" je platna odpoved na otazku "funguje to?". @@ -246,18 +293,18 @@ Cte se zvlast pres `/connectors/:id/checks`. ## API -| Metoda | Cesta | Popis | -| ------ | ----------------------------------------- | ---------------------------- | -| GET | `/api/dashboard/services` | katalog pro builder | -| GET | `/api/dashboard/connectors/services` | katalog ocima firmy | -| GET | `/api/dashboard/connectors` | konektory firmy | -| POST | `/api/dashboard/connectors` | zalozit | -| GET | `/api/dashboard/connectors/:id` | detail | -| PATCH | `/api/dashboard/connectors/:id` | upravit | -| DELETE | `/api/dashboard/connectors/:id` | smazat | -| POST | `/api/dashboard/connectors/:id/test` | overit napojeni | -| GET | `/api/dashboard/connectors/:id/checks` | poslednich pet overeni | -| GET | `/api/dashboard/connectors/egress-ip` | odchozi IP adresa portalu | +| Metoda | Cesta | Popis | +| ------ | -------------------------------------- | ------------------------- | +| GET | `/api/dashboard/services` | katalog pro builder | +| GET | `/api/dashboard/connectors/services` | katalog ocima firmy | +| GET | `/api/dashboard/connectors` | konektory firmy | +| POST | `/api/dashboard/connectors` | zalozit | +| GET | `/api/dashboard/connectors/:id` | detail | +| PATCH | `/api/dashboard/connectors/:id` | upravit | +| DELETE | `/api/dashboard/connectors/:id` | smazat | +| POST | `/api/dashboard/connectors/:id/test` | overit napojeni | +| GET | `/api/dashboard/connectors/:id/checks` | poslednich pet overeni | +| GET | `/api/dashboard/connectors/egress-ip` | odchozi IP adresa portalu | ## Stranky portalu @@ -270,7 +317,8 @@ Cte se zvlast pres `/connectors/:id/checks`. ## Jak pridat sluzbu 1. Zaznam do `services` v `src/data/services.ts`: kategorie, ikona, `general`, - `appId`, `visibility`, `credentials`, pripadne `verifyPath`. + `appId` (nebo `baseUrl` u cizi sluzby), `visibility`, `credentials`, + pripadne `verifyPath`. 2. Pokud pouziva novou ikonu, doplnit klic do `web/src/lib/serviceIcons.ts`. 3. Skripty operaci do `scripts/..js`, viz [11-skripty-konektoru.md](11-skripty-konektoru.md). @@ -281,27 +329,27 @@ Katalog, builder, stranka Sluzby i zakladani konektoru si ji vezmou samy. Kdo se v kodu orientoval podle stareho pojmenovani: -| Driv | Ted | -| ----------------------------- | ------------------------------ | -| `src/data/connectors.ts` | `src/data/services.ts` | +| Driv | Ted | +| --------------------------------- | ----------------------------- | +| `src/data/connectors.ts` | `src/data/services.ts` | | `Connector`, `ConnectorOperation` | `Service`, `ServiceOperation` | -| `connectorCategories` | `serviceCategories` | -| `findConnector` | `findService` | -| `FlowStep.connectorId` | `FlowStep.serviceId` | -| `GET /api/dashboard/connectors` | `GET /api/dashboard/services` | -| `web/src/lib/connectorIcons.ts` | `web/src/lib/serviceIcons.ts` | -| stranka Konektory (katalog) | stranka Sluzby | +| `connectorCategories` | `serviceCategories` | +| `findConnector` | `findService` | +| `FlowStep.connectorId` | `FlowStep.serviceId` | +| `GET /api/dashboard/connectors` | `GET /api/dashboard/services` | +| `web/src/lib/connectorIcons.ts` | `web/src/lib/serviceIcons.ts` | +| stranka Konektory (katalog) | stranka Sluzby | `Connector` a `connectorId` v kodu ted znamenaji napojeni firmy, tedy to, co tim mysli i uzivatel. ## Co chybi -| Chybi | Poznamka | -| ---------------------------- | ----------------------------------------------------- | -| Databaze a sifrovani udaju | hodnoty jsou v pameti procesu, restart je smaze | +| Chybi | Poznamka | +| -------------------------------- | ------------------------------------------------- | +| Databaze a sifrovani udaju | hodnoty jsou v pameti procesu, restart je smaze | | Nastaveni viditelnosti z portalu | `visibility` jde zmenit jen v kodu | -| Zamek pri soubeznem overovani| dva testy tehoz konektoru si prepisou stav | -| Historie zmen konektoru | kdo kdy prepsal udaje, se nikde neuklada | -| OAuth toky | zatim jen hlavicky, obnovovani tokenu resi sluzba | -| Vyber konektoru v builderu | krok uz `connectorId` nese, UI ho zatim nenabizi | +| Zamek pri soubeznem overovani | dva testy tehoz konektoru si prepisou stav | +| Historie zmen konektoru | kdo kdy prepsal udaje, se nikde neuklada | +| OAuth toky | zatim jen hlavicky, obnovovani tokenu resi sluzba | +| Vyber konektoru v builderu | krok uz `connectorId` nese, UI ho zatim nenabizi | diff --git a/documentation/13-transformace-dat.md b/documentation/13-transformace-dat.md index 697ee49..4fa5603 100644 --- a/documentation/13-transformace-dat.md +++ b/documentation/13-transformace-dat.md @@ -19,10 +19,10 @@ Nova objednavka (e-shop) -> objekt "order" Dosud si kroky predavaly jen jednotlive hodnoty. Pribyly dva typy parametru: -| Typ | Co to je | Do sablony | Podminka | -| -------- | --------------------------- | ---------- | ----------------- | -| `object` | cely objekt | ne | je / neni prazdny | -| `list` | seznam | ne | je / neni prazdny | +| Typ | Co to je | Do sablony | Podminka | +| -------- | ----------- | ---------- | ----------------- | +| `object` | cely objekt | ne | je / neni prazdny | +| `list` | seznam | ne | je / neni prazdny | **Do sablony se nedosazuji jako celek.** `{{order}}` v textu by znamenalo vlozit do vety kus JSONu, coz nikdo nechce. Struktura se predava **jako celek** @@ -43,11 +43,11 @@ hranice patri do kontroly parametru, ne az do uklidu databaze. ## Tri zpusoby, jak prevest data -| Zpusob | Kdy se hodi | -| --- | --- | -| **Pravidla** (`transform/map-fields`) | par poli, prevody hodnot, klikatelne | -| **Sablona JSON** (`transform/to-json`) | hlavni prace je ve **tvaru** vysledku | -| **Skript firmy** (`transform/custom`) | slozitejsi prevod, kde je kod citelnejsi nez dvacet pravidel | +| Zpusob | Kdy se hodi | +| -------------------------------------- | ------------------------------------------------------------ | +| **Pravidla** (`transform/map-fields`) | par poli, prevody hodnot, klikatelne | +| **Sablona JSON** (`transform/to-json`) | hlavni prace je ve **tvaru** vysledku | +| **Skript firmy** (`transform/custom`) | slozitejsi prevod, kde je kod citelnejsi nez dvacet pravidel | Skript je JS: dostane `input`, vrati objekt. Nevola nic ven a v logu je u nej **vstup i vystup**, takze kdyz vysledek nesedi, neni potreba hadat, co do @@ -75,10 +75,10 @@ nemuze se v nem udelat preklep v zavorce. Obe moznosti stoji na tom samem enginu v `src/scripts/mapping.ts`. Volba je o tom, cehoz je vic: -| Rezim | Kdy | Skript | -| ------------------------- | -------------------------------------- | ----------------------- | -| **Pravidla** (pole na pole) | hlavni prace je v prevodech hodnot | `transform.map-fields` | -| **Sablona JSON** | hlavni prace je ve tvaru struktury | `transform.to-json` | +| Rezim | Kdy | Skript | +| --------------------------- | ---------------------------------- | ---------------------- | +| **Pravidla** (pole na pole) | hlavni prace je v prevodech hodnot | `transform.map-fields` | +| **Sablona JSON** | hlavni prace je ve tvaru struktury | `transform.to-json` | ### Rezim 1: pravidla @@ -115,15 +115,15 @@ Jedno pravidlo je "vezmi tuhle cestu, projed prevody, uloz sem". Prevod `map` je to, bez ceho by priklad nesel dokoncit. Bez nej by slo prevest hlavicku dokladu, ale ne seznam polozek, a doklad by byl na nulu. -| Klic | K cemu | -| ------------- | --------------------------------------------------------- | -| `to` | kam se ulozi. Tecka znamena zanoreni: `partner.id` | -| `from` | cesta ve zdroji. `items.0.name` i `items[0].name` | -| `value` | pevna hodnota, kdyz `from` chybi | -| `transforms` | prevody v uvedenem poradi | -| `fallback` | pouzije se, kdyz je vysledek prazdny | -| `omitIfEmpty` | prazdny vysledek se do vystupu vubec nezapise | -| `required` | prazdny vysledek je chyba | +| Klic | K cemu | +| ------------- | -------------------------------------------------- | +| `to` | kam se ulozi. Tecka znamena zanoreni: `partner.id` | +| `from` | cesta ve zdroji. `items.0.name` i `items[0].name` | +| `value` | pevna hodnota, kdyz `from` chybi | +| `transforms` | prevody v uvedenem poradi | +| `fallback` | pouzije se, kdyz je vysledek prazdny | +| `omitIfEmpty` | prazdny vysledek se do vystupu vubec nezapise | +| `required` | prazdny vysledek je chyba | `required` neni formalita. Doklad bez `partnerId` iDoklad odmitne, a je lepsi to poznat na kroku transformace s nazvem pole, nez z odpovedi 400 od iDokladu. @@ -154,20 +154,20 @@ jako text, protoze jinak to nedava smysl. ## Prevody -| Prevod | Co dela | -| ----------------------------- | ---------------------------------------- | -| `trim`, `lower`, `upper` | uprava textu | -| `string`, `number`, `boolean` | zmena typu | -| `date` (`iso` / `day`) | datum, `day` je jen `YYYY-MM-DD` | -| `round` (`decimals`) | zaokrouhleni | -| `multiply`, `add` (`by`) | pocty, napriklad prevod na cenu s DPH | -| `default` (`value`) | vyplneni prazdne hodnoty | -| `replace` (`find`, `with`) | nahrazeni textu | -| `slice` (`start`, `end`) | cast textu nebo seznamu | -| `split`, `join` (`separator`) | text na seznam a zpatky | -| `sum` (`path`) | soucet pres seznam | -| `count` | pocet polozek | -| `map` (`rules`) | kazdou polozku seznamu podle vlastnich pravidel | +| Prevod | Co dela | +| ----------------------------- | ----------------------------------------------- | +| `trim`, `lower`, `upper` | uprava textu | +| `string`, `number`, `boolean` | zmena typu | +| `date` (`iso` / `day`) | datum, `day` je jen `YYYY-MM-DD` | +| `round` (`decimals`) | zaokrouhleni | +| `multiply`, `add` (`by`) | pocty, napriklad prevod na cenu s DPH | +| `default` (`value`) | vyplneni prazdne hodnoty | +| `replace` (`find`, `with`) | nahrazeni textu | +| `slice` (`start`, `end`) | cast textu nebo seznamu | +| `split`, `join` (`separator`) | text na seznam a zpatky | +| `sum` (`path`) | soucet pres seznam | +| `count` | pocet polozek | +| `map` (`rules`) | kazdou polozku seznamu podle vlastnich pravidel | Sada je zamerne **uzavrena**. Volny vyraz by z transformace udelal dalsi jazyk k ladeni a hlavne by to byl kod bez sandboxu na miste, kde ho nikdo neceka. @@ -175,11 +175,11 @@ Kdo potrebuje vic, napise skript. ## Kontrola -| Kdy | Co se overi | Vysledek | -| ----------------- | ---------------------------------------------------- | --------- | -| Pri psani | JSON se parsuje, chyba se ukaze hned pod polem | jen v UI | -| Pri ulozeni stromu| JSON a tvar pravidel (`to`, `from`/`value`, `op`) | nedodelek | -| Pri behu | typy, `required`, prazdne hodnoty, hloubka zanoreni | chyba behu| +| Kdy | Co se overi | Vysledek | +| ------------------ | --------------------------------------------------- | ---------- | +| Pri psani | JSON se parsuje, chyba se ukaze hned pod polem | jen v UI | +| Pri ulozeni stromu | JSON a tvar pravidel (`to`, `from`/`value`, `op`) | nedodelek | +| Pri behu | typy, `required`, prazdne hodnoty, hloubka zanoreni | chyba behu | Rozbite pravidlo je **nedodelek**, ne chyba ukladani. Rozdelana prace se nezahazuje, jen automatizace nepujde zapnout. Stejny rezim jako u ostatnich @@ -229,9 +229,9 @@ z transformace ho prepise jen tam, kde neco rika. ## Co chybi -| Chybi | Poznamka | -| -------------------- | ------------------------------------------- | -| Nahled transformace | pravidla jde zkusit jen pres test skriptu | +| Chybi | Poznamka | +| ------------------- | ----------------------------------------- | +| Nahled transformace | pravidla jde zkusit jen pres test skriptu | Hotovo od 2026-08-20: vnorena pravidla se klikaji (prevod **Za kazdou polozku seznamu**), cesty se nabizeji z ukazky skutecneho tela a strom se vykonava pres diff --git a/documentation/14-databaze.md b/documentation/14-databaze.md index cc89a95..5dafe0d 100644 --- a/documentation/14-databaze.md +++ b/documentation/14-databaze.md @@ -11,11 +11,11 @@ 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. -| 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 | +| 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 | Rezim `file` je pro mockup. Filesystem containeru je docasny, takze soubor prezije restart procesu i containeru, ale nove nasazeni ho smaze. Je to @@ -37,12 +37,12 @@ Psat do rozbiteho schematu je horsi nez psat do souboru. ## Promenne -| Promenna | K cemu | -| ------------------- | ---------------------------------------------------------- | -| `DATABASE_URL` | `postgres://uzivatel:heslo@host:5432/csbot` | -| `SECRETS_KEY` | klic pro sifrovani pristupovych udaju, **secret** | -| `DATABASE_POOL_MAX` | kolik spojeni si vezme jedna instance, vychozi 10 | -| `DATABASE_SSL` | `true` u spravovanych databazi, ktere vyzaduji TLS | +| Promenna | K cemu | +| ------------------- | ------------------------------------------------------------------------------- | +| `DATABASE_URL` | `postgres://uzivatel:heslo@host:5432/csbot` | +| `SECRETS_KEY` | klic pro sifrovani pristupovych udaju, **secret** | +| `DATABASE_POOL_MAX` | kolik spojeni si vezme jedna instance, vychozi 10 | +| `DATABASE_SSL` | `true` u spravovanych databazi, ktere vyzaduji TLS | | `DATA_DIR` | slozka pro JSON mimo databazi, vychozi `./data`. Prazdna hodnota vypne i soubor | `SECRETS_KEY` ma byt nahodny retezec, ne heslo: @@ -81,13 +81,13 @@ Bez promennych `npm run dev` funguje dal, jen se uklada do `./data`. Ukladani je **jeden kod pro pamet i soubor**, lisi se jen tim, kam se zapisuje. Kdyby to byly dve implementace, jedna by se casem opravila a druha ne. -| Vlastnost | Jak a proc | -| ---------------- | ----------------------------------------------------------- | -| Atomicky zapis | nejdriv `.tmp`, pak prejmenovani. Pad uprostred zapisu jinak nechá polovicni JSON, ktery se pri startu nenacte | -| 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 | -| Sifrovani | tajne hodnoty jsou v souboru zasifrovane, plaintext nikdy | +| Vlastnost | Jak a proc | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------- | +| Atomicky zapis | nejdriv `.tmp`, pak prejmenovani. Pad uprostred zapisu jinak nechá polovicni JSON, ktery se pri startu nenacte | +| 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 | +| Sifrovani | tajne hodnoty jsou v souboru zasifrovane, plaintext nikdy | ### Klic mimo databazi @@ -145,12 +145,12 @@ zvlast a nic to nestoji. `connectors` (migrace `001_connectors.sql`): -| Sloupec | Poznamka | -| ------------- | ------------------------------------------------- | -| `tenant_id` | povinne, index zacina jim | -| `service_id` | odkaz do katalogu v kodu, ne do tabulky | -| `secrets` | JSONB se sifrovanymi hodnotami, nikdy plaintext | -| `is_default` | jediny vychozi na firmu a sluzbu, hlida index | +| Sloupec | Poznamka | +| ------------ | ----------------------------------------------- | +| `tenant_id` | povinne, index zacina jim | +| `service_id` | odkaz do katalogu v kodu, ne do tabulky | +| `secrets` | JSONB se sifrovanymi hodnotami, nikdy plaintext | +| `is_default` | jediny vychozi na firmu a sluzbu, hlida index | **Sluzby v databazi nejsou.** Jsou to definice, ktere delame my, a repo je u nich zdroj pravdy kvuli code review a historii v gitu. Rucne upraveny radek v produkci @@ -202,29 +202,29 @@ Ta pak potrebuje prime spojeni mimo PgBouncer. Proti Postgresu 16 v kontejneru: -| Co | Vysledek | -| ----------------------------------------------------- | -------- | -| Migrace projedou a zapisou se do `schema_migrations` | ano | -| Udaje jsou v tabulce sifrovane, plaintext nikde | ano | -| Konektor prezije restart procesu | ano | -| Se spravnym klicem se udaje rozsifruji | ano | -| Se spatnym klicem se chovaji jako nevyplnene a loguje se | ano | -| `PATCH` bez tajneho pole tajne pole nesmaze | ano | -| Prepnuti vychoziho konektoru | ano | -| Smazani vychoziho preda priznak zbylemu | ano | -| Bez `DATABASE_URL` jede souborovy rezim a rekne to | ano | -| Konektor v souborovem rezimu prezije restart procesu | ano | -| Tajne hodnoty jsou v JSONu sifrovane, plaintext nikde | ano | -| U databaze se klic vedle dat nevygeneruje | ano | +| Co | Vysledek | +| -------------------------------------------------------- | -------- | +| Migrace projedou a zapisou se do `schema_migrations` | ano | +| Udaje jsou v tabulce sifrovane, plaintext nikde | ano | +| Konektor prezije restart procesu | ano | +| Se spravnym klicem se udaje rozsifruji | ano | +| Se spatnym klicem se chovaji jako nevyplnene a loguje se | ano | +| `PATCH` bez tajneho pole tajne pole nesmaze | ano | +| Prepnuti vychoziho konektoru | ano | +| Smazani vychoziho preda priznak zbylemu | ano | +| Bez `DATABASE_URL` jede souborovy rezim a rekne to | ano | +| Konektor v souborovem rezimu prezije restart procesu | ano | +| Tajne hodnoty jsou v JSONu sifrovane, plaintext nikde | ano | +| U databaze se klic vedle dat nevygeneruje | ano | ## Co chybi -| 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 | -| 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 | +| 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 | +| 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 | diff --git a/documentation/15-rejstrik-funkci.md b/documentation/15-rejstrik-funkci.md index 117a645..35e0e96 100644 --- a/documentation/15-rejstrik-funkci.md +++ b/documentation/15-rejstrik-funkci.md @@ -12,85 +12,92 @@ a kdy to použít. Rozhoduje se na **jednom místě**, viz [14-databaze.md](14-databaze.md). Volající nikdy nezjišťuje, jestli běží Postgres, soubor, nebo pamět. -| Co | Kde | K čemu | -| --- | --- | --- | -| `defineStore(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. | -| `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í). | -| `isVisible(entity, options)` | `src/data/store/types.ts` | Vidí volající tenhle záznam? Prázdný seznam firem znamená "nic", ne "vše". | -| `nowIso()` | `src/data/store/types.ts` | Časová značka. Ať se nepíše `new Date().toISOString()` na třiceti místech. | -| `memorySnapshot` / `fileSnapshot` | `src/data/snapshot.ts` | Nižší vrstva pod `createLocalStore`: atomický zápis JSONu s debounce. Přímo se nepoužívá. | -| `db()`, `query`, `queryOne`, `transaction` | `src/db/pool.ts` | Postgres. `dbFor(tenantId)` je připravený šev pro rozdělení na víc databází. | -| `seal`, `open`, `sealAll`, `openAll` | `src/db/secretBox.ts` | Šifrování přístupových údajů konektorů (AES-256-GCM). Nic tajného se neukládá jinak. | -| `runMigrations()` | `src/db/migrate.ts` | Migrace pod zámkem, jeden soubor = jedna transakce. | +| Co | Kde | K čemu | +| ------------------------------------------ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `defineStore(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. | +| `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í). | +| `isVisible(entity, options)` | `src/data/store/types.ts` | Vidí volající tenhle záznam? Prázdný seznam firem znamená "nic", ne "vše". | +| `nowIso()` | `src/data/store/types.ts` | Časová značka. Ať se nepíše `new Date().toISOString()` na třiceti místech. | +| `memorySnapshot` / `fileSnapshot` | `src/data/snapshot.ts` | Nižší vrstva pod `createLocalStore`: atomický zápis JSONu s debounce. Přímo se nepoužívá. | +| `db()`, `query`, `queryOne`, `transaction` | `src/db/pool.ts` | Postgres. `dbFor(tenantId)` je připravený šev pro rozdělení na víc databází. | +| `seal`, `open`, `sealAll`, `openAll` | `src/db/secretBox.ts` | Šifrování přístupových údajů konektorů (AES-256-GCM). Nic tajného se neukládá jinak. | +| `runMigrations()` | `src/db/migrate.ts` | Migrace pod zámkem, jeden soubor = jedna transakce. | ## Entity a práva (server) -| Co | Kde | K čemu | -| --- | --- | --- | -| `crudRouter(options)` | `src/routes/crud.ts` | Celý CRUD nad jednou entitou: seznam, detail, vytvoření, úprava, mazání, právo, audit. Nová entita v nastavení = jeden `crudRouter`, ne pět handlerů. | -| `readScope(req)` | `src/routes/crud.ts` | Ze které firmy smí request číst. Povinný argument všech `list` volání. | -| `accessFor(user, tenantId?)` | `src/data/access.ts` | Co uživatel smí: práva, záložky, výchozí firma. Klient si nic nedovozuje sám. | -| `permissionsOf(user, tenantId)` | `src/data/permissions.ts` | Efektivní práva z rolí. Pětisekundová cache, `invalidatePermissions()` po zápisu. | -| `hasPermission(...)` | `src/data/permissions.ts` | Jedna kontrola. Používá ji `crudRouter` i ruční handlery. | -| `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. | -| `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. | -| `findPersonByExternalId(...)` | `src/data/people.ts` | Řešitel podle ID z cizí aplikace, například voicebotId. | -| `notify(input)` | `src/data/notifications.ts` | Upozorní člověka. Nečeká se a nevyhazuje chyby, stejně jako audit. | -| `runFlow(steps, context, options)` | `src/runtime/executor.ts` | Vykoná strom kroků. Nikdy nevyhodí výjimku, chyba je výsledek. Používá to akce na ticketu i webhook, aby se strom choval všude stejně. | -| `widgetCatalog(tenantIds, userId)` | `src/data/widgets.ts` | Jediná definice toho, co jde položit na dashboard. Používá ji nabídka i kontrola ukládaného rozložení. | -| `intakeEvent(input)` | `src/data/ticketStore.ts` | Přijme událost zvenku: podle externího ID buď založí ticket, nebo ji navěsí na existující. Jediná cesta, kterou se událost stává ticketem. | -| `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. | -| `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. | +| Co | Kde | K čemu | +| ---------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `crudRouter(options)` | `src/routes/crud.ts` | Celý CRUD nad jednou entitou: seznam, detail, vytvoření, úprava, mazání, právo, audit. Nová entita v nastavení = jeden `crudRouter`, ne pět handlerů. | +| `readScope(req)` | `src/routes/crud.ts` | Ze které firmy smí request číst. Povinný argument všech `list` volání. | +| `accessFor(user, tenantId?)` | `src/data/access.ts` | Co uživatel smí: práva, záložky, výchozí firma. Klient si nic nedovozuje sám. | +| `permissionsOf(user, tenantId)` | `src/data/permissions.ts` | Efektivní práva z rolí. Pětisekundová cache, `invalidatePermissions()` po zápisu. | +| `hasPermission(...)` | `src/data/permissions.ts` | Jedna kontrola. Používá ji `crudRouter` i ruční handlery. | +| `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. | +| `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. | +| `findPersonByExternalId(...)` | `src/data/people.ts` | Řešitel podle ID z cizí aplikace, například voicebotId. | +| `notify(input)` | `src/data/notifications.ts` | Upozorní člověka. Nečeká se a nevyhazuje chyby, stejně jako audit. | +| `runFlow(steps, context, options)` | `src/runtime/executor.ts` | Vykoná strom kroků. Nikdy nevyhodí výjimku, chyba je výsledek. Používá to akce na ticketu i webhook, aby se strom choval všude stejně. | +| `widgetCatalog(tenantIds, userId)` | `src/data/widgets.ts` | Jediná definice toho, co jde položit na dashboard. Používá ji nabídka i kontrola ukládaného rozložení. | +| `intakeEvent(input)` | `src/data/ticketStore.ts` | Přijme událost zvenku: podle externího ID buď založí ticket, nebo ji navěsí na existující. Jediná cesta, kterou se událost stává ticketem. | +| `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. | +| `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) Viz [11-skripty-konektoru.md](11-skripty-konektoru.md). -| Co | Kde | K čemu | -| --- | --- | --- | -| `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. | -| `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. | -| `createHttp(...)` | `src/scripts/http.ts` | HTTP se timeoutem, limitem odpovědi a rozlišením "zkusit znovu" a "marné". | -| `scriptIdFor(serviceId, operationId)` | `src/scripts/lookup.ts` | Který skript obsluhuje operaci z katalogu. | +| Co | Kde | K čemu | +| ------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `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. | +| `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. | +| `serviceBaseUrl(service)` | `src/scripts/connections.ts` | Adresa služby: naše aplikace ze `SERVICES_BASE_URL`, cizí (OpenAI) z jejího `baseUrl`. Přebít jde přes `_BASE_URL`. | +| `targetSecrets(target)` | `src/scripts/connections.ts` | Co se musí vyškrtat z logu. Vrací i holý klíč bez předpony `Bearer `, protože v něm ho cizí služby vracejí v chybách. | +| `createHttp(...)` | `src/scripts/http.ts` | HTTP se timeoutem, limitem odpovědi a rozlišením "zkusit znovu" a "marné". | +| `isPrivateHost(host)` | `src/scripts/http.ts` | Míří jméno do vnitřní sítě? Jedno pravidlo pro HTTP i pro SMTP server z konektoru. | +| `sendMail(target, message)` | `src/mail/smtp.ts` | Odešle e-mail přes SMTP z konektoru. Nikdy nevyhodí výjimku, vrací i to, jestli má smysl zkusit znovu. | +| `verifySmtp(target)` | `src/mail/smtp.ts` | Přihlásí se na server bez odeslání zprávy. Tím se ověřuje konektor e-mailu. | +| `escapeHtml(value)` | `src/data/templates.ts` | Escapuje **dosazenou hodnotu** v HTML šabloně. Značky autora šablony zůstávají, ostré závorky od zákazníka ne. | +| `ctx.http.postForm(...)` | `src/scripts/http.ts` | Odeslání souboru (`multipart/form-data`). Obsah přichází jako Base64, hranici dopisuje runtime. | +| `scriptIdFor(serviceId, operationId)` | `src/scripts/lookup.ts` | Který skript obsluhuje operaci z katalogu. | ## Klient -| Co | Kde | K čemu | -| --- | --- | --- | -| `EntityAdmin` | `components/dashboard/EntityAdmin.tsx` | Celá správa jedné entity: tabulka, modál, validace, mazání. Nová záložka nastavení = popis sloupců a polí, ne nová stránka. | -| `parseJsonField` | `components/dashboard/EntityAdmin.tsx` | Textové pole s JSONem na hodnotu, s hlášením, kde je chyba. | -| `TicketActions` | `components/dashboard/TicketActions.tsx` | CTA akcí na ticketu plus typ, tagy a vlastní pole. Seznam akcí chodí ze serveru už vyfiltrovaný. | -| `ErrorDetail` | `components/dashboard/ErrorDetail.tsx` | Rozbalovací celé chybové hlášení s kopírováním. Chyba se nikdy nezkracuje. | -| `CustomWidgetCard` | `components/dashboard/widgets/CustomWidget.tsx` | Vykreslí widget, jehož data počítá server: číslo, pruhy, tabulka výkonu, časová řada, seznam, data z konektoru. | -| `TicketTable` | `components/dashboard/TicketTable.tsx` | Tabulka ticketů pro všechna místa. Na mobilu se místo posouvání do strany kreslí karty. | -| `TicketEvents` | `components/dashboard/TicketEvents.tsx` | Příchozí události ticketu včetně celého přijatého JSONu. | -| `ViewSwitch` | `components/dashboard/ViewSwitch.tsx` | Přepínač tabulka nebo dlaždice. Používají ho všechny seznamy. | -| `FlowCanvas` s `start` | `components/dashboard/flow/FlowCanvas.tsx` | Tentýž strom kroků i bez spouštěče - pro tělo akce, které spouští člověk. | -| `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` | `lib/api.ts` | Jediná cesta na API: base path, token, `ApiError` s celým hlášením ze serveru. | -| `useApiQuery` | `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. | -| `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. | -| `usePageMeta` | `lib/usePageMeta.ts` | Titulek stránky. | -| `Badge`, `Button`, `Modal`, `Card`, ... | `components/ui/` | Základní prvky. Nový vzhled tlačítka patří sem, ne do stránky. | +| Co | Kde | K čemu | +| --------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| `EntityAdmin` | `components/dashboard/EntityAdmin.tsx` | Celá správa jedné entity: tabulka, modál, validace, mazání. Nová záložka nastavení = popis sloupců a polí, ne nová stránka. | +| `parseJsonField` | `components/dashboard/EntityAdmin.tsx` | Textové pole s JSONem na hodnotu, s hlášením, kde je chyba. | +| `TicketActions` | `components/dashboard/TicketActions.tsx` | CTA akcí na ticketu plus typ, tagy a vlastní pole. Seznam akcí chodí ze serveru už vyfiltrovaný. | +| `ErrorDetail` | `components/dashboard/ErrorDetail.tsx` | Rozbalovací celé chybové hlášení s kopírováním. Chyba se nikdy nezkracuje. | +| `CustomWidgetCard` | `components/dashboard/widgets/CustomWidget.tsx` | Vykreslí widget, jehož data počítá server: číslo, pruhy, tabulka výkonu, časová řada, seznam, data z konektoru. | +| `TicketTable` | `components/dashboard/TicketTable.tsx` | Tabulka ticketů pro všechna místa. Na mobilu se místo posouvání do strany kreslí karty. | +| `TicketEvents` | `components/dashboard/TicketEvents.tsx` | Příchozí události ticketu včetně celého přijatého JSONu. | +| `ViewSwitch` | `components/dashboard/ViewSwitch.tsx` | Přepínač tabulka nebo dlaždice. Používají ho všechny seznamy. | +| `FlowCanvas` s `start` | `components/dashboard/flow/FlowCanvas.tsx` | Tentýž strom kroků i bez spouštěče - pro tělo akce, které spouští člověk. | +| `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` | `lib/api.ts` | Jediná cesta na API: base path, token, `ApiError` s celým hlášením ze serveru. | +| `useApiQuery` | `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. | +| `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. | +| `usePageMeta` | `lib/usePageMeta.ts` | Titulek stránky. | +| `Badge`, `Button`, `Modal`, `Card`, ... | `components/ui/` | Základní prvky. Nový vzhled tlačítka patří sem, ne do stránky. | ## Pravidla, která z toho plynou diff --git a/documentation/16-monetizace.md b/documentation/16-monetizace.md index f248fa2..4444684 100644 --- a/documentation/16-monetizace.md +++ b/documentation/16-monetizace.md @@ -11,12 +11,12 @@ odeslání objednávky, 0,10 Kč). Čtyři věci, každá měří něco jiného: -| Základ | Co to znamená | Proč / proč ne | -| --- | --- | --- | -| Za uživatele | Kolik lidí má přístup do portálu | Předvídatelné, ale nesouvisí s tím, co aplikace dělá. U automatizací platí zákazník za lidi, kteří tam nemusí chodit. | -| Za automatizaci | Kolik má zapnutých stromů | Trestá to rozdělení jednoho velkého stromu na tři přehledné. Špatná motivace. | -| **Za krok** | Kolik kroků se skutečně vykonalo | Odpovídá naší práci: každý krok je jedno volání služby, jeden zápis, jeden běh skriptu. Zákazník vidí, za co platí. | -| Za objem dat | Kolik toho proteče | Nesouvisí s náklady, u nás jsou to kilobajty. | +| Základ | Co to znamená | Proč / proč ne | +| --------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| Za uživatele | Kolik lidí má přístup do portálu | Předvídatelné, ale nesouvisí s tím, co aplikace dělá. U automatizací platí zákazník za lidi, kteří tam nemusí chodit. | +| Za automatizaci | Kolik má zapnutých stromů | Trestá to rozdělení jednoho velkého stromu na tři přehledné. Špatná motivace. | +| **Za krok** | Kolik kroků se skutečně vykonalo | Odpovídá naší práci: každý krok je jedno volání služby, jeden zápis, jeden běh skriptu. Zákazník vidí, za co platí. | +| Za objem dat | Kolik toho proteče | Nesouvisí s náklady, u nás jsou to kilobajty. | Doporučení: **paušál plus kroky**. Paušál kryje portál, tickety, úložiště a podporu, kroky kryjí provoz automatizací. Bez paušálu je zákazník, který má @@ -29,11 +29,11 @@ a jeho dotaz na kontakt stojí jinak, protože nás jinak stojí. Tři pásma: -| Pásmo | Příklady | Návrh ceny | -| --- | --- | --- | -| Obecné | pauza, zápis do logu, podmínka, transformace dat | 0 Kč. Účtovat podmínku je jako účtovat mezeru v textu. | -| Naše práce | webhook, plánovač, založení ticketu, přiřazení řešitele, HTTP požadavek | 0,02 Kč | -| Cizí služba | iDoklad, Shoptet, SAP, CRM, hlasová brána, AI | 0,05 až 0,50 Kč podle toho, co za to platíme sami | +| Pásmo | Příklady | Návrh ceny | +| ----------- | ----------------------------------------------------------------------- | ------------------------------------------------------ | +| Obecné | pauza, zápis do logu, podmínka, transformace dat | 0 Kč. Účtovat podmínku je jako účtovat mezeru v textu. | +| Naše práce | webhook, plánovač, založení ticketu, přiřazení řešitele, HTTP požadavek | 0,02 Kč | +| Cizí služba | iDoklad, Shoptet, SAP, CRM, hlasová brána, AI | 0,05 až 0,50 Kč podle toho, co za to platíme sami | Číslo je jedno pole u operace, takže se dá měnit bez zásahu do kódu. Ceník je verzovaný: změna ceny nepřepíše historii, jinak by se zpětně změnila @@ -75,11 +75,11 @@ Uzavřený měsíc se nedá změnit, jen opravit dobropisem. Cena za krok samotná zákazníka děsí, protože nezná svoje čísla. Proto balíčky s předplacenými kroky a stejnou cenou nad limit: -| Balíček | Paušál | Kroků v ceně | Nad limit | -| --- | --- | --- | --- | -| Start | 490 Kč | 5 000 | 0,05 Kč | -| Provoz | 1 900 Kč | 40 000 | 0,04 Kč | -| Firma | 6 900 Kč | 200 000 | 0,03 Kč | +| Balíček | Paušál | Kroků v ceně | Nad limit | +| ------- | -------- | ------------ | --------- | +| Start | 490 Kč | 5 000 | 0,05 Kč | +| Provoz | 1 900 Kč | 40 000 | 0,04 Kč | +| Firma | 6 900 Kč | 200 000 | 0,03 Kč | Nad limit se **nevypíná**. Zastavit zákazníkovi fakturaci objednávek kvůli překročení limitu je horší než mu to dofakturovat. Limit hlásí varování diff --git a/documentation/17-nastaveni-a-prava.md b/documentation/17-nastaveni-a-prava.md index 509a525..ae9a119 100644 --- a/documentation/17-nastaveni-a-prava.md +++ b/documentation/17-nastaveni-a-prava.md @@ -14,11 +14,11 @@ souborů, ze kterých se jeden opraví a ostatní ne. Proto tři vrstvy, každá napsaná jednou: -| Vrstva | Kde | Co dělá | -| --- | --- | --- | -| Úložiště | `src/data/store/` | `EntityStore` a dvě implementace. Volající nepozná, jestli běží Postgres nebo JSON soubor. | -| API | `src/routes/crud.ts` | `crudRouter` vyrobí pětici endpointů včetně práva a auditu. | -| Klient | `components/dashboard/EntityAdmin.tsx` | Tabulka, modál, validace, mazání. | +| Vrstva | Kde | Co dělá | +| -------- | -------------------------------------- | --------------------------------------------------------------------------------------------- | +| Úložiště | `src/data/store/` | `EntityStore` a dvě implementace. Volající nepozná, jestli běží Postgres nebo JSON soubor. | +| API | `src/routes/crud.ts` | `crudRouter` vyrobí pětici endpointů včetně práva a auditu. | +| Klient | `components/dashboard/EntityAdmin.tsx` | Tabulka, modál, validace, mazání. | Nová entita v nastavení pak znamená: `defineStore` v modulu entity, jeden řádek v `bootstrap.ts`, jeden `crudRouter` v `settings.ts`, jeden popis v @@ -72,11 +72,11 @@ společnou mezivrstvu. Automatizace běží sama, akce je tlačítko, které zm Tělo akce je jedno z trojice: -| Tělo | Kdy | Příklad | -| --- | --- | --- | -| Operace konektoru | běžný případ | odeslat objednávku do iDokladu | -| Vlastní strom | akce má víc kroků a rozhodování | dohledat kontakt, vystavit fakturu, odeslat e-mailem | -| Skript | nic z toho nestačí | vlastní výpočet nebo cizí API, které v katalogu není | +| Tělo | Kdy | Příklad | +| ----------------- | ------------------------------- | ---------------------------------------------------- | +| Operace konektoru | běžný případ | odeslat objednávku do iDokladu | +| Vlastní strom | akce má víc kroků a rozhodování | dohledat kontakt, vystavit fakturu, odeslat e-mailem | +| Skript | nic z toho nestačí | vlastní výpočet nebo cizí API, které v katalogu není | Server vrací k ticketu **jen akce, které v té situaci opravdu jdou spustit**: sedí typ nebo tag, projdou podmínky a volající na ně má právo. Klient @@ -117,12 +117,12 @@ takže ho nemůže ani omylem prodloužit. ## Co se ukládá a co se drží v paměti -| Data | Jak | Proč | -| --- | --- | --- | -| Firmy, uživatelé, role, řešitelé, skupiny, typy, akce, widgety, záložky | `withCache` | Čtou se při každém requestu, mění se zřídka. Kopie v paměti, 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. | -| Konektory | vlastní úložiště | Nesou šifrovaná tajemství a potřebují částečný unikátní index. Viz [14-databaze.md](14-databaze.md). | +| 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. | +| 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 nikdy nepřepíše starý. diff --git a/documentation/18-ticketovaci-system.md b/documentation/18-ticketovaci-system.md index 4a69116..e46bcef 100644 --- a/documentation/18-ticketovaci-system.md +++ b/documentation/18-ticketovaci-system.md @@ -60,10 +60,10 @@ slučovalo věci, které spolu nesouvisí. Dvě různé věci, které se snadno pletou: -| | Co to je | Kde se bere | -| --- | --- | --- | -| **Událost** | fakt zvenku, celá přijatá data | poslal odesílatel | -| **Řádek logu** | naše stopa toho, co se dělo uvnitř | zapsala aplikace | +| | Co to je | Kde se bere | +| -------------- | ---------------------------------- | ----------------- | +| **Událost** | fakt zvenku, celá přijatá data | poslal odesílatel | +| **Řádek logu** | naše stopa toho, co se dělo uvnitř | zapsala aplikace | Události se ukazují na detailu ticketu nad logem a dají se rozbalit na celý přijatý JSON. Když se někdo ptá, proč ticket vypadá takhle, je to jediná @@ -75,12 +75,12 @@ rostoucí ticket by při každém zápisu přepisoval víc a víc dat. Aby šlo říct, kdo kolik odbavil a komu to nejde, nestačí počítat vyřešené. Ticket proto nese: -| Pole | Kdy se zapíše | K čemu | -| --- | --- | --- | +| Pole | Kdy se zapíše | K čemu | +| ----------------- | ------------------------------------------------ | ----------------------------------- | | `firstResponseAt` | při prvním přiřazení, komentáři nebo změně stavu | jak dlouho zákazník čekal na reakci | -| `resolvedAt` | při přechodu na vyřešeno | doba řešení | -| `resolvedById` | tamtéž, je to ten, kdo ho měl u sebe | komu se vyřešení připíše | -| `reopenCount` | při návratu z vyřešeno | kolikrát to hotové nebylo | +| `resolvedAt` | při přechodu na vyřešeno | doba řešení | +| `resolvedById` | tamtéž, je to ten, kdo ho měl u sebe | komu se vyřešení připíše | +| `reopenCount` | při návratu z vyřešeno | kolikrát to hotové nebylo | `firstResponseAt` se zapisuje **jednou a nepřepisuje**. Je to okamžik, kdy zákazník přestal čekat. Kdyby se přepisoval při každé změně, měřil by poslední @@ -109,12 +109,12 @@ něco jiného. ## Pohledy -| Stránka | Co ukazuje | -| --- | --- | -| Tickety | seznam s filtry, **tabulka nebo dlaždice** | -| Detail ticketu | obsah, události, log, akce, typ, tagy, řešitel, skupina | -| Lidé | řešitelé firmy a jejich vytížení, **tabulka nebo dlaždice** | -| Detail osoby | její výkon, co má u sebe, co naposledy vyřešila | +| Stránka | Co ukazuje | +| -------------- | ----------------------------------------------------------- | +| Tickety | seznam s filtry, **tabulka nebo dlaždice** | +| Detail ticketu | obsah, události, log, akce, typ, tagy, řešitel, skupina | +| Lidé | řešitelé firmy a jejich vytížení, **tabulka nebo dlaždice** | +| Detail osoby | její výkon, co má u sebe, co naposledy vyřešila | Přepínač pohledu je jedna komponenta (`components/dashboard/ViewSwitch.tsx`) a používají ji obě stránky se seznamem. @@ -124,14 +124,14 @@ a používají ji obě stránky se seznamem. Widget je dvojice: `render` (jak se to kreslí) a `source` (odkud jsou data). Zdroje: -| Zdroj | Co dělá | -| --- | --- | -| `ticketCount` | počet ticketů podle filtru, volitelně seskupený | -| `ticketList` | seznam ticketů | -| `ticketSeries` | časová řada | -| `workload` | kdo co má u sebe | -| `agentStats` | výkon řešitelů | -| `connector` | **data z napojené služby** | +| Zdroj | Co dělá | +| -------------- | ----------------------------------------------- | +| `ticketCount` | počet ticketů podle filtru, volitelně seskupený | +| `ticketList` | seznam ticketů | +| `ticketSeries` | časová řada | +| `workload` | kdo co má u sebe | +| `agentStats` | výkon řešitelů | +| `connector` | **data z napojené služby** | Zdroj `connector` zavolá **tentýž skript**, který používá krok automatizace i akce na ticketu, a z výsledku vezme, co je v `path`. Widget nemá vlastní @@ -158,17 +158,75 @@ Data všech dlaždic chodí **jedním requestem** (`POST /api/dashboard/widget-d Widget, který selže, hlásí chybu **na své pozici** a celou, nezkrácenou - jeden rozbitý zdroj nesmí zhasnout celý přehled. +## Helpdesk: ticket, ktery vidi dve firmy + +Ticket patri jedne firme. U helpdesku ale figuruji dve: ta, ktera pozadavek +poslala, a ta, ktera ho resi. Reseni je jedno pole navic, ne druha hranice +viditelnosti. + +| Pole | Kdo to je | +| ------------------------- | ---------------------------------------------- | +| `Ticket.tenantId` | firma, ktera pozadavek **resi**, tedy vlastnik | +| `Ticket.helpdeskSourceId` | firma, ktera pozadavek **poslala** | + +**Vlastnikem je zamerne dodavatel, ne zadavatel.** Kdyby byl vlastnikem +zadavatel, mel by resitel pozadavek jen jako cizi ticket a nemel by ho ve sve +fronte, ve statistikach ani v prirazovani. Takhle je to na jeho strane obycejny +ticket a nemuselo se kvuli tomu sahnout na nic z toho, co uz funguje. + +Komu pozadavek pripadne, urcuje `helpdeskProviderId` **na firme zadavatele**. +Nastavuje ho spravce platformy v Nastaveni, Firmy. Kdo koho obsluhuje je +obchodni vztah, ne volba klienta - kdyby si dodavatele vybiral uzivatel, poslal +by pozadavek nekomu, s kym nema smlouvu. Bez vyplneneho dodavatele se pozadavek +nezalozi a rekne se to nahlas. + +### Co smi zadavatel + +| Akce | Smi | +| ------------------------------ | --- | +| Videt svoje pozadavky | ano | +| Otevrit detail a prubeh | ano | +| Pripsat komentar | ano | +| Menit stav, resitele, typ | ne | +| Videt ostatni tickety resitele | ne | + +Komentar je jedina zmena, kterou nad cizim ticketem smi. Doplnit, co zapomnel +napsat, je presne to, kvuli cemu se pozadavek otevira; stav urcuje ten, kdo to +resi. + +Filtruje se podle `helpdeskSourceIds`, ktere **nahrazuje** filtr podle +vlastnika - zadavatel vlastnikem neni, takze by mu jinak nezbylo nic. Bezny +seznam ticketu tim zustava nedotceny: `listTickets({ tenantIds })` se nezmenil. + +### Kdo helpdesk vidi + +Pravo `helpdesk.view` (videt sekci) a `helpdesk.create` (poslat pozadavek). +Obe prideluje **admin te firmy** pres role, stejne jako u ostatnich prav. +Zalozka `helpdesk` je v katalogu modulu jako povinna, aby ji mely i firmy +zalozene driv - o tom, kdo ji uvidi, stejne rozhoduje pravo. + +### API + +| Metoda | Cesta | Popis | +| ------ | ------------------------------------- | -------------------------- | +| GET | `/api/dashboard/helpdesk` | pozadavky teto firmy | +| POST | `/api/dashboard/helpdesk` | poslat pozadavek | +| GET | `/api/dashboard/helpdesk/:id` | detail vlastniho pozadavku | +| POST | `/api/dashboard/helpdesk/:id/comment` | pripsat komentar | + +Stranka portalu je `/dashboard/helpdesk`. + ## Kde se co definuje Akce a widgety **nejsou v nastavení**. Je to definice toho, co aplikace umí, stejná úroveň jako automatizace, a mají vlastní záložku: -| Záložka | Co tam patří | -| --- | --- | -| Automatizace | stromy, které běží samy | -| Akce | tlačítka na ticketu, tělo je **tentýž strom** | -| Widgety | dlaždice na přehled | -| Nastavení | firmy, lidé, role, typy ticketů, audit | +| Záložka | Co tam patří | +| ------------ | --------------------------------------------- | +| Automatizace | stromy, které běží samy | +| Akce | tlačítka na ticketu, tělo je **tentýž strom** | +| Widgety | dlaždice na přehled | +| Nastavení | firmy, lidé, role, typy ticketů, audit | Tělo akce se skládá stejným editorem jako automatizace. Místo karty spouštěče je karta "spouští člověk tlačítkem na ticketu" a parametry, na které jde diff --git a/documentation/19-kapacita-200-firem.md b/documentation/19-kapacita-200-firem.md index c1b035e..feb3492 100644 --- a/documentation/19-kapacita-200-firem.md +++ b/documentation/19-kapacita-200-firem.md @@ -13,12 +13,12 @@ Jeden proces, režim souboru, tickety se posílaly přes příjem událostí. Měřeno na vývojovém stroji, tedy horní hranice latence, ne serveru. | Ticketů | Výpis seznamu | Statistiky řešitelů | Soubor | -| --- | --- | --- | --- | -| 103 | 1,9 ms | 1,6 ms | 153 kB | -| 503 | 6,1 ms | 12,1 ms | 729 kB | -| 1 003 | 14,2 ms | 2,7 ms | 1,4 MB | -| 2 003 | 18,8 ms | 21,1 ms | 2,9 MB | -| 5 003 | 52,9 ms | 1,7 ms | 7,2 MB | +| ------- | ------------- | ------------------- | ------ | +| 103 | 1,9 ms | 1,6 ms | 153 kB | +| 503 | 6,1 ms | 12,1 ms | 729 kB | +| 1 003 | 14,2 ms | 2,7 ms | 1,4 MB | +| 2 003 | 18,8 ms | 21,1 ms | 2,9 MB | +| 5 003 | 52,9 ms | 1,7 ms | 7,2 MB | Příjem událostí: **130 až 190 událostí za sekundu** včetně celého kola HTTP, uložení a zápisu do logu. @@ -36,12 +36,12 @@ Z toho plyne: 200 firem, 10 automatizací každá, 15 lidí. Odhad provozu: -| Veličina | Výpočet | Za den | Za měsíc | -| --- | --- | --- | --- | -| Běhy automatizací | 2 000 automatizací, 100 běhů denně | 200 000 | 6 mil. | -| Kroky | 3 kroky na běh | 600 000 | 18 mil. | -| Tickety | 200 firem, 200 denně | 40 000 | 1,2 mil. | -| Řádky logu | 5 na ticket plus kroky | ~800 000 | 24 mil. | +| Veličina | Výpočet | Za den | Za měsíc | +| ----------------- | ---------------------------------- | -------- | -------- | +| Běhy automatizací | 2 000 automatizací, 100 běhů denně | 200 000 | 6 mil. | +| Kroky | 3 kroky na běh | 600 000 | 18 mil. | +| Tickety | 200 firem, 200 denně | 40 000 | 1,2 mil. | +| Řádky logu | 5 na ticket plus kroky | ~800 000 | 24 mil. | Špička není průměr. 7 kroků za sekundu v průměru znamená ve špičce klidně 100 za sekundu, protože e-shopy neposílají objednávky rovnoměrně. @@ -101,14 +101,14 @@ rozejdou. Řeší to `LISTEN/NOTIFY` na obnovu kopií a sdílený kanál na stre Za rychlost a výsledek cizí služby neručíme, a proto se s tím musí počítat v návrhu, ne v provozu: -| Riziko | Co s tím | -| --- | --- | -| Služba odpovídá pomalu | Timeout na krok, ne na celý běh. Běh se uspí a pokračuje. | -| Služba je chvíli mimo | Opakování s rostoucí prodlevou, ne hned a ne donekonečna. | -| Služba je mimo dlouho | Vypnout ji po sérii chyb a nezkoušet každý běh znovu, ať netrpí ostatní. | -| Služba má limit volání | Strop souběžných volání **na dvojici firma a služba**, ne globálně. | -| Služba odpoví dvakrát jinak | Klíč proti dvojímu provedení u kroku, aby se nevystavila druhá faktura. | -| Služba je pomalá jen pro jednu firmu | Fronta po firmách, aby jedna firma nezablokovala ostatní. | +| Riziko | Co s tím | +| ------------------------------------ | ------------------------------------------------------------------------ | +| Služba odpovídá pomalu | Timeout na krok, ne na celý běh. Běh se uspí a pokračuje. | +| Služba je chvíli mimo | Opakování s rostoucí prodlevou, ne hned a ne donekonečna. | +| Služba je mimo dlouho | Vypnout ji po sérii chyb a nezkoušet každý běh znovu, ať netrpí ostatní. | +| Služba má limit volání | Strop souběžných volání **na dvojici firma a služba**, ne globálně. | +| Služba odpoví dvakrát jinak | Klíč proti dvojímu provedení u kroku, aby se nevystavila druhá faktura. | +| Služba je pomalá jen pro jednu firmu | Fronta po firmách, aby jedna firma nezablokovala ostatní. | Timeout a rozlišení "zkusit znovu" a "marné" už v `scripts/http.ts` je, klíč proti dvojímu provedení taky. Chybí to, co je nad tím: fronta, opakování @@ -134,12 +134,12 @@ bylo použitelné. Po těch úpravách, pro zadaných 200 firem: -| Část | Kolik | Proč | -| --- | --- | --- | -| Web a API | 2 instance, 1 vCPU a 1 GB každá | Požadavky jsou krátké, jde hlavně o dostupnost při restartu. | +| Část | Kolik | Proč | +| ----------- | --------------------------------- | ------------------------------------------------------------------------------------------------------- | +| Web a API | 2 instance, 1 vCPU a 1 GB každá | Požadavky jsou krátké, jde hlavně o dostupnost při restartu. | | Worker běhů | 2 instance, 1 vCPU a 512 MB každá | Kroky čekají na cizí službu, procesor se skoro nepoužije. Jeden proces zvládne stovky souběžných kroků. | -| Postgres | 4 vCPU, 8 GB RAM, 200 GB disku | 10 až 20 zápisů za sekundu v průměru je málo, disk sežere log. | -| Celkem | ~8 vCPU, ~11 GB RAM | | +| Postgres | 4 vCPU, 8 GB RAM, 200 GB disku | 10 až 20 zápisů za sekundu v průměru je málo, disk sežere log. | +| Celkem | ~8 vCPU, ~11 GB RAM | | Kritické číslo není procesor, ale **disk pod databází** a retence logu. S plnou odpovědí služby u každého kroku je to zhruba 20 GB měsíčně na diff --git a/documentation/20-fronta-a-runtime.md b/documentation/20-fronta-a-runtime.md index d0554d7..33cad52 100644 --- a/documentation/20-fronta-a-runtime.md +++ b/documentation/20-fronta-a-runtime.md @@ -33,11 +33,11 @@ v `GET /api/dashboard/runs` nebo v logu ticketu. ## Co frontu plní -| Druh | Kdo to spustí | Příklad | -| --- | --- | --- | -| Push | cizí služba zavolá nás | e-shop pošle novou objednávku | -| Vnitřní událost | něco se stalo u nás | vznikl nebo se změnil ticket | -| Pull | ptáme se sami | e-mail, zprávy z Messengeru | +| Druh | Kdo to spustí | Příklad | +| --------------- | ---------------------- | ----------------------------- | +| Push | cizí služba zavolá nás | e-shop pošle novou objednávku | +| Vnitřní událost | něco se stalo u nás | vznikl nebo se změnil ticket | +| Pull | ptáme se sami | e-mail, zprávy z Messengeru | ### Pull, tedy pravidelné dotazování @@ -68,9 +68,9 @@ s vnořenými objekty a poli. Proto má každý parametr spouštěče **cestu**: } ``` -| Parametr | Cesta | Typ | -| --- | --- | --- | -| `docId` | `document.id` | string | +| Parametr | Cesta | Typ | +| -------------- | ------------------ | ------ | +| `docId` | `document.id` | string | | `errorMessage` | `errors.0.message` | string | Ve stromu se pak píše `{{docId}}` bez ohledu na to, jak hluboko to odesílatel @@ -86,13 +86,13 @@ adresa včetně domény**. ## Opakování a vzdání se -| Pokus | Kdy | -| --- | --- | -| 1. | hned | -| 2. | za 30 s | -| 3. | za 2 min | -| 4. | za 10 min | -| 5. | za hodinu | +| Pokus | Kdy | +| ----- | --------- | +| 1. | hned | +| 2. | za 30 s | +| 3. | za 2 min | +| 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. @@ -144,24 +144,24 @@ Založit ticket nebo přehodit ho na člověka není volání cizí služby, tak nejde přes skript - sahá to do našeho úložiště. Pro uživatele je to v katalogu operace jako každá jiná. -| Krok | Co dělá | -| --- | --- | -| `ticket/upsert` | podle externího ID založí ticket, nebo na existující navěsí událost | +| Krok | Co dělá | +| -------------------------- | --------------------------------------------------------------------- | +| `ticket/upsert` | podle externího ID založí ticket, nebo na existující navěsí událost | | `ticket/assign-least-busy` | předá nejvolnějšímu ze skupiny, při shodě rozhoduje podíl ke kapacitě | -| `ticket/set-type` | nastaví typ, za kterým stojí vlastní pole | -| `ticket/set-stage` | posune do další fáze workflow daného typu | -| `ticket/add-tags` | přidá štítky, existující nechá | -| `ticket/set-status` | změní stav v životním cyklu | -| `incident/create` | založí incident | -| `flow/pause`, `flow/log` | pauza a zápis do logu | +| `ticket/set-type` | nastaví typ, za kterým stojí vlastní pole | +| `ticket/set-stage` | posune do další fáze workflow daného typu | +| `ticket/add-tags` | přidá štítky, existující nechá | +| `ticket/set-status` | změní stav v životním cyklu | +| `incident/create` | založí incident | +| `flow/pause`, `flow/log` | pauza a zápis do logu | ## Tři osy na ticketu -| Osa | Kdo ji určuje | K čemu | -| --- | --- | --- | -| `status` | pevná čtveřice (nový, v řešení, čeká, vyřešeno) | životní cyklus, počítají se z něj statistiky a fronta | -| `stage` | firma u typu ticketu (`TicketType.statuses`) | postup uvnitř typu: čeká na zabalení, předáno dopravci | -| `tags` | kdokoliv, volně | označení, která spolu nemusí souviset | +| Osa | Kdo ji určuje | K čemu | +| -------- | ----------------------------------------------- | ------------------------------------------------------ | +| `status` | pevná čtveřice (nový, v řešení, čeká, vyřešeno) | životní cyklus, počítají se z něj statistiky a fronta | +| `stage` | firma u typu ticketu (`TicketType.statuses`) | postup uvnitř typu: čeká na zabalení, předáno dopravci | +| `tags` | kdokoliv, volně | označení, která spolu nemusí souviset | Fáze může být **jen jedna**, proto se na ni dá spolehnout v podmínce. Přes štítky by to fungovalo taky, ale ticket by mohl mít "čeká na zabalení" diff --git a/documentation/21-realne-sluzby.md b/documentation/21-realne-sluzby.md new file mode 100644 index 0000000..85088f2 --- /dev/null +++ b/documentation/21-realne-sluzby.md @@ -0,0 +1,346 @@ +# 21 - Realne sluzby a co k nim potreba + +Naprogramovano. Tenhle dokument rika, **ktera sluzba v katalogu ma za sebou +opravdu bezici aplikaci**, jake udaje po firme chce a co s ni umime udelat. + +Obecny popis vrstev je v [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md), +popis skriptu v [11-skripty-konektoru.md](11-skripty-konektoru.md). + +## Zdroj pravdy + +Seznam bezicich aplikaci je na `https://services.csbot.cz/apps`. Kazda ma +`/docs` se Swaggerem a `/openapi.json` (u .NET aplikaci `/docs/v1/swagger.json`) +se strojove citelnym popisem. + +Katalog v `src/data/services.ts` z toho vychazi. **Neni to totez**: jedna +aplikace muze nest vic sluzeb katalogu a nektere sluzby katalogu zatim zadnou +aplikaci nemaji. + +## Ktera sluzba stoji na cem + +| Sluzba katalogu | Aplikace (`appId`) | Overeni (`verifyPath`) | +| ------------------ | ----------------------------- | ---------------------------------------------- | +| iDoklad | `idoklad` | `/account/agenda` | +| RAYNET CRM | `raynet` | `/company?limit=1` | +| CSOB (PSD2) | `csob` | `/accounts?size=1` | +| SAP Business One | `sap-bo` | `/api/system/info` | +| PPL CPL | `pplcplapi` | `/customer` | +| Microsoft 365 | `microsoft-365-service` | `/status` | +| Google Workspace | `google-service` | `/google/drive/files` | +| Google Analytics 4 | `analytics` | `/ga/admin/accountSummaries` | +| Search Console | `analytics` | `/gsc/sites` | +| Google Ads | `analytics` | `/googleads/customers:listAccessibleCustomers` | +| Sklik | `analytics` | `/sklik/limits` | +| Meta Ads | `meta` | `/ads/me/adaccounts` | +| Prepis hovoru | `audio-transcription` | nema, overi se jen dostupnost | +| E-mail | SMTP server firmy | prihlaseni na server, nic se neodesila | +| OpenAI | mimo nas, `api.openai.com/v1` | `/models` | + +**Ctyri sluzby na jedne aplikaci.** GA4, Search Console, Google Ads a Sklik +bezi v `analytics`, ale kazda ma **jine pristupove udaje** a jine ceny za +pristup. Slucovat je do jedne sluzby by znamenalo, ze firma, ktera ma jen +Sklik, musi zaroven vyplnit Google. Proto jsou to ctyri sluzby s jednim `appId`. + +**Prepis hovoru nema overeni udaju.** Jeho jedine volani je prepis, ktery se +uctuje. Overeni proto rekne jen "sluzba odpovida" a nahlas dodá, ze udaje +overene nejsou - test, ktery projde i se spatnym klicem, by lhal. + +## Sluzby, ktere aplikaci zatim nemaji + +E-shop, WhatsApp, Facebook Messenger, Instagram, SMS, Slack, Voicebot +a AI zpracovani textu jsou v katalogu jako popis toho, co chceme umet. Konektor +u nich zalozit jde, ale overeni skonci chybou, protoze na te adrese nic nebezi. + +Meta Ads je **neco jineho nez Facebook Messenger a Instagram**: aplikace `meta` +je nad Marketing API, tedy reklamy, a je jen pro cteni. Zpravy ze stranky +a prime zpravy ta aplikace neumi, proto se na ni ty dve sluzby nenapojily. + +## Co ktera sluzba chce po firme + +Udaje patri konektoru, ne prostredi. Tajne se z API nikdy nevraci. + +### RAYNET CRM + +| Pole | Hlavicka | Kde to vzit | +| ---------------- | ----------------- | ----------------------------- | +| API klic | `X-Api-Key` | RAYNET: Nastaveni, Klic k API | +| E-mail uzivatele | `X-Raynet-Email` | prihlasovaci e-mail | +| Nazev instance | `X-Instance-Name` | subdomena uctu | + +### CSOB (PSD2) + +Nejvic udaju z celeho katalogu, a je to tak spravne: bankovni rozhrani chce +certifikat i token. `X-Access-Token` navic **casem vyprsi** a musi se prepsat, +jinak konektor prestane fungovat, aniz by se cokoliv jineho zmenilo. + +Certifikat QWAC se vklada jako PFX zakodovany do Base64. + +### SAP Business One + +`X-SAP-B1-BaseUrl` je adresa Service Layer u zakaznika a musi byt dostupna +z internetu. `X-SAP-B1-Reject-Unauthorized` se nastavi na `false` jen tam, kde +ma Service Layer self-signed certifikat. + +### PPL CPL + +Client ID a Client Secret z vyvojarskeho portalu. `X-Environment` prepne na +testovaci prostredi, ktere **nevytvari skutecne zasilky** - hodi se pri +zkousení stromu. + +### Microsoft 365 + +Tenant ID, Client ID a Client Secret registrovane aplikace v Entra ID. +Prihlasuje se aplikace, ne clovek, takze kazdy krok rika, **ktere schranky** +se tyka. + +### Google Workspace + +Dve cesty, staci jedna: + +- **JSON klic service accountu** (`X-Google-Service-Account-Json`) plus + opravneni (`X-Google-Service-Account-Scopes`). Tohle je cesta pro provoz bez + cloveka: sluzba si z klice vystavi token sama. +- **Hotovy access token** (`X-Google-Access-Token`). Plati asi hodinu, takze + na trvaly provoz to neni. + +Overeni konektoru cte Disk, takze service account potrebuje aspon scope +`https://www.googleapis.com/auth/drive.readonly`. Bez nej test skonci chybou, +i kdyz je klic v poradku. + +### Google Analytics 4, Search Console, Google Ads + +Vsechny tri pouzivaji tentyz princip prihlaseni pres Google, jen s jinym +prefixem hlavicky. **Jeden service account staci na vsechny tri**, kdyz se mu +v kazde sluzbe udeli pristup. Google Ads navic vzdy potrebuje developer token. + +### Sklik + +Jeden token z API Drak. Vygenerovani noveho tokenu **zneplatni ten predchozi**, +takze se u sdileneho uctu vyplati vedet, kdo ho generoval naposled. + +### Meta Ads + +Token systemoveho uzivatele z Business Manageru. Uzivatelsky token prestane +platit, kdyz clovek odejde z firmy nebo si zmeni heslo, takze se pro +server-to-server nehodi. App secret je povinny tam, kde ma aplikace zapnute +`appsecret_proof`. + +### Prepis hovoru + +Deepgram i OpenAI klic. Obe sluzby bezi paralelne a treti volani jejich +vysledky slucuje, takze bez obou klicu to nefunguje. + +### OpenAI + +Jen API klic. Zadava se **holy**, slovo `Bearer` dopise portal - viz nize. + +## OpenAI: sluzba, ktera nebezi u nas + +Zbytek katalogu jsou nase aplikace za `services.csbot.cz/apps`. OpenAI je cizi +domena, se kterou nemuzeme hnout, takze se s ni zachazi jinak na trech mistech. + +### Adresa je u sluzby, ne z `appId` + +`Service` ma nove nepovinne pole `baseUrl` s absolutni adresou. Skladat adresu +ze `SERVICES_BASE_URL` by u ni nedavalo smysl - to je zaklad **nasich** +aplikaci. + +Prepsat ji jde dvema zpusoby: + +| Kudy | Pro koho plati | K cemu | +| ------------------ | -------------- | ------------------------------ | +| `OPENAI_BASE_URL` | cela instance | brána, napodobenina pri vyvoji | +| adresa u konektoru | jedna firma | vlastni Azure OpenAI | + +Obecne: `_BASE_URL`, kde se z ID sluzby udelaji velka pismena +a pomlcka je podtrzitko (`sap-bo` je `SAP_BO_BASE_URL`). + +### Klic se zadava holy + +OpenAI chce `Authorization: Bearer `. Kdyby si mel uzivatel slovo +`Bearer` psat sam, byl by to prvni zdroj chyb, ktery **neni videt ani zpetne** - +hodnota se z API nevraci, takze preklep v ni uz nikdo nenajde. + +Pole udaju proto ma nepovinny `prefix` a runtime ho doplni az pri sestaveni +hlavicky. Redakce v logu se dela na obojí: na cely retezec i na samotny klic, +protoze cizi sluzby vraci v chybe jednou jedno a jednou druhé. + +### Co s OpenAI umime + +| Operace | Endpoint | K cemu | +| -------------------- | ---------------------------- | -------------------------------------- | +| Zeptat se modelu | `POST /chat/completions` | shrnuti, klasifikace, sepsani odpovedi | +| Nahrat soubor | `POST /files` | vrati `fileId` pro dalsi krok | +| Zeptat se na soubor | `POST /responses` | vytezeni faktury, smlouvy, fotky | +| Prepsat zvuk | `POST /audio/transcriptions` | jeden pruchod prepisem | +| Nacist seznam modelu | `GET /models` | co ucet umi, nic nestoji | + +**Model je volny text s vychozi hodnotou**, ne vyber ze seznamu. Pevny seznam +by zestarl pri kazdem vydani noveho modelu a krok stromu by pak odmital +hodnotu, kterou ucet umi. Co ucet umi, vrati operace Nacist seznam modelu. + +**Otazka nad souborem je jiny endpoint nez obycejny dotaz.** Soubor jako vstup +umi az Responses API; Chat Completions by prijalo jen text, takze by se obsah +PDF musel vlepit rucne - a to nejde. + +**Nahrani a dotaz jsou dva kroky.** Jednoho souboru se casto pta vic dotazu +a nahravat ho pokazde znovu by stalo cas i penize. + +`temperature` se posila **jen kdyz ji uzivatel vyplni**. Novejsi modely ji +odmitaji uplne, takze poslat vychozi hodnotu by krok rozbilo tam, kde o ni +nikdo nestal. + +## E-mail: sluzba, ktera nejde pres HTTP + +Zbytek katalogu se vola pres HTTP a operaci vykona skript. SMTP neni HTTP, +a skript umi jen `ctx.http` - dat mu sit jinudy by zrusilo pravidlo, ze skript +nema jak zavolat ven mimo nas klient. + +E-mail je proto **vnitrni krok** (`src/runtime/builtinSteps.ts`), stejne jako +zalozeni ticketu. Rozdil je jen v tom, kam saha: ticket do naseho uloziste, +e-mail na posmovni server firmy. + +Sluzba to o sobe rika sama, priznakem `transport: 'smtp'`. Podle nej se rozhodne +i overeni konektoru. Neni to vlastnost konektoru: jak se sluzba vola, je +vlastnost sluzby. + +### Co si firma vyplni + +| Pole | Klic | Poznamka | +| ------------------- | ---------- | -------------------------------------------------------- | +| SMTP server | `host` | napriklad smtp.seznam.cz | +| Port | `port` | 587 pro STARTTLS, 465 pro sifrovane od zacatku | +| Sifrovani | `security` | prazdne se ridi portem, prepsat lze ssl, starttls, zadne | +| Uzivatel | `user` | obvykle cela adresa | +| Heslo | `password` | tajne, z API se nikdy nevraci | +| Adresa odesilatele | `from` | server ji musi povolit | +| Jmeno odesilatele | `fromName` | co uvidi prijemce misto hole adresy | +| Adresa pro odpovedi | `replyTo` | kdyz maji odpovedi chodit jinam | + +Prazdne sifrovani se ridi portem, protoze to je zvyklost, kterou zna kazdy. +Vyslovna hodnota to prebije - jsou servery, ktere to maji jinak. + +Adresa serveru se hlida stejne jako u HTTP: **nesmi mirit do vnitrni site**. +Vyplnuje ji firma, takze je to jedina zabrana proti tomu, aby si nechala +navazat spojeni dovnitr. + +### Co se vyplnuje v kroku + +| Pole | Druh | Poznamka | +| ------------------- | -------- | ----------------------------------- | +| Prijemce | text | adresy oddelene carkou | +| Kopie, skryta kopie | text | nepovinne | +| Predmet | text | sablona, tedy `Ticket {{ticketId}}` | +| Telo zpravy | **html** | pise se jako HTML, vice radku | +| Textova verze | longtext | bez vyplneni se vyrobi z HTML | +| Adresa pro odpovedi | text | prebije hodnotu z konektoru | + +Krok vraci `messageId`, `accepted` a `rejected`, takze se za nim da vetvit +podminkou na to, jestli server nekoho odmitl. + +Textova verze neni pridavek. Klient, ktery HTML nezobrazi, by dostal prazdnou +zpravu, a filtry nevyzadane posty berou chybejici textovou cast jako priznak +spamu. + +### HTML telo a dosazovani promennych + +Druh pole `html` je novy vedle `text` a `longtext` a znamena dve veci: builder +ho vykresli jako vysoke pole s neproporcionalnim pismem, a runtime v nem +**escapuje dosazene hodnoty**. + +Escapuje se hodnota, ne sablona. Znacky, ktere napsal autor sablony, jsou zamer; +ostre zavorky v hodnote od zakaznika ne. Bez toho by text ticketu s `` +prepsal rozvrzeni zpravy a `