Realne sluzby, OpenAI, odesilani e-mailu a helpdesk
Katalog srovnany s tim, co opravdu bezi na services.csbot.cz/apps: trinact sluzeb dostalo pristupove udaje a levne cteci overeni, opravena appId, ktera nikam nevedla (ppl, microsoft365, transcription), a GA4, Search Console, Google Ads i Sklik ted stoji na aplikaci analytics, kazda s vlastnimi udaji. Nove sluzby SAP Business One, Google Workspace a Meta Ads. K tomu 23 skriptu, ktere s nimi opravdu neco delaji. OpenAI jako prvni sluzba, ktera nebezi u nas: Service.baseUrl s absolutni adresou, prepis pres <SLUZBA>_BASE_URL nebo adresu u konektoru, predpona hlavicky u pole udaju (uzivatel vlepi holy klic, Bearer dopise runtime). Dotaz na model, nahrani souboru, otazka nad souborem, prepis zvuku. Skript umi odeslat soubor pres ctx.http.postForm (multipart, obsah Base64). Sluzba E-mail pres SMTP. Neni to skript, ale vnitrni krok - SMTP neni HTTP. Konektor nese schranku firmy, krok ma HTML telo, ve kterem se dosazene hodnoty escapuji (znacky autora sablony jsou zamer, ostre zavorky od zakaznika ne). Overeni konektoru se prihlasi na server a nic neodesle. Helpdesk: Ticket.helpdeskSourceId drzi firmu, ktera pozadavek poslala, vlastnikem zustava ta, ktera ho resi - jinak by ho resitel nemel ve sve fronte. Komu pozadavek pripadne, urcuje Tenant.helpdeskProviderId. Zadavatel vidi jen svoje pozadavky a smi k nim pripsat komentar. Opravy v portalu: - hlasky o ulozisti a odchozi IP vidi jen spravce platformy - typ ticketu se v automatizaci vybira ze seznamu firmy, nebo dosadi z dat - stav ticketu je otevreny naseptavac, ne ciselnik - ticket jde zalozit rucne, zakaznik u nej neni povinny - kanal se prejmenoval a parametry u webhooku jsou oznacene jako nepovinne - srovnane markdown tabulky v cele dokumentaci Co z teto davky jeste neni: prepinac firmy je porad jen stav uvnitr stranky Prehled, takze se prepnuti neprojevi v Lidech ani jinde. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b25a149574
commit
a771834e57
@@ -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 |
|
||||
|
||||
@@ -77,12 +77,12 @@ AppFactory. Z aplikacniho repozitare se infrastruktura nemeni, viz AGENTS.md.
|
||||
|
||||
## Povinne endpointy
|
||||
|
||||
| Verejna cesta | Vraci |
|
||||
| ------------------------------ | -------------------------------------- |
|
||||
| `/apps/<app-id>/health` | `{"status":"ok","uptimeSec":N}` |
|
||||
| `/apps/<app-id>/docs` | presmeruje na `/docs/` |
|
||||
| `/apps/<app-id>/docs/` | Swagger UI |
|
||||
| `/apps/<app-id>/openapi.json` | OpenAPI definice |
|
||||
| Verejna cesta | Vraci |
|
||||
| ----------------------------- | ------------------------------- |
|
||||
| `/apps/<app-id>/health` | `{"status":"ok","uptimeSec":N}` |
|
||||
| `/apps/<app-id>/docs` | presmeruje na `/docs/` |
|
||||
| `/apps/<app-id>/docs/` | Swagger UI |
|
||||
| `/apps/<app-id>/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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+32
-32
@@ -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 <token>`:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+46
-46
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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:<id>` | 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:<id>` | 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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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` |
|
||||
| `<SLUZBA>_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** |
|
||||
| `<SLUZBA>_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 |
|
||||
|
||||
@@ -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 |
|
||||
| ------------------- | -------------- | --------------------------------- |
|
||||
| `<SLUZBA>_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 <klic>` 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/<sluzba>.<operace>.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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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<T>(kind)` | `src/data/store/index.ts` | Založí úložiště pro nový druh záznamu. Jeden řádek na entitu. |
|
||||
| `initStores({databaseReady})` | `src/data/store/index.ts` | Vybere režim. Volá se jednou při startu, nikde jinde. |
|
||||
| `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<T>(kind)` | `src/data/store/index.ts` | Založí úložiště pro nový druh záznamu. Jeden řádek na entitu. |
|
||||
| `initStores({databaseReady})` | `src/data/store/index.ts` | Vybere režim. Volá se jednou při startu, nikde jinde. |
|
||||
| `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 `<SLUZBA>_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<T>` | `lib/api.ts` | Jediná cesta na API: base path, token, `ApiError` s celým hlášením ze serveru. |
|
||||
| `useApiQuery<T>` | `lib/useApiQuery.ts` | Načtení dat do stránky včetně `reload`. S `body` pošle POST (dávkové načtení), s `enabled: false` se neptá vůbec. |
|
||||
| `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<T>` | `lib/api.ts` | Jediná cesta na API: base path, token, `ApiError` s celým hlášením ze serveru. |
|
||||
| `useApiQuery<T>` | `lib/useApiQuery.ts` | Načtení dat do stránky včetně `reload`. S `body` pošle POST (dávkové načtení), s `enabled: false` se neptá vůbec. |
|
||||
| `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
|
||||
|
||||
|
||||
@@ -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í
|
||||
|
||||
@@ -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<T>` a dvě implementace. Volající nepozná, jestli běží Postgres nebo JSON soubor. |
|
||||
| API | `src/routes/crud.ts` | `crudRouter` vyrobí pětici endpointů včetně práva a auditu. |
|
||||
| Klient | `components/dashboard/EntityAdmin.tsx` | Tabulka, modál, validace, mazání. |
|
||||
| Vrstva | Kde | Co dělá |
|
||||
| -------- | -------------------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| Úložiště | `src/data/store/` | `EntityStore<T>` a dvě implementace. Volající nepozná, jestli běží Postgres nebo JSON soubor. |
|
||||
| API | `src/routes/crud.ts` | `crudRouter` vyrobí pětici endpointů včetně práva a auditu. |
|
||||
| Klient | `components/dashboard/EntityAdmin.tsx` | Tabulka, modál, validace, mazání. |
|
||||
|
||||
Nová entita v nastavení pak znamená: `defineStore` v modulu entity, jeden řádek
|
||||
v `bootstrap.ts`, jeden `crudRouter` v `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ý.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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í"
|
||||
|
||||
@@ -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: `<SLUZBA>_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 <klic>`. 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 `<b>`
|
||||
prepsal rozvrzeni zpravy a `<script>` by se dostal prijemci do schranky.
|
||||
|
||||
Deje se to v `fillTemplates` v executoru, protoze jen tam je jeste videt, co
|
||||
byla sablona a co dosazena hodnota. Escapovat az vysledek nejde - v nem uz se
|
||||
to nerozlisi.
|
||||
|
||||
Druh pole se urcuje **vyslovne v katalogu**, ne odhadem podle nazvu. Hadat
|
||||
podle `id === 'html'` by fungovalo do prvni akce, ktera to pole pojmenuje jinak.
|
||||
|
||||
### Overeni konektoru
|
||||
|
||||
Misto cteciho volani se konektor **prihlasi na server** (`verify` v nodemaileru).
|
||||
Nic se neodesila, takze test nikomu nic nedorucí, a bez platneho hesla neprojde.
|
||||
|
||||
Hlaska rozlisuje, co se stalo, stejne jako u HTTP:
|
||||
|
||||
| Co server rekl | Co to znamena |
|
||||
| -------------------------- | -------------------------------------------------- |
|
||||
| `EAUTH`, kod 535 | udaje dostal a neuznal je, jde o uzivatele a heslo |
|
||||
| `EENVELOPE` | neprijal odesilatele nebo prijemce |
|
||||
| `ECONNECTION`, `ETIMEDOUT` | nespojilo se, tedy adresa, port nebo sifrovani |
|
||||
| kod 4xx | docasne odmitnuti, opakovani ma smysl |
|
||||
|
||||
**Odeslani e-mailu je jediny vnitrni krok, ktery smi rict "zkus to znovu".**
|
||||
Ostatni selhavaji na spatnem nastaveni, ktere se opakovanim nespravi. Nedostupny
|
||||
posmovni server ale za minutu bezet muze, kdezto spatne heslo bude spatne porad.
|
||||
|
||||
U schranek s dvoufazovym overenim musi byt v konektoru **heslo pro aplikaci**,
|
||||
ne heslo k uctu. Server na to odpovi obycejnym 535 a hlaska to proto rika sama.
|
||||
|
||||
## Soubory ve skriptech
|
||||
|
||||
Soubor prochazi krokem stromu jako **Base64 retezec**. Duvod: parametr skriptu
|
||||
je vzdy hodnota, kterou jde zapsat do JSONu, protoze se uklada do zaznamu behu.
|
||||
Binarni data by se tam nevesla.
|
||||
|
||||
Odesila se pres `ctx.http.postForm`, ktery slozi `multipart/form-data`:
|
||||
|
||||
```js
|
||||
await ctx.http.postForm('/files', {
|
||||
purpose: 'user_data',
|
||||
file: { filename: 'faktura.pdf', base64: inputs.obsah, contentType: 'application/pdf' },
|
||||
});
|
||||
```
|
||||
|
||||
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. Je zamerne nizsi nez u cizich
|
||||
sluzeb: OpenAI zvladne stovky megabajtu, nas zaznam behu ne.
|
||||
|
||||
## Skripty, ktere k realnym sluzbam existuji
|
||||
|
||||
| Skript | Co dela |
|
||||
| ------------------------------ | ------------------------------------------------ |
|
||||
| `raynet.find-company` | dohleda firmu, nenalezeno neni chyba |
|
||||
| `raynet.upsert-contact` | najde podle e-mailu a doplni, jinak zalozi |
|
||||
| `raynet.create-lead` | zalozi poptavku, volitelne s vazbou na firmu |
|
||||
| `csob.list-transactions` | prelozi cislo uctu na ID banky a stahne pohyby |
|
||||
| `sap-bo.find-business-partner` | hleda pres OData filtr, apostrof se zdvojuje |
|
||||
| `sap-bo.list-orders` | objednavky partnera nebo za obdobi |
|
||||
| `ppl.create-shipment` | zalozi zasilku a pocka na zpracovani davky |
|
||||
| `ppl.track` | stav zasilky, nenalezeno neni chyba |
|
||||
| `microsoft365.send-mail` | e-mail z konkretni schranky |
|
||||
| `microsoft365.create-event` | schuzka vcetne casove zony |
|
||||
| `google.send-email` | Gmail: sklada cely RFC 2822 e-mail |
|
||||
| `google.append-sheet-row` | pripise radek do tabulky, ID vytahne z odkazu |
|
||||
| `ga4.run-report` | srovna GA4 hlavicky a hodnoty na radky |
|
||||
| `search-console.run-report` | pojmenuje dimenze zpatky, CTR na procenta |
|
||||
| `google-ads.campaign-report` | GAQL, mikrojednotky na koruny |
|
||||
| `sklik.campaign-report` | RPC pole argumentu, halere na koruny |
|
||||
| `meta-ads.insights` | vykon reklam, cisla z textu na cisla |
|
||||
| `transcription.transcribe` | dva enginy a sloucení, s nahradnikem pri vypadku |
|
||||
| `openai.chat` | dotaz na model |
|
||||
| `openai.upload-file` | nahrani souboru pres multipart |
|
||||
| `openai.ask-about-file` | otazka nad souborem pres Responses API |
|
||||
| `openai.transcribe-audio` | prepis zvuku |
|
||||
| `openai.list-models` | co ucet umi |
|
||||
|
||||
E-mail v tabulce neni schvalne: neni to skript, ale vnitrni krok, viz vyse.
|
||||
|
||||
Skripty pro iDoklad jsou popsane v [11-skripty-konektoru.md](11-skripty-konektoru.md).
|
||||
|
||||
## Co chybi
|
||||
|
||||
| Chybi | Poznamka |
|
||||
| ---------------------------------- | ----------------------------------------------------------------- |
|
||||
| Spoustece ze skutecnych sluzeb | triggery v katalogu jsou zatim popis, ne kod |
|
||||
| Stazeni binarni odpovedi | `ctx.http` cte odpoved jako text, PDF etikety se nevraci |
|
||||
| Google: dohledani souboru na Disku | wrapper nema v OpenAPI parametry dotazu, neni jiste, co propousti |
|
||||
| OAuth toky (Google, CSOB, Meta) | token se zadava rucne a po vyprsení se rucne prepisuje |
|
||||
| Prilohy u e-mailu | krok posila jen telo, soubor zatim neprilozi |
|
||||
| Prijem e-mailu (IMAP) | spoustec Prijat e-mail je zatim jen popis v katalogu |
|
||||
| Overeni udaju u Prepisu hovoru | sluzba nema levne cteci volani, ktere by udaje otestovalo |
|
||||
+185
-12
@@ -2,6 +2,179 @@
|
||||
|
||||
Nejnovejsi nahore.
|
||||
|
||||
## 2026-08-26 - helpdesk: pozadavek, ktery vidi zadavatel i resitel
|
||||
|
||||
Zakaznik nemel jak poslat pozadavek. Ticket pritom patri jedne firme, kdezto
|
||||
u helpdesku figuruji dve - ta, ktera se pta, a ta, ktera to resi.
|
||||
|
||||
### Pridano
|
||||
|
||||
- **Pole `Ticket.helpdeskSourceId`** s firmou, ktera pozadavek poslala.
|
||||
Vlastnikem (`tenantId`) zustava ta, ktera ho **resi**. Zamerne tak: 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 sahnout na nic, co uz funguje.
|
||||
- **`Tenant.helpdeskProviderId`**, tedy komu firma posila pozadavky. Nastavuje
|
||||
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.
|
||||
- **Sekce Helpdesk** (`/dashboard/helpdesk`) a router
|
||||
`/api/dashboard/helpdesk`: seznam vlastnich pozadavku, zalozeni, detail
|
||||
s prubehem a komentar. Zamerne to **neni druhy seznam ticketu** - chybi tu
|
||||
filtry, prirazovani i fronta, protoze zadavatele nezajima, kdo to ma u sebe.
|
||||
- **Prava `helpdesk.view` a `helpdesk.create`.** Prideluje je admin te firmy
|
||||
pres role, stejne jako u ostatnich prav.
|
||||
|
||||
### Zmeneno
|
||||
|
||||
- **`listTickets` umi filtrovat podle zdroje** (`helpdeskSourceIds`). Vyplnene
|
||||
**nahrazuje** filtr podle vlastnika, protoze zadavatel vlastnikem neni.
|
||||
Bezny seznam ticketu se nezmenil.
|
||||
- **`getTicket` a `addComment` maji druhou cestu dovnitr.** Firma, ktera
|
||||
pozadavek poslala, ho smi cist a pripsat k nemu komentar, i kdyz ho nevlastni.
|
||||
Komentar je jedina zmena, kterou nad nim smi: stav, resitele a typ urcuje ten,
|
||||
kdo to resi.
|
||||
|
||||
## 2026-08-26 - portal prestal ukazovat provozni veci a ticket jde zalozit rucne
|
||||
|
||||
Sada oprav podle toho, co v portalu drhlo.
|
||||
|
||||
### Zmeneno
|
||||
|
||||
- **Hlasky o ulozisti a o odchozi IP adrese vidi jen spravce platformy.**
|
||||
Kam se uklada a z jake adresy volame ven resime my, ne zakaznik. "Data se
|
||||
ukladaji do souboru na serveru" na nej navic pusobi jako priznani, ze mu tu
|
||||
praci muzeme ztratit. Nemazou se, jen se schovavaji - nam poradi porad.
|
||||
Totez plati pro radek "volano z IP" v historii overeni.
|
||||
- **Typ ticketu se v automatizaci vybira ze seznamu.** Bylo to textove pole,
|
||||
do ktereho mel clovek opsat ID typu odjinud. Novy druh pole `lookup` je
|
||||
**ciselnik a volny text zaroven**: bud se vybere ze seznamu typu te firmy,
|
||||
nebo se hodnota dosadi z dat (`{{data.typ}}`). Typy ticketu jsou vlastnost
|
||||
firmy, takze nabidka chodi za tu, ve ktere clovek je.
|
||||
- **Stav ticketu je otevreny naseptavac, ne ciselnik.** Stav je volny retezec
|
||||
a vzdycky byl - ticket muze prijit z cizi aplikace s jejim vlastnim stavem.
|
||||
Vyber ze seznamu tomu odporoval. Ted je to textove pole s nabidkou toho, co
|
||||
firma uz pouziva (`GET /api/dashboard/tickets/statuses`), a nova hodnota
|
||||
projde stejne dobre. Zapisuje se az pri opusteni pole, ne po kazdem pismenu.
|
||||
- **"Kanal" se prejmenoval na "Odkud pozadavek prisel"** a rika o sobe, ze je
|
||||
nepovinny a slouzi jen k filtrovani a ikone v seznamu.
|
||||
- **Vstupni parametry u webhooku jsou oznacene jako nepovinne.** Webhook prijme
|
||||
cokoliv; rucne vypsany seznam parametru neni podminka, ale pohodli pro
|
||||
podminky - a vyplni se sam z vlepene ukazky tela. Prazdny seznam uz nehlasi,
|
||||
ze "bez nich nelze pridat podminku".
|
||||
|
||||
### Pridano
|
||||
|
||||
- **Zalozeni ticketu rucne** (`POST /api/dashboard/tickets`, pravo
|
||||
`ticket.create`, tlacitko Novy ticket na strance Tickety). Dosud ticket
|
||||
vznikal jen z automatizace nebo z prichozi udalosti, takze pozadavek prijaty
|
||||
telefonem nemel jak do systemu.
|
||||
- **Zakaznik u ticketu je nepovinny.** Rucne zalozeny ticket je casto ukol, ne
|
||||
pozadavek od nekoho zvenku; povinna firma a kontakt by znamenaly, ze si je
|
||||
clovek vymysli. Ve formulari je zakaznik schovany pod odkazem.
|
||||
|
||||
### Co z teze davky jeste neni
|
||||
|
||||
- **Sekce Helpdesk** pro zakaznika vcetne sdileni ticketu mezi admin firmou
|
||||
a tim, kdo ho zalozil.
|
||||
- **Prepinac firmy nad celym dashboardem.** Dnes je to stav uvnitr stranky
|
||||
Prehled, takze se prepnuti neprojevi v Lidech ani jinde - ostatni stranky
|
||||
volaji API bez `tenantId` a server pouzije vychozi firmu. Ma to doplnovat
|
||||
API vrstva na jednom miste, ne kazda stranka zvlast.
|
||||
|
||||
## 2026-08-26 - odesilani e-mailu ze schranky firmy
|
||||
|
||||
Sluzba E-mail byla v katalogu jako popis: bez udaju, bez vykonne casti. Ted se
|
||||
odesila doopravdy, ze schranky, kterou si firma vyplni v konektoru.
|
||||
|
||||
### Pridano
|
||||
|
||||
- **Sluzba E-mail pres SMTP.** V konektoru server, port, sifrovani, uzivatel,
|
||||
heslo, adresa a jmeno odesilatele a adresa pro odpovedi. Zadne udaje
|
||||
v prostredi - kazda firma odesila ze sve schranky.
|
||||
- **Vnitrni krok `email/send`.** SMTP neni HTTP a skript umi jen `ctx.http`,
|
||||
takze operaci vykonava vnitrni krok stejne jako zalozeni ticketu. Pristupove
|
||||
udaje pritom zustavaji v konektoru.
|
||||
- **Druh pole `html`.** Telo zpravy se pise jako HTML a builder ho vykresli
|
||||
jako vysoke pole s neproporcionalnim pismem. Runtime v nem **escapuje
|
||||
dosazene hodnoty**: znacky autora sablony jsou zamer, ostre zavorky
|
||||
v hodnote od zakaznika ne. Bez toho by text ticketu s `<b>` prepsal
|
||||
rozvrzeni zpravy a `<script>` by dosel prijemci do schranky.
|
||||
- **Overeni konektoru prihlasenim.** Sluzba s `transport: 'smtp'` se neoveruje
|
||||
ctecim volanim, ale prihlasenim na server. Nic se neodesila, takze test
|
||||
nikomu nic nedoruci, a bez platneho hesla neprojde.
|
||||
- **Vnitrni krok smi rict "zkus to znovu"** (`StepOutcome.retryable`). Dosud
|
||||
se opakovani u vnitrnich kroku vzdy vypinalo, protoze selhavaly na spatnem
|
||||
nastaveni. Nedostupny posmovni server ale za minutu bezet muze.
|
||||
- **Textova verze zpravy.** Bez vyplneni se vyrobi z HTML. Klient, ktery HTML
|
||||
nezobrazi, by jinak dostal prazdnou zpravu a filtry nevyzadane posty berou
|
||||
chybejici textovou cast jako priznak spamu.
|
||||
- **Zavislost `nodemailer`.** Vlastnorucne psany SMTP klient by byl 300 radku,
|
||||
ktere nejde bez schranky overit.
|
||||
|
||||
### Opraveno
|
||||
|
||||
- **SMTP server z konektoru nesmi mirit do vnitrni site.** Stejne pravidlo jako
|
||||
u HTTP (`isPrivateHost`), protoze adresu vyplnuje firma.
|
||||
|
||||
## 2026-08-26 - katalog stoji na sluzbach, ktere opravdu bezi, a pribyla OpenAI
|
||||
|
||||
Katalog popisoval, co chceme umet. U vetsiny sluzeb chybely pristupove udaje,
|
||||
takze konektor nemel co vyplnit, a `appId` u nekterych ukazovalo na aplikaci,
|
||||
ktera neexistuje. Zdroj pravdy o tom, co bezi, je `services.csbot.cz/apps`
|
||||
a jeji `/openapi.json`.
|
||||
|
||||
Novy dokument: [21-realne-sluzby.md](21-realne-sluzby.md).
|
||||
|
||||
### Zmeneno
|
||||
|
||||
- **Trinact sluzeb katalogu ma napojeni na bezici aplikaci** vcetne poli udaju
|
||||
a levneho cteciho volani na overeni. RAYNET, CSOB, PPL, Microsoft 365, GA4,
|
||||
Search Console, Google Ads, Sklik a Prepis hovoru mely `credentials: []`,
|
||||
takze konektor nesel vyplnit a test se nemel ceho chytit.
|
||||
- **Opravena `appId`, ktera nikam nevedla.** PPL bezi na `pplcplapi`, ne na
|
||||
`ppl`. Microsoft 365 na `microsoft-365-service`. Prepis hovoru na
|
||||
`audio-transcription`. GA4, Search Console, Google Ads a Sklik bezi vsechny
|
||||
na aplikaci `analytics`, kazdy s vlastnimi udaji - slucovat je do jedne
|
||||
sluzby by znamenalo, ze firma se samotnym Sklikem musi vyplnit i Google.
|
||||
- **Prepis hovoru ma jen operaci, kterou aplikace umi.** Operace "Vytvorit
|
||||
souhrn" za sebou nic nemela; souhrn ted udela OpenAI.
|
||||
- **Query ve `verifyPath` se do hlasky nedava.** Odrizne se stejne jako
|
||||
u `ScriptRequestInfo` - v query muze byt tajemstvi.
|
||||
|
||||
### Pridano
|
||||
|
||||
- **Sluzba OpenAI.** Firma zada svuj API klic do konektoru a muze se ptat
|
||||
modelu, poslat soubor a nechat si prepsat zvuk. Skripty `openai.chat`,
|
||||
`openai.upload-file`, `openai.ask-about-file`, `openai.transcribe-audio`
|
||||
a `openai.list-models`.
|
||||
- **Sluzba, ktera nebezi u nas.** `Service.baseUrl` nese absolutni adresu cizi
|
||||
sluzby. Skladat ji ze `SERVICES_BASE_URL` by nedavalo smysl, to je zaklad
|
||||
nasich aplikaci. Prepsat ji jde promennou `<SLUZBA>_BASE_URL` (dosud jen
|
||||
slibenou v dokumentaci, ted opravdu implementovanou) nebo adresou u konektoru,
|
||||
cimz vede cesta na Azure OpenAI.
|
||||
- **Predpona hlavicky u pole udaju** (`ServiceCredentialField.prefix`).
|
||||
OpenAI chce `Authorization: Bearer <klic>`; uzivatel vlepi holy klic a slovo
|
||||
pred nim dopise runtime. Preklep v rucne psanem `Bearer` by nesel najit ani
|
||||
zpetne, protoze se hodnota z API nevraci.
|
||||
- **Odesilani souboru ze skriptu** (`ctx.http.postForm`). Obsah prichazi jako
|
||||
Base64, protoze parametr kroku se uklada do zaznamu behu a binarni data by se
|
||||
tam nevesla. Strop `SCRIPT_MAX_UPLOAD_BYTES`, vychozi 10 MB.
|
||||
- **Sluzby SAP Business One, Google Workspace a Meta Ads.** Bezi, ale
|
||||
v katalogu nebyly. Meta Ads je zamerne oddelena od Facebook Messengeru
|
||||
a Instagramu: aplikace `meta` je nad Marketing API, tedy reklamy, a je jen
|
||||
pro cteni.
|
||||
- **Dvacet tri skriptu k realnym sluzbam**, od dohledani firmy v RAYNETu po vykon
|
||||
kampani ve Skliku. Seznam je v [21-realne-sluzby.md](21-realne-sluzby.md).
|
||||
|
||||
### Opraveno
|
||||
|
||||
- **Redakce tajemstvi bere i holou hodnotu.** Dosud se skrtaly jen hodnoty
|
||||
hlavicek, takze u `Authorization: Bearer sk-...` by samotne `sk-...` v chybove
|
||||
odpovedi proslo do logu. `targetSecrets` ted vraci obojí a pouziva ho
|
||||
i overeni konektoru, ktere si dosud sestavovalo seznam samo.
|
||||
|
||||
## 2026-08-25 - chybova hlaseni konektoru rikaji, co se stalo
|
||||
|
||||
"Pristup zamitnut: GET /apps/idoklad/account/agenda vratilo HTTP 403.
|
||||
@@ -143,12 +316,12 @@ Skript je **ciste prevod hodnot**: dostane `input`, vrati objekt. Nema `require`
|
||||
`import`, `process`, `fetch` ani `console`. Volani ven patri do kroku konektoru,
|
||||
ktery ma pristupove udaje, opakovani i zapis do logu.
|
||||
|
||||
| Pojistka | Proc |
|
||||
| --- | --- |
|
||||
| limit 2 s | zacykleny skript by jinak zablokoval workera vsem firmam |
|
||||
| vysledek do 256 kB | vetsi objekt uz stejne nikdo dal nezpracuje |
|
||||
| kod do 20 000 znaku | delsi uz neni prevod, ale aplikace |
|
||||
| zadny stav mezi behy | stav, ktery prezije beh, je zdroj nejhur hledanych chyb |
|
||||
| Pojistka | Proc |
|
||||
| -------------------- | -------------------------------------------------------- |
|
||||
| limit 2 s | zacykleny skript by jinak zablokoval workera vsem firmam |
|
||||
| vysledek do 256 kB | vetsi objekt uz stejne nikdo dal nezpracuje |
|
||||
| kod do 20 000 znaku | delsi uz neni prevod, ale aplikace |
|
||||
| zadny stav mezi behy | stav, ktery prezije beh, je zdroj nejhur hledanych chyb |
|
||||
|
||||
**Cim to neni.** `node:vm` neni bezpecnostni hranice proti nekomu, kdo se chce
|
||||
dostat ven. Je to izolace proti nehode a proti zacykleni. Skript pise spravce
|
||||
@@ -307,12 +480,12 @@ ukazovalo 373 za automatizaci, ktera bezi tri mesice.
|
||||
|
||||
### Nove soubory
|
||||
|
||||
| Soubor | Co dela |
|
||||
| --- | --- |
|
||||
| `src/data/invites.ts` | Entita pozvanky, kod, platnost. |
|
||||
| `src/routes/invites.ts` | Verejne cesty (prohlednuti a prijeti) a sprava. |
|
||||
| `web/src/pages/Invite.tsx` | Stranka za odkazem: jmeno, e-mail, heslo. |
|
||||
| `web/src/components/dashboard/InvitePanel.tsx` | Sprava pozvanek v zalozce Lide. |
|
||||
| Soubor | Co dela |
|
||||
| ---------------------------------------------- | ----------------------------------------------- |
|
||||
| `src/data/invites.ts` | Entita pozvanky, kod, platnost. |
|
||||
| `src/routes/invites.ts` | Verejne cesty (prohlednuti a prijeti) a sprava. |
|
||||
| `web/src/pages/Invite.tsx` | Stranka za odkazem: jmeno, e-mail, heslo. |
|
||||
| `web/src/components/dashboard/InvitePanel.tsx` | Sprava pozvanek v zalozce Lide. |
|
||||
|
||||
## 2026-08-13 - vyrizeno je vyslovny priznak, ne hadani ze stavu
|
||||
|
||||
|
||||
Reference in New Issue
Block a user