Commit Graph
36 Commits
Author SHA1 Message Date
JiriUhlirandClaude Opus 5 4545de8076 Odchozi IP adresa portalu
Kdyz cizi sluzba odmitne pristup, prvni otazka je, z jake adresy se vlastne
vola. Z containeru to videt neni, vidi to az protistrana, takze se zepta echo
sluzby podle EGRESS_IP_URL a vysledek se drzi v pameti po EGRESS_IP_TTL_MS.
Prazdna EGRESS_IP_URL funkci vypne, prepsat ji jde na vlastni echo pod svou
domenou.

Adresa je natvrdo na strance Konektory a u kazdeho odmitnuteho overeni v logu.
Pripojuje se jen u 401 a 403 - jinde nema co rict a nestoji za volani ven.

Neni to tajemstvi: kazda volana sluzba tuhle adresu stejne vidi.

Endpoint egress-ip je registrovany pred GET /:id, jinak by ho router vzal
jako id konektoru.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 07:22:26 +02:00
JiriUhlirandClaude Opus 5 ab88979627 Dialog portalem do body, hlavicky odpovedi u chyby
Dialog se vykresloval uvnitr karty konektoru misto pres obrazovku. Samo
position: fixed nestaci: rodic s backdrop-filter (nase .glass, tedy skoro
kazdy panel a karta) je pro fixed potomka containing block. Modal proto jde
portalem do document.body. Tykalo se to vsech dialogu, videt to bylo az
u Logu, ktere jsou v male karte.

K chybe se zapisuji vybrane hlavicky odpovedi: server, via, content-type,
www-authenticate, retry-after, x-request-id, date. Rikaji, kdo odpoved vydal.
Server: Kestrel je sama aplikace, Via: 1.1 Caddy proxy pred ni. U 403 od proxy
byva telo prazdne a bez hlavicek by nezbylo vubec nic. Allowlist, ne vsechno:
Set-Cookie a podobne do zaznamu nepatri.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 07:15:22 +02:00
JiriUhlirandClaude Opus 5 5a124a53d8 Chybova hlaseni konektoru rikaji, co se stalo, a kam to slo
Duvod od sluzby jde primo do hlasky: z tela odpovedi se vytahne detail,
error_description, message, title, error i seznam missingHeaders. Retezec,
ktery vypada jako JSON, se rozbaluje dal - sluzba iDoklad presne takhle
predava telo od iDokladu samotneho. Kdyz sluzba nenapsala nic, rekne se to.

401 a 403 uz nejsou jedna hlaska. 401 = udaje sluzba dostala a neuznala je.
403 = tvar udaju je v poradku, zakazuje se samo volani.

V kazde hlasce je cela adresa vcetne serveru (ScriptRequestInfo.url),
bez query - v query muze byt tajemstvi. Zaklad adresy je z konfigurace
a konektor ho smi prepsat, takze se neda odvodit z toho, kde je nasazeny
portal. Adresa je videt i na karte konektoru a v odpovedi na test, i kdyz
overeni projde.

Tlacitko Logy na karte konektoru a historie poslednich peti overeni.
Odpoved sluzby dosud existovala jen v odpovedi na test, tedy do prekresleni
stranky, a v logu containeru. Do logu containeru se nikdo divat nechodi.
Zaznam se uklada i pri uspechu, jinak by neslo poznat, jestli konektor nesel
nikdy, nebo prestal jit ve chvili, kdy nekdo sahnul na udaje.

Migrace 003_connector_checks.sql, endpoint GET /connectors/:id/checks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 07:06:14 +02:00
JiriUhlirandClaude Opus 5 6297bbf480 Vystup z vetve plati i za podminkou, samostatny krok Zalozit kontakt
Bezny postup nesel poskladat z kroku: najdi podle ICO, kdyz neni zkus mail,
kdyz porad neni zaloz - ID dava jednou jedna vetev a jednou druha, ale
rozsah vystupy z vetvi za podminku nepoustel. Slucovat kvuli tomu hledani
a zakladani do jednoho kroku bylo obejiti nasi chyby, ne reseni.

- Vystup z vetve je za podminkou k dispozici, jen jako nepovinny. Ze muze
  chybet, se neztratilo: builder to u pole ukaze a pri behu se dosadi
  prazdno.
- Vystup se stejnym jmenem uz z nabidky nemaze ten starsi. Po druhem
  hledani kontaktu zmizelo ID z prvniho, tedy to, co je v tu chvili
  potreba. Odkaz se jmenem kroku je jednoznacny.
- Duplicitni jmena u vystupu kroku uz nejsou nedodelek. Konflikt zustava
  mezi parametry spoustece, kde zadny prefix neni.
- Novy krok Zalozit kontakt. Nic nedohledava, hledani je vlastni krok.
  Najit nebo zalozit zustava pro toho, komu staci jistota jednim krokem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 09:01:57 +02:00
JiriUhlirandClaude Opus 5 c62902bfb6 Polozky dokladu se najdou pod obema jmeny
iDoklad ceka v tele `IssuedInvoiceItems`, ale prevod je casto pojmenuje
`items` podle toho, jak se jmenuji ve zdrojove objednavce. Kontrola trvala
na `items`, telo se ale posilalo tak, jak prislo - prevod s PascalCase
jmeny by tedy neprosel kontrolou, a prevod s malymi pismeny by prosel
a doklad by v iDokladu vznikl bez radku.

Ted se polozky hledaji pod obema jmeny a do tela se doplni pod tim, ktere
ceka iDoklad.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 08:53:24 +02:00
JiriUhlirandClaude Opus 5 13c0c03ed4 Krok Najit nebo zalozit kontakt a druhy vstup vlastniho skriptu
Chybelo k tomu, aby sla postavit cela cesta objednavka -> faktura.

- Novy skript idoklad.upsert-contact: dohleda odberatele podle ICO nebo
  e-mailu, a kdyz neni, zalozi ho a vrati nove ID. Vystup `created` rekne,
  co se stalo, takze na to jde navazat podminkou.
  Proc jeden krok a ne dvojice ve vetvich podminky: vetev nepridava nic do
  sekvence za podminkou, protoze nemusela probehnout. ID odberatele by za
  podminkou nebylo k dispozici, i kdyz ho ve skutecnosti daji obe vetve.
  Zaklada se az kdyz hledani nic nenaslo - opacne poradi by pri kazde
  objednavce vyrobilo dalsiho odberatele se stejnym ICO.
