Commit Graph
8 Commits
Author SHA1 Message Date
JiriUhlirandClaude Opus 5 a771834e57 Realne sluzby, OpenAI, odesilani e-mailu a helpdesk
Katalog srovnany s tim, co opravdu bezi na services.csbot.cz/apps:
trinact sluzeb dostalo pristupove udaje a levne cteci overeni, opravena
appId, ktera nikam nevedla (ppl, microsoft365, transcription), a GA4,
Search Console, Google Ads i Sklik ted stoji na aplikaci analytics,
kazda s vlastnimi udaji. Nove sluzby SAP Business One, Google Workspace
a Meta Ads. K tomu 23 skriptu, ktere s nimi opravdu neco delaji.

OpenAI jako prvni sluzba, ktera nebezi u nas: Service.baseUrl s absolutni
adresou, prepis pres <SLUZBA>_BASE_URL nebo adresu u konektoru, predpona
hlavicky u pole udaju (uzivatel vlepi holy klic, Bearer dopise runtime).
Dotaz na model, nahrani souboru, otazka nad souborem, prepis zvuku.
Skript umi odeslat soubor pres ctx.http.postForm (multipart, obsah Base64).

Sluzba E-mail pres SMTP. Neni to skript, ale vnitrni krok - SMTP neni HTTP.
Konektor nese schranku firmy, krok ma HTML telo, ve kterem se dosazene
hodnoty escapuji (znacky autora sablony jsou zamer, ostre zavorky od
zakaznika ne). Overeni konektoru se prihlasi na server a nic neodesle.

Helpdesk: Ticket.helpdeskSourceId drzi firmu, ktera pozadavek poslala,
vlastnikem zustava ta, ktera ho resi - jinak by ho resitel nemel ve sve
fronte. Komu pozadavek pripadne, urcuje Tenant.helpdeskProviderId.
Zadavatel vidi jen svoje pozadavky a smi k nim pripsat komentar.

Opravy v portalu:
- hlasky o ulozisti a odchozi IP vidi jen spravce platformy
- typ ticketu se v automatizaci vybira ze seznamu firmy, nebo dosadi z dat
- stav ticketu je otevreny naseptavac, ne ciselnik
- ticket jde zalozit rucne, zakaznik u nej neni povinny
- kanal se prejmenoval a parametry u webhooku jsou oznacene jako nepovinne
- srovnane markdown tabulky v cele dokumentaci

Co z teto davky jeste neni: prepinac firmy je porad jen stav uvnitr stranky
Prehled, takze se prepnuti neprojevi v Lidech ani jinde.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 07:40:16 +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 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 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