# 99 - Zaznam zmen Nejnovejsi nahore. ## 2026-08-12 - soubor jako uloziste bez databaze Mockup se k databazi nedostane, takze pribyl treti rezim: JSON soubor. Prezije restart procesu i containeru, ale ne redeploy. ### Pridano - `src/data/snapshot.ts`: atomicky zapis (`.tmp` a prejmenovani), slucovani zapisu a dokonceni rozepsaneho zapisu pri `SIGTERM`. Rozbity soubor se prejmenuje na `.broken`, zaloguje a jede se s prazdnymi daty - aplikace, ktera nenastartuje, je pro AppFactory nefunkcni sluzba. - `src/data/connectors/local.ts`: jeden kod pro pamet i soubor, lisi se jen tim, kam se zapisuje. Nahrazuje `memory.ts` - dve implementace by se casem rozesly. - Klic k sifrovani se mimo databazi vygeneruje do `DATA_DIR/secrets.key`, takze sifrovani funguje bez nastaveni. U databaze se negeneruje: kdo ma zalohu tabulky, ma i klic ze stejneho stroje. - `DATA_DIR` v konfiguraci, `data/` v `.gitignore` a `.dockerignore`. - Hlaska v portalu rozlisuje tri nasledky: pamet (ztrata pri restartu), soubor (ztrata pri redeployi) a databaze (bez ztraty, hlaska se nezobrazuje). ### Overeno Bez databaze: konektor s vyplnenymi udaji prezil restart, v JSONu jsou hodnoty sifrovane a plaintext v nem neni. S databazi: rezim `postgres` funguje dal a klic vedle dat se nevygeneroval. ## 2026-08-12 - databaze pro konektory Konektory se ukladaji do Postgresu, pristupove udaje sifrovane. Popis v [14-databaze.md](14-databaze.md). ### Pridano - `pg` jako zavislost, pool v `src/db/pool.ts` vcetne transakci a `dbFor(tenantId)` jako sev pro budouci oddelenou databazi jednoho klienta. - Migrace ze souboru `src/db/migrations/*.sql`, pousti se pri startu pod `pg_advisory_lock` - pri rolling deployi je jinak pusti vsechny instance naraz. Jeden soubor je jedna transakce, takze pri chybe nevznikne rozdelane schema. - Sifrovani pristupovych udaju (`src/db/secretBox.ts`), AES-256-GCM s nahodnym IV a verzi klice. Nerozsifrovatelna hodnota nepada, chova se jako nevyplnena a zaloguje se - jeden rozbity konektor nesmi shodit seznam ostatnich. - Dve implementace uloziste konektoru za jednim rozhranim (`memory`, `postgres`). Rozhodnuti je jen na jednom miste, v `src/data/connectorStore.ts`. - `/health/ready` s pingem do databaze. `/health` na databazi zamerne nezavisi: kratky vypadek DB by jinak vedl k restartovani containeru. - `GET /api/dashboard/storage` a hlaska na strance Konektory o tom, ze data jsou jen v pameti. Bez toho se clovek divi, kam se podely jeho konektory. - Jediny vychozi konektor na firmu a sluzbu hlida castecny unikatni index, ne jen kod. Dva soubezne zapisy by jinak udelaly dva vychozi. ### Zmeneno - Cteni i zapis konektoru je asynchronni, vcetne validace stromu. - Databaze je volitelna. Bez `DATABASE_URL` nebo `SECRETS_KEY` se jede v pameti a rekne se to v logu i v portalu. Container, ktery nenastartuje, je pro AppFactory nefunkcni sluzba. - Kdyz jsou migrace nastavene a selzou, jede se dal v pameti. Psat do rozbiteho schematu je horsi nez neukladat. ### Overeno Proti Postgresu 16 v kontejneru: migrace, sifrovani v tabulce, preziti restartu, rozsifrovani spravnym klicem, degradace pri spatnem klici, `PATCH` bez tajneho pole, prepnuti a smazani vychoziho konektoru, a pametovy rezim bez `DATABASE_URL`. ## 2026-08-12 - transformace dat a oprava konektoru Transformace dat popsana v [13-transformace-dat.md](13-transformace-dat.md). ### Pridano - Kroky si predavaji i cele struktury. `FieldType` ma `object` a `list`. Do sablony se nedosazuji, predavaji se jako celek dalsimu kroku - proto je u nich v builderu vyber a ne textove pole. Z podminek nad nimi ma smysl jen "prisla / neprisla". - Strop na velikost struktury (`SCRIPT_MAX_VALUE_BYTES`, vychozi 256 kB). Radek s vystupem kroku je nejrychleji rostouci tabulka v systemu. - `src/scripts/mapping.ts`: cesty, prevody, pravidla a sablona JSON. Skripty ho dostanou na `ctx.util` jako `get`, `applyRules` a `fillJson`. - Dva rezimy transformace: `transform.map-fields` (pole na pole s prevody) a `transform.to-json` (sablona cileveho objektu s `${cesta}`). V sablone je marker `${...}` zamerne jiny nez `{{...}}`: sablony kroku se dosazuji driv, nez krok bezi, a stihly by ji prepsat. - Prevod `map` pro seznamy. Bez nej by slo prevest hlavicku dokladu, ale ne polozky objednavky, a doklad by byl na nulu. - `idoklad.create-invoice-from-object`: druha polovina prikladu, bere hotove telo dokladu z transformace. - Spoustec e-shopu predava celou objednavku jako objekt a polozky jako seznam. - Klikaci editor pravidel v builderu vcetne rezimu JSON pro vnorena pravidla. - Kontrola JSONu a tvaru pravidel uz pri ulozeni stromu. Preklep je nedodelek, ne chyba ukladani - rozdelana prace se nezahazuje. ### Opraveno - **Konektor neukladal pristupove udaje.** Server byl v poradku, chyba byla v prohlizeci: u pole `type="password"` prohlizec ignoruje `autocomplete="off"` a dosazuje ulozene prihlaseni. Uzivatel pak videl jednu hodnotu, React drzel jinou, a ulozilo se to, co drzel React, tedy nic. Resi to `autocomplete` `new-password`, jmena poli, ktera nepripominaji heslo, a prepinac zobrazeni, aby slo overit, co je opravdu zapsane. - Zakladani a uprava konektoru se presunuly do dialogu. Na strance jsou jen male karty - formulare rozlozene po strance byly u vic konektoru neprehledne. ## 2026-08-12 - sluzby a konektory Rozdeleni na sluzbu a konektor. Popis v [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md). Slovo "konektor" driv v kodu znamenalo katalog toho, co umime. Ted znamena napojeni jedne firmy, tedy to, co tim mysli i uzivatel. ### Pridano - `src/data/services.ts`: sluzba nese `general`, `appId`, `visibility`, `credentials` a `verifyPath`. Kategorie `obecne` sdruzuje veci, ktere ma kazdy a nepotrebuji konektor: webhook, planovac, tickety, transformace dat, HTTP pozadavek, pauza, log. - Viditelnost sluzby: vsichni, jen uvedene firmy a lide, nebo jen spravce platformy. Neviditelna sluzba se z API nevraci vubec. - `src/data/connectorStore.ts`: konektory za firmu vcetne hodnot pristupovych udaju. Hodnoty se z API nikdy nevraci, jen `filled` a `missing`. - `FlowStep.connectorId`: krok rika, pod kterym napojenim se ma volat. `null` = vychozi konektor firmy, takze vzorovy strom je prenositelny. - Overeni konektoru pres `verifyPath`, tedy cteci volani vyzadujici autorizaci. U sluzby bez nej se overi jen dostupnost a odpoved to rekne nahlas. - Stranky `/dashboard/sluzby` a `/dashboard/konektory` vcetne formularu udaju. - Endpointy `/api/dashboard/services` a CRUD `/api/dashboard/connectors` vcetne Swaggeru. - Predvyplnene prihlaseni spravcem platformy a prepinac demo uctu na login strance. Kvuli testovani prototypu, pred ostrym pouzitim odebrat. ### Zmeneno - **Pristupove udaje se prestaly cist z environment variables.** Cela instance by mela jedny udaje spolecne a dve firmy by fakturovaly z jednoho uctu. Z prostredi zustava jen `SERVICES_BASE_URL`. - Stav "napojeno" se prestal cist z katalogu a zacal pocitat z konektoru firmy. Sluzba ma jen `available` nebo `planned`. - Prejmenovani napric kodem: `Connector` na `Service`, `FlowStep.connectorId` na `serviceId`, `GET /connectors` na `GET /services`, `connectorIcons` na `serviceIcons`, stranka Konektory (katalog) na Sluzby. Prevodni tabulka je v dokumentu 12. - Validace stromu overuje i konektor. Cizi konektor je chyba, chybejici napojeni nedodelek. ## 2026-08-12 - skripty konektoru Naprogramovana vykonna cast konektoru. Popis je v [11-skripty-konektoru.md](11-skripty-konektoru.md). ### Pridano - `scripts/` se skripty konektoru. Jeden soubor nese manifest (vstupni a vystupni parametry) i kod. Obycejny JavaScript, aby se nemusel prekladat. - Hot reload podle casu zmeny souboru. Uprava v portalu i rucni uprava souboru se projevi bez restartu. - Kontrola vstupu i vystupu proti manifestu, jedna funkce pro obe strany. Chybejici povinny vystup je chyba skriptu, ne uzivatele. - `ctx` predavany skriptu: `http` nad adresou napojeni, `util`, `log`, `config`, `idempotencyKey`, `fail` a `retry`. Skript nedostane pristupove udaje. - Rozliseni opakovatelne a koncove chyby. Runner nikdy nevyhodi vyjimku, vzdy vraci vysledek vcetne `retryable`. - Redakce tajnych hodnot pred zapisem do logu. - Napojeni z environment variables (`src/scripts/connections.ts`) vcetne iDokladu. - Sest ukazkovych skriptu pro iDoklad proti skutecnemu API sluzby `services.csbot.cz/apps/idoklad`, kazdy na jiny vzor. - Stranka `/dashboard/skripty`: seznam, manifest, editor, zkusebni spusteni. Formular testu se sklada z manifestu, nepise se pro kazdy skript. - Endpointy `/api/dashboard/scripts`, `/:id`, `PUT /:id`, `/:id/test` a `/reload`. Vse ve Swaggeru. ### Zmeneno - Katalog konektoru uz neni jen staticky seznam. Akce ze skriptu se domeruji prekryvem v `src/data/connectors.ts`, takze se naraz objevi ve validaci stromu, ve vypoctu scope i v sablonach. Pri stejnem ID operace vyhrava skript. - `ConnectorOperation` ma `implementation` a `scriptId`. Katalog v portalu operace se skriptem oznacuje ikonou. - `ApiError` na klientovi nese cele telo odpovedi a umi z nej vytahnout `issues`. - Dockerfile kopiruje `scripts/` do vysledneho image. ### Vedome neudelano Skripty bezi v procesu serveru, ne v sandboxu. Jsou nase a prosly gitem. Zakaznicke skripty budou potrebovat izolovany engine ve vlastnim vlakne, duvod je v [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md). Ulozeni z portalu zapisuje do souboru v containeru. Bez trvaleho svazku ho redeploy vrati na verzi z gitu. ## 2026-08-12 - navrhy Pridany [09-navrh-rozsireni.md](09-navrh-rozsireni.md) a [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md). 09 popisuje datove modely: akce navazane na typ nebo tag ticketu s telem jako operaci, vlastnim stromem nebo skriptem, typy a tagy ticketu, role a prava jako data misto unionu, zalozky a zpristupneni konektoru za firmu, konektory rozdelene na definici, zpristupneni a napojeni, cekaci krok, sablony zprav, vlastni widgety se seskupovanim a prevod na Postgres. Soucasti je kontrola navrhu proti celemu prikladu se dvema firmami jednoho cloveka. 10 popisuje vykonnou cast: cestu udalosti od webhooku pres inbox a dispatcher k workeru, frontu v Postgresu se `SKIP LOCKED`, davkovy odber, spravedlnost mezi klienty, idempotenci, retence a rozpocet na 150 klientu ve dvou scenarich objemu. Nic z toho neni naprogramovane, oba dokumenty jsou navrh k rozhodnuti. Kod se nemenil. ## 2026-08-03 Tickety predelane na plnohodnotny konektor. Prestavaji byt polozkou v seznamu a stavaji se prichozim pozadavkem, ktery ma sveho cloveka a dohledatelny prubeh. Popis modelu je v [06-tickety.md](06-tickety.md). ### Pridano - Kanaly do ticketu: WhatsApp jako novy konektor, e-mail a hlasova linka jako plnohodnotne spoustece. - Konektor Tickety presunut do nove kategorie `servicedesk`, rozsiren o spoustece `created`, `unknown-customer`, `assigned`, `status-changed` a akce `assign`, `set-status`, `link-customer`. - Resitele (`src/data/people.ts`) oddelene od uzivatelu portalu, spojka e-mailem. - Log ticketu ve strome vcetne toho, co ktera volana sluzba vratila. - Prehled vytizeni tymu, kdo co ma u sebe, zaroven jako filtr seznamu. - Detail ticketu `/dashboard/tickety/:id`: prubeh a log, prirazeni, stav, zakaznik, komentare. - Filtry seznamu ticketu na serveru: resitel (vcetne `me` a `unassigned`), stav, kanal. - `providedFields` v katalogu konektoru: parametry, ktere spoustec predava sam. - Udalost `ticket.assigned` na sbernici i v portalu. - Endpointy `/api/dashboard/people`, `/tickets/workload`, `/tickets/:id` a POST varianty pro assign, status a comment. Vse ve Swaggeru. ### Zmeneno - `Ticket` ma misto volneho `requester` strukturovaneho `customer` s nullable `id` firmy v CRM, k tomu `channel`, `assignee` jako odkaz na cloveka a `automationId`. - `customer.id === null` je nosna informace, ne chybejici udaj. Prave na ni se pta podminka "mame zakaznika?" ve strome automatizace. - Simulace ticketu bere kanal a prepinac, jestli se zakaznik dohleda. Zakladany ticket dostane cely realisticky log. - Builder ukazuje parametry od sluzby jen ke cteni. Server je pri ulozeni vzdy dosadi z katalogu, a to jeste pred validaci stromu. ### Vedome neudelano Bugs a wishes zustavaji mimo. Vyvojarska agenda ma jiny zivotni cyklus a slucovat ji s tickety by znamenalo, ze ani jedna evidence nefunguje poradne. ### Doplneno pote Puvodni verze mela diru: ticket nemel zadny obsah a krok "Zalozit ticket" nesel nastavit. Slo tedy rict "z WhatsApp udelej ticket", ale ne uz co se ma kam ulozit. - `Ticket.body` a `Ticket.sourceRef`. Predmet je shrnuti, telo je cely text pozadavku. `body` vystaveno i ve spoustecich `created` a `unknown-customer`, takze na obsah ticketu jde udelat podminka v navazne automatizaci. - Nastavitelna pole akci (`OperationField` a `FlowStep.inputs`). Ticket, e-mail a WhatsApp maji skutecna pole misto pouhe napovedy. - Sablony `{{parametr}}` v hodnotach poli (`src/data/templates.ts`) vcetne nabidky parametru, ktera je vklada na pozici kurzoru. - Vyber resitele u akci se plni ze seznamu lidi, ne z rucne psaneho ID. - Nevyplnene povinne pole a odkaz na neexistujici parametr se hlasi jako nedodelek. Nastaveni pole, ktere akce nema, je chyba 400. - Akce bez `inputs` to v builderu napisou primo na karte kroku. ### Vystupy kroku a predvalidace Druha dira: kroky slo vkladat kamkoliv, ale podminka videla jen parametry spoustece. Slo tedy pridat krok "zeptej se CRM", ale ne se vetvit podle toho, co vratil. Bez toho byla predvalidace k nicemu. - `outputFields` v katalogu: co akce vrati dalsim krokum. Ma je "Dohledat firmu" (`customerKnown`, `companyId`, `companyName`), "Zaradit do kategorie", "Zalozit obchodni pripad" i "Zalozit ticket". - Nova akce RAYNET "Dohledat firmu". Nic nezaklada, jen odpovi, jestli odesilatele zname. Presne pro predvalidaci. - `src/data/flowScope.ts` pocita, co je videt v kterem miste stromu. Krok vidi spoustec plus vystupy kroku pred nim. Vetev nepridava nic do sekvence za podminkou, protoze nemusela probehnout. - Builder nabizi v podmince i v polich akce presne ty parametry, ktere v danem miste doopravdy jsou. - Odkaz na parametr, ktery ve strome neni, je chyba 400. Odkaz na parametr, ktery vznika az pozdeji, je nedodelek s radou posunout podminku niz. - Duplicitni jmeno parametru ve scope je nedodelek. V sablone by nesl poznat, ktery se dosadi. ### Kanaly a vzorove automatizace - Konektory Facebook Messenger a Instagram, kanaly `facebook` a `instagram` u ticketu. - Ctyri nove vzorove automatizace v rozdeleni, ktere odpovida zameru: jedna na kanal pro prijem, jedna spolecna pro smerovani na resitele. Prijmove zamerne neprirazuji, smerovani si ticket prevezme a podminkou `assigned neni splneno` neprepise rucni rozhodnuti. ### Firmy a prava Treti a nejvazneji dira: portal nemel zadnou tenanci. Kterykoliv prihlaseny uzivatel videl vsechny tickety vsech firem a cely seznam resitelu, `requireRole` se nikde nevolal. Popis v [07-firmy-a-prava.md](07-firmy-a-prava.md). - Tenant jako hranice viditelnosti. `tenantId` na ticketu, resiteli i automatizaci. - Uzivatel muze patrit do **vic firem**, v kazde s jinou roli (`memberships`). Pristup napric firmami je zvlast jako `platformAdmin`. - Tri pohledy na tickety: `all`, `tenant`, `mine`. Admin mezi nimi prepina, vcetne vyberu firmy. - `src/data/access.ts` jako jedine misto, kde se rozhoduje o pravech. `GET /api/dashboard/access` rekne klientovi, co smi kreslit. - Filtr na firmu je v ulozistich **povinny argument**. Zapomenuty filtr neznamena "vse", ale nezkompiluje se. - Nikdy tise nezuzujeme. Cizi firma vraci 403 nebo 404. - Prirazeni jen v ramci firmy. Prehazovat praci mezi lidmi smi jen admin, agent si smi vzit ticket na sebe. - `requireRole` nahrazen `requirePlatformAdmin`. Prava uvnitr firmy resi `access.ts`, protoze zavisi na tom, ktera firma pozadavek zajima. - Demo ucty pokryvaji vsechny tri situace vcetne cloveka ve dvou firmach. ### Nastavitelny dashboard Prehled byl pevne dany. Ted si ho kazdy sklada sam, popis v [08-dashboard-widgety.md](08-dashboard-widgety.md). - Katalog widgetu na serveru vcetne toho, ktere sirky ktery widget unese. Graf v tretine sloupce se necte, proto se tam ani nenabizi. - Rezim uprav na `/dashboard`: pridani z nabidky, vyber sirky, poradi sipkami, odebrani. Ulozi se az tlacitkem, Zrusit vrati puvodni stav. - Rozlozeni se uklada za **dvojici uzivatel a firma**, ne jen za uzivatele. - Data nacita prehled a rozdava je widgetum. Deset dlazdic tak neznamena deset stejnych dotazu. - Server rozlozeni overuje proti katalogu. Neznamy widget nebo nepodporovana sirka se neulozi, widget zmizely z katalogu se v prehledu ukaze jako chyba, ne ze tise zmizi. ### Zapsano jako otevrene rozhodnuti Vsechny automatizace se stejnym spoustecem se spusti. Doporucene rozdeleni na to nenarazi, ale az se bude psat runtime, musi se to rozhodnout vedome. Varianty a doporuceni v [06-tickety.md](06-tickety.md). ## 2026-07-31 Prvni nasazeni aplikace do repozitare csbot-prototype. Puvodni sablona byla holy Express s endpointy `/` a `/health`. Nahradil ji kompletni web a klientsky portal. ### Pridano - Verejny web: homepage se sekcemi, sluzby, o nas, kontakt s formularem, 404. - Prihlaseni pres JWT s demo ucty. - Klientsky portal: prehled s grafem, tickety, incidenty, automatizace, konektory, nastaveni. - Builder automatizaci: strom akci, spoustec, vetveni podminkou. - Katalog 25 konektoru v 8 kategoriich. - Webhook s registrovanou adresou, token generuje server. - Zivy dashboard pres SSE, vcetne indikatoru spojeni a bublin s udalostmi. - Simulace provozu pod tlacitkem v postrannim menu portalu. - Swagger UI na `/docs` a OpenAPI definice na `/openapi.json`. - Dokumentace ve slozce `documentation/`. - `.gitignore`, ktery drzi `node_modules` a `dist` mimo repozitar. ### Zmeneno oproti sablone - Aplikace prepnuta na ESM (`"type": "module"`) a `module: NodeNext`. - Jeden container obsluhuje API i zbuildovanou React aplikaci z `dist/public`. - Dockerfile buildu je server i web, vysledny image dostava jen `dist`. - Port zustava 3000, naslouchani na `0.0.0.0` beze zmeny. ### Reseni reverse proxy - `ROOT_PATH` se cte z prostredi, nikde neni hardcoded. - Aplikace se mountuje na koren i na prefix, funguje tedy at Caddy prefix odstrani nebo ne. - Server vklada do `index.html` znacku `` a `window.__BASE_PATH__`, aby SPA nasla soubory i na vnorenych cestach. - OpenAPI `servers` obsahuje prefix, takze Swagger Try it out vola spravnou adresu. - Router ma `strict: true`, jinak by se presmerovani `/docs` na `/docs/` zacyklilo. ### Overeno lokalne S `ROOT_PATH=/apps/csbot-prototype`: - `/apps/csbot-prototype/health` i `/health` vraci 200, - `/apps/csbot-prototype/docs` presmeruje na `/docs/`, ta vraci Swagger UI, - `swagger-ui.css` se nacte pres prefix, - OpenAPI `servers` obsahuje `/apps/csbot-prototype`, - `index.html` na vnorene ceste obsahuje spravny ``, - prihlaseni pres prefix vraci token, - neexistujici cesta pod `/api` vraci JSON, ne HTML aplikace. ### Znama omezeni Data jsou v pameti, restart je vrati do vychoziho stavu. Obsah webu je ukazkovy. Ulozeny strom automatizace se nevykonava.