- Krok Vlastni skript ma druhy vstup "Co pridat ke vstupu". Skript casto
  potrebuje krome hlavniho objektu jednu hodnotu z predchoziho kroku,
  typicky ID odberatele. Prijde v input.extra, aby se hlavni data nemichala
  s tim, co jsme doplnili my.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 08:50:03 +02:00
JiriUhlirandClaude Opus 5 e4779caf82 Vlastni skripty firmy: prevod dat v JS, v logu vstup i vystup
Klikaci pravidla jsou u peti poli rychlejsi, ale u modelu objednavky je jich
dvacet a v tom se necte. Vedle nich proto skript firmy: prevod z A do B
napsany v JS, jeden na zakaznika.

- Skripty se ukladaji do uloziste, ne na disk. Disk je uvnitr kontejneru
  a redeploy ho vymaze.
- Krok Transformace dat - Vlastni skript. Vysledek jde dal jako krok.result.
- V logu ticketu je u kroku vstup i vystup. Prave to byl duvod, proc skript
  nad pravidly vyhral.
- Zkouska bez ulozeni: v portalu se vlepi skutecne telo a hned je videt, co
  z toho leze.
- Skripty jsou v zalozce Akce, vedle definic akci. Obojí je popis toho, co
  aplikace ve firme umi, a spravuje to tentyz clovek.

Skript je ciste prevod hodnot: dostane input, vrati objekt. Nema require,
import, process, fetch ani console, bezi nejvys 2 s a vysledek se vejde do
256 kB. node:vm neni bezpecnostni hranice proti nekomu, kdo se chce dostat
ven - je to izolace proti nehode a proti zacykleni.

Pri zkousce se ukazalo, ze casovy limit nepokryval samotny beh: runInContext
jen vyrobil funkci a zavolat ji zvenku znamenalo, ze while (true) uvnitr
zablokovalo proces navzdy. Kod se ted vola uvnitr runInContext.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 08:08:09 +02:00
JiriUhlirandClaude Opus 5 99561c4682 Pauza, zapis do logu a podminky nad vystupy konecne funguji
Vyslo najevo pri stavbe ukazkovych automatizaci nad modelem objednavky.

- log/write a delay/wait nemely vykonnou cast. Byly registrovane jen pod
  klici flow/log a flow/pause, ktere v katalogu nejsou. Kdo si v builderu
  vybral Zapsat zpravu nebo Pockat, dostal pri behu "operace nema vykonnou
  cast" - krok slo pridat, ale nikdy nefungoval.
- Obe operace navic nemely nastavitelna pole, takze do nich neslo napsat,
  co se ma zapsat a jak dlouho cekat.
- Podminka nad vystupem kroku se nevyhodnotila. Podminka si drzi ID
  parametru, aby ji prejmenovani nerozbilo, ale data chodi pod jmenem:
  parametr f_9x1 nese hodnotu z klice callSid, vystup kroku
  st_kontakt.idoklad.found lezi pod found. Beh dostava tabulku jmen podle
  ID, takze se ma o co oprit. Bez toho podminka tise vychazela jako prazdna
  a slo se vzdy vetvi NE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 07:04:30 +02:00
JiriUhlirandClaude Opus 5 9b18531d3e Prace nad celym modelem: cesty, ukazka tela a smycka nad seznamem
Odesilatel posila cely model. Objednavka ze Shoptetu ma zanoreni, ceny
v podobjektech a seznam polozek - a dosud sel napojit jen plochy seznam
skalarnich poli, takze items[] neslo pouzit vubec.

- Odkaz v sablone muze byt cesta: {{data.order.billingAddress.city}},
  {{data.order.items[0].name}}, {{st_faktura.invoiceId}}. Overuje se prvni
  cast odkazu, takze ploche odkazy funguji dal presne jako driv.
- Cele telo je v kontextu i v puvodnim tvaru. Deklarovane parametry maji
  pri shode jmen prednost.
- Spoustec si pamatuje ukazku skutecneho tela. Server z ni odvodi model,
  tedy seznam cest i s typy, a ten se v krocich klika misto opisovani.
  Tlacitko doplni z hodnot v ukazce parametry pro podminky.
- Novy krok Pro kazdou polozku: projde seznam a za kazdou polozku vykona
  vnoreny podstrom. Uvnitr je item a index, po skonceni krok.results se
  seznamem vysledku. Kazdy vysledek nese i puvodni polozku - radek
  objednavky potrebuje jak ID z CRM, tak mnozstvi z puvodnich dat.
  Strop 200 polozek, mimo seznam krok selze s tim, co tam misto nej je.
- Prevod Za kazdou polozku seznamu v klikacim editoru mapovani. Engine ho
  umel, sel ale napsat jen rucnim JSONem.
- Typograficke uvozovky a sipka z kodu pryc.

Overeno nad skutecnym modelem objednavky: 23 cest vcetne
data.order.items[].unitPrice.withoutVat, sablony s cestou i s indexem,
smycka nad dvema polozkami s posbiranymi vysledky.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 06:53:10 +02:00
JiriUhlirandClaude Opus 5 fc0c2ff6c7 Opakovani do logu nepise a ticket se rodi s poslanym stavem
Overeno proti serveru: tri shodne POSTy udelaly jednu udalost s pocitadlem 3,
ale krok se do logu porad zapsal trikrat. Zadani bylo, ze opakovani ma byt
informace, ne dalsi radek.

- Krok muze rict quiet a jeho radek se do logu ticketu nezapise.
- Ticket vznika uz s poslanym stavem. Predtim se zalozil s vychozim "Novy"
  a hned se prepsal, takze v logu stalo "stav Novy -> completed" u ticketu,
  ktery v tom stavu nikdy nebyl. Odtud i to "Novy" ve widgetu.
- Zaznam zmen: celkovy pocet behu se pocita od zavedeni historie po dnech,
  puvodni citac se den ode dne nedelil a rozpocitat ho zpetne neni z ceho.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:14:20 +02:00
JiriUhlirandClaude Opus 5 f1e8253169 Log rekne co zpusobilo jakou zmenu, prevzeti ticketu a pozvanky
Nalezeno na bezicim serveru: TK-4946 mel 177 udalosti a 620 radku logu,
pritom se skoro nic nestalo. Zmereno proti fronte: ve stejnem okne vzniklo
presne tolik behu, kolik prislo udalosti (22 a 22), kazdy s jednim pokusem.
Fronta nenasobi nic, odesilatel poslal 177 POSTu. Nase vina byla, ze to
z historie neslo poznat.

