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