- Data udalosti se ukladaji. Kdyz krok nema vlastni, ulozi se to, cim beh
  zacal - u webhooku prijate telo. Prazdna udalost je horsi nez zadna.
- Shodna udalost se pocita (repeats, lastAt), nezaklada dalsi radek. Ticket
  se pritom nemeni, takze duplikat nerozblika dashboard ani nespusti
  automatizaci na zmenu ticketu. Zahodit ji nejde, jinak by nikdo nezjistil,
  ze proti nam neco tluce.
- Zmeny se radi pod udalost, ktera je zpusobila, a u udalosti stoji jmeno
  automatizace. Log se cte jako "prislo tohle -> zmenilo to tohle".
- Poznamka o stavu jen kdyz se stav zmenil. "z in-progress na in-progress"
  u kazde zpravy byl zdroj tech 620 radku.
- runsToday konecne znamena dnes: behy po dnech, k tomu vcera a celkem.
  Dosud to byl citac od zalozeni automatizace, jen se jmenoval "dnes".

Vedle toho prace, o kterou slo predtim:
- Prevzeti ticketu ze skupiny (POST /tickets/:id/claim) a krok Predat skupine
  s prepinacem automatickeho prideleni nejvolnejsimu.
- Pozvanky do firmy: odkaz s nahodnym kodem, heslo si nastavi pozvany.
- Resitele, skupiny a pozvanky presunuty z Nastaveni do zalozky Lide, cleny
  skupiny se vybiraji klikanim.
- Ctyri AI znaky, ktere zbyvaly v kodu, pryc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 15:54:34 +02:00
JiriUhlirandClaude Opus 5 fbe5b8ff6f Vyrizeno je vyslovny priznak, ne hadani ze stavu
`Ticket.closed` se nastavuje vyslovne. Predchozi verze ho odvozovala ze jmena
stavu (completed, vyreseno, ...), coz nikdo nechtel a hlavne to uhodne spatne
pokazde, kdyz si nekdo pojmenuje stavy po svem. `TicketType.closedStatuses`
zruseno, byla to tatáz obchazka o uroven vys.

Kroky `ticket/upsert` a `ticket/set-status` maji vstup Vyrizeny s trema stavy:
ano, ne, prazdne. Prazdne znamena nemenit - jinak by kazda zmena textu stavu
mimochodem otevrela vyrizeny ticket. `POST /tickets/:id/status` prijima
`closed` jako nepovinny bool a podminka ve strome se na nej muze zeptat.

Overeno 8 kontrolami: ticket se stavem completed neni automaticky vyrizeny,
dokud to nekdo nerekne. Automatizace s podminkou status = completed zabere na
ticketu, ktery do toho stavu prejde, ale na uz existujici tickety nesahne -
spousti ji udalost, ne stav.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 07:38:20 +02:00
JiriUhlirandClaude Opus 5 202d525905 Stav ticketu je volny retezec, ciselnik pryc
Ciselnik new/open/waiting/resolved je pryc. Tickety chodi z cizich aplikaci,
ktere maji svoje stavy - voicebot posila ringing a completed. Nutit je do nasi
ctverice znamenalo, ze u ticketu svitilo "Novy", i kdyz byl podle odesilatele
davno hotovy.

Misto nej priznak `closed`: fronta, vytizeni i statistiky potrebuji vedet, co
uz nikdo neresi, a z volneho retezce to poznat nejde. Nastavuje se sam podle
`TicketType.closedStatuses`, a kdyz je typ nema, podle bezneho pojmenovani
(vyreseno, hotovo, completed, closed).

`stage` zruseno. Byla to obchazka, jak dostat cizi stavy do ticketu, aniz by
se sahlo na ciselnik. Kdyz je stav volny, druhe pole na tutéz vec jen matlo.

Vyber stavu v detailu nabizi stavy typu, doporucene a ten, ktery ticket ma
prave ted, aby hodnota z cizi aplikace ze seznamu nezmizela. Filtr v seznamu
nabizi stavy, ktere v datech opravdu jsou.

Overeno 11 kontrolami: ticket z voicebota ma stav ringing, pak in-progress
a completed, completed se pozna jako hotovo a zmizi z fronty, filtr i widget
ukazuji tvoje stavy a rucne jde nastavit i "ceka na zpetne volani".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 17:36:41 +02:00
JiriUhlirandClaude Opus 5 ef3080d3f6 Nastaveni prezije nasazeni, ukazkova data uz se nevraci
Bez databaze lezi data uvnitr containeru, takze redeploy je smaze a seed je
nasype znovu. Dokud nebude Postgres, resi se to trema vecmi:

Ukazkova data jen se SEED_DEMO=1. Automatizace, tickety a incidenty se uz po
kazdem nasazeni nevraci. Konfigurace se nasypava dal, bez ni je portal
nepouzitelny.

Token webhooku z WEBHOOK_TOKEN_TEST. Driv se pri kazdem nasazeni vygeneroval
novy, takze odesilatel musel prepisovat adresu ve svem kodu. Ted je token
v promenne aplikace: neni v gitu a adresa se nemeni.

Automatizace, ktera na instanci opravdu bezi, je v seedu. Je to provizorium,
ne cil - az data prezijou nasazeni, patri zpatky do dat.

Dal:

- `ticket/upsert` umi vsechna pole ticketu: zakaznik, kanal, odkaz na zdroj,
  priorita, stitky, resitel, skupina a vlastni pole typu jako JSON. Zakaznik
  a kanal se vyplnuji jen pri zalozeni, aby pozdejsi udalost s prazdnym
  jmenem neprepsala, co uz tam je.
- Kroky ve strome jdou sbalit, vychozi je sbaleno. Sbaleny krok ukazuje, co
  ma vyplneno, ne popis operace.
- Seskupovani widgetu podle faze a dva nove widgety: tickety podle stavu
  a podle faze za tento mesic, obojí s proklikem na vyfiltrovany seznam.
- Faze se ukazuje jako stav. Driv byl videt jen nas ctyrprvkovy ciselnik,
  coz u ticketu z cizi aplikace nedava smysl. Zivotni cyklus zustava vedle
  jako drobny text, protoze se z nej pocitaji statistiky.

Overeno 18 kontrolami proti bezicimu serveru.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 17:21:32 +02:00
JiriUhlirandClaude Opus 5 6abac82d5e Krok prirazeni resiteli a ukazkova data mimo zivy provoz
Opraveno: `ticket/assign` nemel vykonnou cast, takze krok "Prirad resiteli"
vzdycky selhal hlaskou "operace nema vykonnou cast". Doplnen jako vnitrni krok
vedle prirazeni nejvolnejsimu ze skupiny a prirazeni podle externiho ID.

Ukazkova automatizace "Smerovani ticketu na resitele" je vypnuta. Zapnuta
prebirala tickety, ktere uz nekomu patrily podle skutecne automatizace
zakaznika, a prepsat rucni nebo cizi rozhodnuti je to nejhorsi, co muze
automatizace udelat. Do udaju spoustece zaroven pribylo `assigned`
a `knownCustomer`, aby slo napsat podminku "uz je prirazeny, nesahej na to".

`ticket/upsert` prijima `status`: kdyz hodnota patri mezi nase ctyri stavy,
nastavi stav, jinak se ulozi jako faze. Cizi aplikace posila svoje stavy
hovoru a nas zivotni cyklus je pevny, protoze se z nej pocitaji statistiky.
Do shrnuti kroku se napise, co se stalo.

Overeno pripadem z provozu: callSid do externiho ID, voicebotId jako stitek,
status jako stav. Ctyri zpravy o trech hovorech daly tri tickety, filtr na
stitek vratil jen hovory daneho voicebota a widget je spocital vcetne
prokliku na vyfiltrovany seznam.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 17:03:03 +02:00
JiriUhlirandClaude Opus 5 a57eca123e Fronta a worker: webhook odpovi hned, praci udelaji workeri
Webhook uz nic nevykonava v requestu. Zapise udalost do fronty a odpovi 202
do jednotek milisekund; strom vykona worker na pozadi. Za konektory nerucime,
takze cekat na cizi sluzbu v requestu znamena ztracet udalosti pri timeoutu.

Fronta ma opakovani s rostouci prodlevou (30 s, 2 min, 10 min, hodina),
spravedlive poradi po firmach (jedna firma s tisicem udalosti nezablokuje
ostatni), navrat zaseknutych behu po restartu a uklid hotovych. Marna chyba
se neopakuje - chybejici skript za minutu existovat nezacne.

Tri druhy spoustecu: push (webhook), vnitrni udalost (vznik a zmena ticketu)
a pull, tedy pravidelne dotazovani u sluzeb bez webhooku (posta, zpravy).
Planovac jen rekne "je cas", samotny dotaz je prvni krok stromu, takze ma
zaznam v logu a opakuje se pri chybe jako cokoliv jineho.

Kontrakt tela webhooku: kazdy parametr ma cestu (data.order.id,
errors.0.message), takze jde napojit i odesilatel s vnorenym modelem.
U adresy je metoda, ukazka tela a kopiruje se cela adresa vcetne domeny.

Vnitrni kroky, ktere sahaji do naseho uloziste: ticket/upsert (zaloz nebo
dopln podle externiho ID), assign-least-busy, assign-by-external, set-type,
set-stage, add-tags, set-status, incident/create, flow/pause a flow/log.

Faze ticketu jako treti osa vedle stavu a stitku. Stav je zivotni cyklus
a pocitaji se z nej statistiky, faze je workflow daneho typu a muze byt jen
jedna, takze se na ni da spolehnout v podmince.

ID z cizich aplikaci u resitele: voicebot posle voicebotId a ticket skonci
u toho, komu patri. Vazba je na jednom miste, ne v kazde automatizaci.

Kazda chyba zaklada incident se dvema urovnemi: impact cte klient a je
srozumitelny, detail cte admin a je v nem cely beh, ktery krok selhal, cele
hlaseni a data na vstupu. Detail vidi jen spravce platformy.

Ochrana proti smycce: automatizace navazana na zmenu ticketu ticket meni,
cimz se spousti znovu - pri vyvoji to server polozilo. Resi to oznaceni behu
pres AsyncLocalStorage a strop peti behu na jeden ticket za minutu.

Upozorneni pri prideleni prace vcetne cisla u zalozky Tickety. Zivy dashboard:
dlazdice nad nasimi daty na udalost, data z konektoru podle ttlSec s moznosti
vynutit nacteni znovu.

Opraveno: path a intervalSec u spoustece se pri ulozeni zahazovaly; nad
seznamem neslo pouzit contains, takze na stitky neslo postavit podminku;
novejsi vystup kroku ted prekryje starsi misto hlaseni konfliktu.

Overeno dvema scenari proti bezicimu serveru, 34 kontrol: firma se skladem,
expedici a IT, a hovory z voicebota (callSid do externiho ID, status do faze,
prirazeni podle voicebotId, tri zpravy = jeden ticket se tremi udalostmi).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 16:41:02 +02:00
JiriUhlirandClaude Opus 5 5d186dcd2e Runtime vykonava strom, prokliky z widgetu, oprava ukladani rozlozeni
Runtime: `src/runtime/executor.ts` jde krok po kroku, u podminky se vetvi,
do poli dosadi parametry, akci pusti pres runScript a vystupy pripise do
kontextu pro dalsi krok. Cely prubeh jde do logu ticketu vcetne toho, co
sluzba vratila. Pouzivaji ho obe cesty: akce na ticketu i webhook.

Opraveno: rozlozeni dashboardu s vlastnim widgetem se NEDALO ULOZIT.
`validateLayout` znala jen vestaveny katalog, takze kazdy pokus skoncil
hlaskou "widget v katalogu neexistuje" - presne to, co hlasil uzivatel.
Katalog je ted jedna funkce a pouziva ji nabidka i kontrola. Zaroven je
za konkretni firmu, driv slo polozit dlazdici jedne firmy na dashboard druhe.

Prokliky: z widgetu lidi na cloveka, ze seskupeni na vyfiltrovany seznam
ticketu. Odkazy sklada server, protoze on jediny zna filtr widgetu. Seznam
ticketu cte filtr z adresy a umi filtrovat na typ, tag a skupinu.

Tabulky: spolecna `TicketTable` pro seznam i detail osoby. Na mobilu se
neposouva do strany, uzka obrazovka dostane karty. Detail osoby ma velkou
tabulku se zalozkami "ma u sebe" a "vyresil" a prepinacem pohledu.

Odebrano: simulace vcetne tlacitka, dialogu i endpointu. Trojice pohledu
nad tickety - vyber firmy je select, "moje" je prepinac, driv to delalo
totez dvakrat.

Pridan zmereny rozbor kapacity pro 200 firem (19-kapacita-200-firem.md):
soucasny stav to nezvladne, protoze data jsou v pameti a vypis je linearni.
Zmereno na 5 000 ticketech, vcetne toho, co s tim a kolik serveru to chce.

Overeno 7 kontrolami proti bezicimu serveru.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 15:54:17 +02:00
JiriUhlirandClaude Opus 5 29de584df8 Ticketovaci system: udalosti, externi ID, statistiky a widgety nad konektory
Jakakoliv udalost se muze stat ticketem. Prijem je verejny endpoint na firmu
(`POST /webhook/ticket/:token`), takze zalozit ticket jde i bez stavby stromu.

Externi ID je unikatni V RAMCI FIRMY: dalsi zprava se stejnym ID se navesi na
existujici ticket misto zalozeni druheho, a stejne ID u jine firmy je jiny
ticket. Cislo a retezec jsou tentyz klic. Udalosti se drzi cele vcetne
prijatych dat a jdou rozbalit v detailu - je to neco jineho nez log.

Ticket nove nese firstResponseAt, resolvedAt, resolvedById a reopenCount.
Bez nich neslo rict, kdo kolik odbavil ani jak dlouho zakaznik cekal.
`getAgentStats` z toho pocita vykon resitelu vcetne medianovych casu
a vracenych ticketu. Pocet vyresenych sam o sobe odmenuje toho, kdo tickety
zaviral predcasne, proto je vraceni videt vedle nej.

Widgety: klient konecne vola /widget-data. Endpoint existoval, ale nikdo ho
nepouzival, takze vlastni widget hlasil "nepodarilo se zobrazit". Pribyl zdroj
`connector` - co umi zjistit napojena sluzba, jde vytahnout do dlazdice pres
tentyz skript, ktery pouziva krok automatizace. Vysledek se cachuje.

Akce a widgety uz nejsou v nastaveni, maji vlastni zalozku vedle automatizaci.
Telo akce se sklada stromem, ne JSONem v textarei - je to tentyz editor,
jen misto karty spoustece je "spousti clovek tlacitkem na ticketu".

Nova zalozka Lide se seznamem a detailem osoby. Seznam ticketu i lidi ma dva
pohledy, tabulku a dlazdice.

Opraveno: createTicket bral typeId, fields, tags i assigneeGroupId, ale nikdy
je neukladal. Ticket zalozeny s typem zustaval bez typu a bez vlastnich poli.

Dlouhe pomlcky, sipky, vypustky a bullety pryc z celeho projektu.

Overeno 21 kontrolami proti bezicimu serveru v rezimu souboru.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 15:13:09 +02:00
JiriUhlir 6c90372991 test 2026-08-13 10:20:47 +02:00
JiriUhlirandClaude Opus 5 afbe948da3 Uloziste pro vsechna data, oprava .gitignore, dodelany navrh rozsireni
.gitignore mel vzorec `data/`, ktery se shodl i se `src/data/`. Sestnact
zdrojovych souboru tim tise chybelo v gitu vcetne cele slozky
`src/data/store/`. Opraveno na `/data/`, stejne v .dockerignore.

Tickety vcetne logu, automatizace, incidenty a rozlozeni dashboardu se po
kazde zmene ukladaji. Pomocnik `withMirror` je opak `withCache`: data se meni
v pameti a zapisuji cela, misto aby se po zapisu znovu nacitala. Citace ID se
pri startu dopocitaji z ulozenych zaznamu, takze novy ticket neprepise stary.

Detail ticketu umi typ, tagy, vlastni pole typu a prehozeni na skupinu.
Nastaveni ma prepnuti spravce na jiny ucet, vychozi jen pro cteni.
Skupiny resitelu chodi spolu s lidmi jednim requestem.

Dokumentace: rejstrik znovupouzitelnych funkci (15), navrh monetizace
a ceny za krok (16), popis nastaveni a prav (17). Doplneny endpointy
do openapi.ts, petice CRUD rout se generuje jednou funkci.

Overeno v rezimu souboru: zmeny prezily tvrde ukonceni procesu a po restartu
byly zpatky vcetne logu ticketu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 07:46:22 +02:00
JiriUhlirandClaude Opus 5 e7cf499a0b Cely navrh rozsireni: role, firmy, typy ticketu, akce, widgety, audit
Implementace vsech bodu z documentation/09-navrh-rozsireni.md. Vsechno lezi
v obecnem ulozisti, ktere umi Postgres i JSON soubor.

Zaklad, aby se nepsalo osmkrat totez:
- src/data/store/: jedno rozhrani EntityStore, dve implementace (local se
  souborem nebo pameti, postgres nad tabulkou records). Vyber je na jednom
  miste v store/index.ts
- src/data/store/cached.ts: synchronni kopie v pameti pro entity ctene pri
  kazdem requestu (uzivatel v autorizaci, firmy pri vypoctu prav). Bez toho by
  se autorizacni middleware musel predelat na async
- src/routes/crud.ts: fabrika na CRUD routy. Kazda entita by jinak znamenala
  stejnych sto radku a sedmkrat by se opravila spatne
- src/data/bootstrap.ts: jedno misto, kde je seznam entit a jejich vychozi sady
- migrace 002_records.sql: jedna tabulka s JSONB. Tvary se jeste hybou a nikdo
  se nad nimi nedotazuje po polich. Az se to usadi, entita se povysi na vlastni
  tabulku, presne jako uz maji konektory

Prava a role (bod 5):
- role jsou zaznamy, pravo je retezec, katalog prav je zdroj pravdy. Union
  'admin' | 'agent' na ucetni a skladnika nestacil a pridavat hodnoty je slepa
  ulicka, kazdy klient chce jine
- Membership.roleIds misto role. Systemove role admin a agent zustavaji, takze
  se zadny ucet nemusel predelavat
- accessFor vraci prava i zalozky. Klient si nic nedovozuje

Zalozky a limity za firmu (bod 6):
- TenantFeatures: moduly, limity, zpristupnene sluzby. Dve vrstvy s jinym
  vlastnikem, ktere se nesmi michat: co ma firma zaplacene nastavujeme my,
  kdo z jejich lidi to smi nastavuje jejich admin. Efektivni viditelnost je
  prunik, takze vypnuty modul neexistuje ani pro admina te firmy
- navigace ze serveru, ne konstanta na klientovi

Firmy, uzivatele, resitele a skupiny: CRUD vcetne clenstvi a hesel. Heslo se
z API nikdy nevraci, ani jako hash. Skupiny resitelu kvuli tomu, ze prehazovat
praci na jmeno nestaci - clovek chce rict "tohle je pro ucetni".

Typy ticketu a akce (body 1, 2, 3):
- typ ticketu s vlastnimi polemi, plus tagy. Akce se vazou na typ nebo tag,
  ale za tagem nestoji zadna pole, takze akce na tagu umi jen vestavena pole
- Ticket dostal typeId, fields, tags a assigneeGroupId
- definice akce s telem jako operace, strom nebo skript. Pravo vznika spolu
  s akci jako action:<id>, admin pak zaskrtava akce, ne prava
- CTA na ticketu filtruje server podle typu, tagu, podminek a prav. Kdyby to
  pocital klient, pocitalo by se to na dvou mistech
- vestavene akce (typ, tagy, skupina) jdou pres tutez fabriku, takze maji
  svoje pravo a projdou auditem
- spusteni zapise do logu ticketu hned, jeste nez se neco stane

Vlastni widgety (bod 4):
- rozdeleni na render a source, groupBy, filtr je tentyz, ktery umi seznam
  ticketu. Uzivatel nepise dotazy
- jeden batch endpoint na cely prehled. Widget, ktery selze, vraci chybu na sve
  pozici a nezhasne prehled

Audit a impersonace (bod 6c):
- audit zapisuje i odepreni, jinak by pokusy o cizi firmu nikde nezustaly
- impersonace: jen spravce platformy, nikdy na jineho spravce platformy,
  30 minut bez obnoveni, vychozi jen cteni. Zapis pod rezimem cteni vraci 403
  na urovni middleware, ne az v handleru

Overeno bez databaze: vsech devet ulozist se nacte, role se zaloz1 a prezije
restart, agent dostane 403 na spravu roli a uzsi navigaci, neznama prava se
odmitnou, heslo se nevraci, typ a tagy ticketu se ulozi, CTA se objevi, widget
data pocitaji vcetne seskupeni, impersonace odmitne zapis i prepnuti na admina,
audit obsahuje actedBy. Degradace pri nedostupne databazi taky overena: migrace
selzou, jede se do souboru a rekne se proc.

Neovereno: migrace 002_records.sql proti zive databazi. Kontejner uz nebyl
k dispozici, generickou vrstvu drzi tentyz pool a migrator jako konektory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 07:06:38 +02:00
JiriUhlirandClaude Opus 5 6e3d0640ff Soubor jako uloziste, kdyz neni databaze
Mockup se k databazi nedostane, takze pribyl treti rezim: JSON soubor. Prezije
restart procesu i containeru, ale ne redeploy - filesystem containeru je
docasny. Je to mezistupen, ne nahrada databaze, a tak je to i napsane v portalu.

| Rezim    | Kdy                               | Restart | Redeploy |
| -------- | --------------------------------- | ------- | -------- |
| postgres | DATABASE_URL i SECRETS_KEY        | prezije | prezije  |
| file     | neni DB, ale je DATA_DIR          | prezije | ne       |
| memory   | ani jedno, nebo nejde zapsat      | ne      | ne       |

Rozhodnuti zustava na jednom miste (src/data/connectorStore.ts).

Pridano:
- src/data/snapshot.ts: atomicky zapis (.tmp a prejmenovani), slucovani zapisu
  a dokonceni rozepsaneho zapisu pri SIGTERM. Bez atomickeho zapisu by pad
  uprostred nechal polovicni JSON, ktery se pri startu nenacte. Rozbity soubor
  se prejmenuje na .broken a jede se dal - 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 s pravy
  0600, takze sifrovani funguje bez nastaveni. Chrani to proti nahodnemu
  precteni JSONu, ne proti pristupu k disku - klic lezi vedle dat a je to tak
  napsane i v portalu. U databaze se negeneruje vubec: 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, soubor a databaze

Overeno bez databaze: konektor s vyplnenymi udaji prezil restart, v JSONu jsou
hodnoty sifrovane a plaintext v nem neni. Pote s databazi: rezim postgres
funguje dal a klic vedle dat se nevygeneroval. Kontejner i data/ po overeni
smazany.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 15:18:05 +02:00
JiriUhlirandClaude Opus 5 61cf29878f Dockerfile: kopirovat migrace do image
tsc do dist kopiruje jen .js, takze .sql migrace by v containeru chybely.
Aplikace by spadla na migracich a jela dal v pameti, tedy presne to, co ma
databaze resit. Cesta odpovida dist/db/migrations, odkud si je hleda
src/db/migrate.ts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 15:09:21 +02:00
JiriUhlirandClaude Opus 5 78e7f99d60 Konektory do Postgresu, pristupove udaje sifrovane
Pristupove udaje konektoru se ukladaji do databaze a prezijou restart. Popis
v documentation/14-databaze.md.

Databaze je volitelna a rezimy jsou oddelene:
- postgres kdyz je DATABASE_URL i SECRETS_KEY
- memory jinak, tedy pri nasazenem mockupu a lokalnim vyvoji bez DB

Rozhodnuti je jen na jednom miste (src/data/connectorStore.ts). Nikde jinde se
nezjistuje, jestli databaze je - kdyby se to rozlezlo po kodu, jedno misto by se
zapomnelo a chovalo by se pak jinak nez zbytek.

Chybejici databaze nesmi shodit start: container, ktery nenastartuje, je pro
AppFactory nefunkcni sluzba. Misto toho se do logu napise proc a portal to ukaze
na strance Konektory. Stejne tak kdyz migrace selzou - psat do rozbiteho
schematu je horsi nez neukladat.

Databaze potrebuje oboji. Bez SECRETS_KEY by se udaje ukladaly v plaintextu
a to je horsi nez ztratit je pri restartu: tabulku vidi kazda zaloha a kazdy
dump pri ladeni.

Pridano:
- pool v src/db/pool.ts vcetne transakci a dbFor(tenantId) jako sev pro budouci
  oddelenou databazi jednoho klienta
- migrace ze src/db/migrations/*.sql pod pg_advisory_lock, jinak je pri rolling
  deployi pusti vsechny instance naraz. Jeden soubor je jedna transakce
- sifrovani 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
- /health/ready s pingem do DB. /health na databazi zamerne nezavisi, kratky
  vypadek by jinak vedl k restartovani containeru
- GET /api/dashboard/storage a hlaska v portalu o tom, ze data jsou jen v pameti
- 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.

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, pametovy rezim bez
DATABASE_URL. Kontejner po overeni smazan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 15:08:25 +02:00
JiriUhlirandClaude Opus 5 3279dd7dac Cele chybove hlaseni u konektoru i skriptu
U odpovedi 401 nebo 400 je duvod napsany v tele odpovedi sluzby, ne v tom, ze
prislo 401. Dosud se telo zkracovalo na 400 znaku a u overeni konektoru se
zahazovalo cele - zbyla veta "Pristup zamitnut", podle ktere se neda hledat.

- ScriptError nese `request` (metoda a cesta) a `detail` s celou odpovedi
  sluzby, zkracenou az na SCRIPT_ERROR_DETAIL_BYTES (vychozi 8 kB). Chyby jsou
  vzacne, takze objem neroste jako u logu uspesnych kroku
- do detailu jde surove telo, ne prochazene pres JSON.stringify. U chyby chceme
  presne to, co sluzba poslala, vcetne HTML nebo prosteho textu
- u chyby spojeni se pridava i `cause`, u neocekavane vyjimky zasobnik volani
  (mimo produkci, stejne jako u centralniho error handleru)
- overeni konektoru vraci `detail`, `status` i `request`
- cely detail jde i do logu serveru, at je to dohledatelne bez portalu
- do chyby se dava jen cesta, ne cela adresa: v query muze byt tajemstvi
- nova komponenta ErrorDetail: rozbaleni cele odpovedi a tlacitko Kopirovat vse

Overeno: npm run typecheck prochazi na serveru i webu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 14:38:43 +02:00
JiriUhlirandClaude Opus 5 ad56c7f513 Transformace dat, oprava ukladani udaju konektoru
Transformace dat ve dvou rezimech plus oprava chyby, kvuli ktere se neukladaly
pristupove udaje konektoru. Popis v documentation/13-transformace-dat.md.

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

Dva rezimy transformace, oba nad enginem v src/scripts/mapping.ts:
- transform.map-fields: pole na pole s prevody, klikatelne
- transform.to-json: sablona cileveho objektu s ${cesta}

Marker ${...} je zamerne jiny nez {{...}}. Sablony kroku se dosazuji driv, nez
krok bezi, takze {{total}} by strom stihl vyhodnotit, nenasel by parametr toho
jmena a dosadil by prazdno. Cely retezec navic zachova typ, takze
"unitPrice": "${total}" vyrobi cislo - jinak by cizi sluzba dostala castku jako
text a odmitla ji.

Prevod map pro seznamy je to, bez ceho by priklad nesel dokoncit. Bez nej jde
prevest hlavicku dokladu, ale ne polozky objednavky, a doklad by byl na nulu.

Dal pridano:
- idoklad.create-invoice-from-object: druha polovina prikladu, bere hotove telo
  dokladu z transformace a doplni povinna pole ze vzoru iDokladu
- spoustec e-shopu predava celou objednavku jako objekt a polozky jako seznam
- klikaci editor pravidel vcetne rezimu JSON pro vnorena pravidla u map
- 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, overeno
volanim POST i PATCH. 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.

Overeno: npm run typecheck prochazi na serveru i webu, node --check na skriptech.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 14:32:39 +02:00
JiriUhlirandClaude Opus 5 8ad91a6c28 Rozdeleni na sluzby a konektory, pristupove udaje do konektoru
Slovo "konektor" v kodu znamenalo katalog toho, co umime. Ted znamena napojeni
jedne firmy, tedy to, co tim mysli i uzivatel. Popis modelu je
v documentation/12-sluzby-a-konektory.md.

Tri vrstvy:
- Sluzba: ze iDoklad existuje, co umi a co potrebuje k napojeni. Nase.
- Skript: kod, ktery jednu operaci sluzby opravdu vykona. Nas.
- Konektor: ucet firmy vcetne jejich pristupovych udaju. Firemni.

Pristupove udaje se prestaly cist z environment variables. Cela instance by
mela jedny udaje spolecne a dve firmy by fakturovaly z jednoho uctu. Napojeni
je vlastnost firmy, ne prostredi. Z prostredi zustava jen SERVICES_BASE_URL.

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, zapis do logu
- viditelnost sluzby: vsichni, jen uvedene firmy a lide, nebo jen spravce
  platformy. Neviditelna sluzba se z API nevraci vubec, ne se stavem 403 -
  firma nema poznat, ze takova sluzba existuje
- src/data/connectorStore.ts: konektory za firmu vcetne hodnot udaju. Hodnoty
  se z API nikdy nevraci, jen filled a missing. Prazdne pole hodnotu nemeni,
  takze ulozeni formularu bez tajnych hodnot nic nepresepe
- FlowStep.connectorId: krok rika, pod kterym napojenim volat. null = vychozi
  konektor firmy, diky tomu je vzorovy strom prenositelny mezi firmami
- overeni konektoru pres verifyPath, tedy cteci volani vyzadujici autorizaci.
  U sluzby bez nej se overi jen dostupnost a odpoved to rekne nahlas, jinak by
  zeleny vysledek uzivateli lhal
- stranky /dashboard/sluzby a /dashboard/konektory vcetne formularu udaju
- endpointy /api/dashboard/services a CRUD /api/dashboard/connectors ve Swaggeru
- predvyplnene prihlaseni spravcem platformy a prepinac demo uctu na login
  strance, kvuli testovani prototypu

Zmeneno:
- stav "napojeno" se prestal cist z katalogu a zacal pocitat z konektoru firmy.
  Sluzba ma jen available nebo planned
- validace stromu overuje i konektor. Cizi konektor je chyba, chybejici
  napojeni nedodelek - rozdelana prace se nezahazuje
- 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

Overeno: npm run typecheck prochazi na serveru i webu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 14:11:50 +02:00
JiriUhlirandClaude Opus 5 6f6b287d7e Skripty konektoru: vykonna cast s manifestem a kontrolou parametru
Konektory dostaly vykonnou cast. Jeden skript je jeden soubor, ktery nese
manifest (vstupni a vystupni parametry) i kod. Diky manifestu s nim umi
pracovat strom automatizace, aniz by o kodu cokoliv vedel.

Soubory jsou zamerne obycejny JavaScript, ne TypeScript. TypeScript by se
musel prelozit a to je presne to otaceni, ktere tady nema byt. Registr
sleduje cas zmeny souboru, takze uprava v portalu, rucni uprava souboru
i novy soubor ve slozce funguji stejne a bez restartu.

Pridano:
- scripts/ se skripty konektoru, nazev souboru je zaroven ID operace
- kontrola vstupu i vystupu proti manifestu, jedna funkce pro obe strany.
  Chybejici povinny vystup je chyba skriptu, ne uzivatele - jinak by strom
  veril parametru, ktery nikdy nedosel
- 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. Cizi API rado vraci prijaty
  token v chybove zprave a log ticketu vidi klient
- napojeni z environment variables 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 vcetne 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 vyhrava skript
- ConnectorOperation ma implementation a scriptId
- ApiError na klientovi nese cele telo odpovedi a umi z nej vytahnout issues
- Dockerfile kopiruje scripts/ do vysledneho image

Ukladani nemuze rozbit fungujici skript: kod se nejdriv zapise do docasneho
souboru, ten se nacte a overi, a az pak prepise puvodni.

K tomu tri dokumenty navrhu dalsich kroku: 09 datove modely a prava,
10 runtime a rozpocet na 150 klientu, 11 popis skriptu konektoru.

Overeno: npm run typecheck prochazi na serveru i webu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 13:37:58 +02:00
JiriUhlir bbc2236c0d dashboard widgets 2026-08-03 13:10:41 +02:00
JiriUhlirandClaude Opus 5 2a3d75c85e Firmy a prava: tenance napric portalem
Portal nemel zadnou tenanci. Kterykoliv prihlaseny uzivatel videl vsechny
tickety vsech firem i cely seznam resitelu, requireRole se nikde nevolal.

Tenant je hranice viditelnosti, tenantId na ticketu, resiteli i automatizaci.
Uzivatel muze patrit do vic firem, v kazde s jinou roli. Pristup napric firmami
je zvlast jako platformAdmin.

Tri pohledy na tickety: all, tenant, mine. Admin mezi nimi prepina vcetne
vyberu firmy. O pravech rozhoduje jedine data/access.ts, klient si nic
nedovozuje a bere je z GET /api/dashboard/access.

Filtr na firmu je v ulozistich povinny argument, takze zapomenuty filtr
neznamena vse, ale nezkompiluje se. Cizi firma vraci 403 nebo 404, nikdy
tise zuzeny vysledek.

Prirazeni jen v ramci firmy. Prehazovat praci mezi lidmi smi jen admin,
agent si smi vzit ticket na sebe.

Zmena prihlasovani: ucet klient@firma.cz zanikl, demo ucty jsou nove.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:59:30 +02:00
JiriUhlirandClaude Opus 5 dd021b5f69 Obsah ticketu, nastaveni kroku a vystupy kroku
Ticket dostal telo (body) a odkaz na zdrojovou zpravu. Predmet je shrnuti,
telo je cely text pozadavku.

Akce maji nastavitelna pole (inputs) se sablonami {{parametr}}. Zatim ticket,
kanaly, CRM a AI, ostatni maji jen napovedu.

Krok vidi parametry spoustece plus vystupy kroku pred nim, takze jde vlozit
predvalidaci a vetvit se podle jejiho vysledku. Vetev podminky nepridava nic
do sekvence za podminkou.

Nove konektory Facebook Messenger a Instagram, nova akce RAYNET Dohledat firmu.

Ctyri vzorove automatizace v rozdeleni jedna na kanal pro prijem
a jedna spolecna pro smerovani na resitele.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:29:26 +02:00
JiriUhlir 52b190bfbc updated tickets 2026-08-03 11:45:35 +02:00
JiriUhlir 9a0c67af08 Odstraneni docasnych souboru z testovaciho behu 2026-07-31 17:05:01 +02:00
JiriUhlir e177ca2087 Oprava: aplikace padala pri startu v produkcnim rezimu
Dockerfile nastavuje NODE_ENV=production a config.ts pri chybejicim
JWT_SECRET vyhazoval vyjimku. AppFactory tu promennou nastavenou nema,
takze se container po startu ukoncil a sluzba byla nedostupna (502).

Chybejici JWT_SECRET uz start neshodi. Vygeneruje se nahodny klic platny
do restartu containeru a do logu jde varovani. Klic zapsany v kodu se
nepouziva, aby nevznikla bezpecnostni dira.

Dusledek: bez nastavene promenne prestanou po restartu platit vydane
tokeny. JWT_SECRET se ma nastavit jako promenna aplikace v AppFactory.
2026-07-31 17:04:32 +02:00
JiriUhlir 7b045a9f20 Nahrazeni sablony kompletnim webem a klientskym portalem
Web a portal Automia v jednom containeru. Express obsluhuje API
i zbuildovanou React aplikaci z dist/public.

Obsah:
- verejny web: homepage, sluzby, o nas, kontakt, 404
- prihlaseni pres JWT, demo ucty
- portal: prehled s grafem, tickety, incidenty, automatizace, konektory
- builder automatizaci: strom akci, vetveni podminkou
- katalog 25 konektoru v 8 kategoriich
- webhook s registrovanou adresou, token generuje server
- zivy dashboard pres SSE vcetne simulace provozu
- Swagger UI na /docs a OpenAPI na /openapi.json

Soulad s AGENTS.md:
- ROOT_PATH z prostredi, prefix proxy nikde nehardcodovan
- mount na koren i na prefix, funguje s handle_path i bez nej
- base tag a window.__BASE_PATH__ vkladane do index.html za behu
- OpenAPI servers obsahuje prefix, Try it out vola spravnou adresu
- povinne /health a /docs, port 3000, naslouchani na 0.0.0.0
- secrets jen z environment variables, nikdy v logu

Dokumentace ve slozce documentation/.
2026-07-31 17:00:37 +02:00
AppFactory Bot 46f2f0b07e Initial Node.js TypeScript service 2026-07-31 14:42:26 +00:00