Realne sluzby, OpenAI, odesilani e-mailu a helpdesk

Katalog srovnany s tim, co opravdu bezi na services.csbot.cz/apps:
trinact sluzeb dostalo pristupove udaje a levne cteci overeni, opravena
appId, ktera nikam nevedla (ppl, microsoft365, transcription), a GA4,
Search Console, Google Ads i Sklik ted stoji na aplikaci analytics,
kazda s vlastnimi udaji. Nove sluzby SAP Business One, Google Workspace
a Meta Ads. K tomu 23 skriptu, ktere s nimi opravdu neco delaji.

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

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

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

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

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-08-26 07:40:16 +02:00
co-authored by Claude Opus 5
parent b25a149574
commit a771834e57
78 changed files with 7021 additions and 932 deletions
+185 -12
View File
@@ -2,6 +2,179 @@
Nejnovejsi nahore.
## 2026-08-26 - helpdesk: pozadavek, ktery vidi zadavatel i resitel
Zakaznik nemel jak poslat pozadavek. Ticket pritom patri jedne firme, kdezto
u helpdesku figuruji dve - ta, ktera se pta, a ta, ktera to resi.
### Pridano
- **Pole `Ticket.helpdeskSourceId`** s firmou, ktera pozadavek poslala.
Vlastnikem (`tenantId`) zustava ta, ktera ho **resi**. Zamerne tak: kdyby byl
vlastnikem zadavatel, mel by resitel pozadavek jen jako cizi ticket a nemel by
ho ve sve fronte, ve statistikach ani v prirazovani. Takhle je to na jeho
strane obycejny ticket a nemuselo se sahnout na nic, co uz funguje.
- **`Tenant.helpdeskProviderId`**, tedy komu firma posila pozadavky. Nastavuje
spravce platformy v Nastaveni, Firmy. Kdo koho obsluhuje je obchodni vztah,
ne volba klienta - kdyby si dodavatele vybiral uzivatel, poslal by pozadavek
nekomu, s kym nema smlouvu. Bez vyplneneho dodavatele se pozadavek nezalozi
a rekne se to nahlas.
- **Sekce Helpdesk** (`/dashboard/helpdesk`) a router
`/api/dashboard/helpdesk`: seznam vlastnich pozadavku, zalozeni, detail
s prubehem a komentar. Zamerne to **neni druhy seznam ticketu** - chybi tu
filtry, prirazovani i fronta, protoze zadavatele nezajima, kdo to ma u sebe.
- **Prava `helpdesk.view` a `helpdesk.create`.** Prideluje je admin te firmy
pres role, stejne jako u ostatnich prav.
### Zmeneno
- **`listTickets` umi filtrovat podle zdroje** (`helpdeskSourceIds`). Vyplnene
**nahrazuje** filtr podle vlastnika, protoze zadavatel vlastnikem neni.
Bezny seznam ticketu se nezmenil.
- **`getTicket` a `addComment` maji druhou cestu dovnitr.** Firma, ktera
pozadavek poslala, ho smi cist a pripsat k nemu komentar, i kdyz ho nevlastni.
Komentar je jedina zmena, kterou nad nim smi: stav, resitele a typ urcuje ten,
kdo to resi.
## 2026-08-26 - portal prestal ukazovat provozni veci a ticket jde zalozit rucne
Sada oprav podle toho, co v portalu drhlo.
### Zmeneno
- **Hlasky o ulozisti a o odchozi IP adrese vidi jen spravce platformy.**
Kam se uklada a z jake adresy volame ven resime my, ne zakaznik. "Data se
ukladaji do souboru na serveru" na nej navic pusobi jako priznani, ze mu tu
praci muzeme ztratit. Nemazou se, jen se schovavaji - nam poradi porad.
Totez plati pro radek "volano z IP" v historii overeni.
- **Typ ticketu se v automatizaci vybira ze seznamu.** Bylo to textove pole,
do ktereho mel clovek opsat ID typu odjinud. Novy druh pole `lookup` je
**ciselnik a volny text zaroven**: bud se vybere ze seznamu typu te firmy,
nebo se hodnota dosadi z dat (`{{data.typ}}`). Typy ticketu jsou vlastnost
firmy, takze nabidka chodi za tu, ve ktere clovek je.
- **Stav ticketu je otevreny naseptavac, ne ciselnik.** Stav je volny retezec
a vzdycky byl - ticket muze prijit z cizi aplikace s jejim vlastnim stavem.
Vyber ze seznamu tomu odporoval. Ted je to textove pole s nabidkou toho, co
firma uz pouziva (`GET /api/dashboard/tickets/statuses`), a nova hodnota
projde stejne dobre. Zapisuje se az pri opusteni pole, ne po kazdem pismenu.
- **"Kanal" se prejmenoval na "Odkud pozadavek prisel"** a rika o sobe, ze je
nepovinny a slouzi jen k filtrovani a ikone v seznamu.
- **Vstupni parametry u webhooku jsou oznacene jako nepovinne.** Webhook prijme
cokoliv; rucne vypsany seznam parametru neni podminka, ale pohodli pro
podminky - a vyplni se sam z vlepene ukazky tela. Prazdny seznam uz nehlasi,
ze "bez nich nelze pridat podminku".
### Pridano
- **Zalozeni ticketu rucne** (`POST /api/dashboard/tickets`, pravo
`ticket.create`, tlacitko Novy ticket na strance Tickety). Dosud ticket
vznikal jen z automatizace nebo z prichozi udalosti, takze pozadavek prijaty
telefonem nemel jak do systemu.
- **Zakaznik u ticketu je nepovinny.** Rucne zalozeny ticket je casto ukol, ne
pozadavek od nekoho zvenku; povinna firma a kontakt by znamenaly, ze si je
clovek vymysli. Ve formulari je zakaznik schovany pod odkazem.
### Co z teze davky jeste neni
- **Sekce Helpdesk** pro zakaznika vcetne sdileni ticketu mezi admin firmou
a tim, kdo ho zalozil.
- **Prepinac firmy nad celym dashboardem.** Dnes je to stav uvnitr stranky
Prehled, takze se prepnuti neprojevi v Lidech ani jinde - ostatni stranky
volaji API bez `tenantId` a server pouzije vychozi firmu. Ma to doplnovat
API vrstva na jednom miste, ne kazda stranka zvlast.
## 2026-08-26 - odesilani e-mailu ze schranky firmy
Sluzba E-mail byla v katalogu jako popis: bez udaju, bez vykonne casti. Ted se
odesila doopravdy, ze schranky, kterou si firma vyplni v konektoru.
### Pridano
- **Sluzba E-mail pres SMTP.** V konektoru server, port, sifrovani, uzivatel,
heslo, adresa a jmeno odesilatele a adresa pro odpovedi. Zadne udaje
v prostredi - kazda firma odesila ze sve schranky.
- **Vnitrni krok `email/send`.** SMTP neni HTTP a skript umi jen `ctx.http`,
takze operaci vykonava vnitrni krok stejne jako zalozeni ticketu. Pristupove
udaje pritom zustavaji v konektoru.
- **Druh pole `html`.** Telo zpravy se pise jako HTML a builder ho vykresli
jako vysoke pole s neproporcionalnim pismem. Runtime v nem **escapuje
dosazene hodnoty**: znacky autora sablony jsou zamer, ostre zavorky
v hodnote od zakaznika ne. Bez toho by text ticketu s `<b>` prepsal
rozvrzeni zpravy a `<script>` by dosel prijemci do schranky.
- **Overeni konektoru prihlasenim.** Sluzba s `transport: 'smtp'` se neoveruje
ctecim volanim, ale prihlasenim na server. Nic se neodesila, takze test
nikomu nic nedoruci, a bez platneho hesla neprojde.
- **Vnitrni krok smi rict "zkus to znovu"** (`StepOutcome.retryable`). Dosud
se opakovani u vnitrnich kroku vzdy vypinalo, protoze selhavaly na spatnem
nastaveni. Nedostupny posmovni server ale za minutu bezet muze.
- **Textova verze zpravy.** Bez vyplneni se vyrobi z HTML. Klient, ktery HTML
nezobrazi, by jinak dostal prazdnou zpravu a filtry nevyzadane posty berou
chybejici textovou cast jako priznak spamu.
- **Zavislost `nodemailer`.** Vlastnorucne psany SMTP klient by byl 300 radku,
ktere nejde bez schranky overit.
### Opraveno
- **SMTP server z konektoru nesmi mirit do vnitrni site.** Stejne pravidlo jako
u HTTP (`isPrivateHost`), protoze adresu vyplnuje firma.
## 2026-08-26 - katalog stoji na sluzbach, ktere opravdu bezi, a pribyla OpenAI
Katalog popisoval, co chceme umet. U vetsiny sluzeb chybely pristupove udaje,
takze konektor nemel co vyplnit, a `appId` u nekterych ukazovalo na aplikaci,
ktera neexistuje. Zdroj pravdy o tom, co bezi, je `services.csbot.cz/apps`
a jeji `/openapi.json`.
Novy dokument: [21-realne-sluzby.md](21-realne-sluzby.md).
### Zmeneno
- **Trinact sluzeb katalogu ma napojeni na bezici aplikaci** vcetne poli udaju
a levneho cteciho volani na overeni. RAYNET, CSOB, PPL, Microsoft 365, GA4,
Search Console, Google Ads, Sklik a Prepis hovoru mely `credentials: []`,
takze konektor nesel vyplnit a test se nemel ceho chytit.
- **Opravena `appId`, ktera nikam nevedla.** PPL bezi na `pplcplapi`, ne na
`ppl`. Microsoft 365 na `microsoft-365-service`. Prepis hovoru na
`audio-transcription`. GA4, Search Console, Google Ads a Sklik bezi vsechny
na aplikaci `analytics`, kazdy s vlastnimi udaji - slucovat je do jedne
sluzby by znamenalo, ze firma se samotnym Sklikem musi vyplnit i Google.
- **Prepis hovoru ma jen operaci, kterou aplikace umi.** Operace "Vytvorit
souhrn" za sebou nic nemela; souhrn ted udela OpenAI.
- **Query ve `verifyPath` se do hlasky nedava.** Odrizne se stejne jako
u `ScriptRequestInfo` - v query muze byt tajemstvi.
### Pridano
- **Sluzba OpenAI.** Firma zada svuj API klic do konektoru a muze se ptat
modelu, poslat soubor a nechat si prepsat zvuk. Skripty `openai.chat`,
`openai.upload-file`, `openai.ask-about-file`, `openai.transcribe-audio`
a `openai.list-models`.
- **Sluzba, ktera nebezi u nas.** `Service.baseUrl` nese absolutni adresu cizi
sluzby. Skladat ji ze `SERVICES_BASE_URL` by nedavalo smysl, to je zaklad
nasich aplikaci. Prepsat ji jde promennou `<SLUZBA>_BASE_URL` (dosud jen
slibenou v dokumentaci, ted opravdu implementovanou) nebo adresou u konektoru,
cimz vede cesta na Azure OpenAI.
- **Predpona hlavicky u pole udaju** (`ServiceCredentialField.prefix`).
OpenAI chce `Authorization: Bearer <klic>`; uzivatel vlepi holy klic a slovo
pred nim dopise runtime. Preklep v rucne psanem `Bearer` by nesel najit ani
zpetne, protoze se hodnota z API nevraci.
- **Odesilani souboru ze skriptu** (`ctx.http.postForm`). Obsah prichazi jako
Base64, protoze parametr kroku se uklada do zaznamu behu a binarni data by se
tam nevesla. Strop `SCRIPT_MAX_UPLOAD_BYTES`, vychozi 10 MB.
- **Sluzby SAP Business One, Google Workspace a Meta Ads.** Bezi, ale
v katalogu nebyly. Meta Ads je zamerne oddelena od Facebook Messengeru
a Instagramu: aplikace `meta` je nad Marketing API, tedy reklamy, a je jen
pro cteni.
- **Dvacet tri skriptu k realnym sluzbam**, od dohledani firmy v RAYNETu po vykon
kampani ve Skliku. Seznam je v [21-realne-sluzby.md](21-realne-sluzby.md).
### Opraveno
- **Redakce tajemstvi bere i holou hodnotu.** Dosud se skrtaly jen hodnoty
hlavicek, takze u `Authorization: Bearer sk-...` by samotne `sk-...` v chybove
odpovedi proslo do logu. `targetSecrets` ted vraci obojí a pouziva ho
i overeni konektoru, ktere si dosud sestavovalo seznam samo.
## 2026-08-25 - chybova hlaseni konektoru rikaji, co se stalo
"Pristup zamitnut: GET /apps/idoklad/account/agenda vratilo HTTP 403.
@@ -143,12 +316,12 @@ Skript je **ciste prevod hodnot**: dostane `input`, vrati objekt. Nema `require`
`import`, `process`, `fetch` ani `console`. Volani ven patri do kroku konektoru,
ktery ma pristupove udaje, opakovani i zapis do logu.
| Pojistka | Proc |
| --- | --- |
| limit 2 s | zacykleny skript by jinak zablokoval workera vsem firmam |
| vysledek do 256 kB | vetsi objekt uz stejne nikdo dal nezpracuje |
| kod do 20 000 znaku | delsi uz neni prevod, ale aplikace |
| zadny stav mezi behy | stav, ktery prezije beh, je zdroj nejhur hledanych chyb |
| Pojistka | Proc |
| -------------------- | -------------------------------------------------------- |
| limit 2 s | zacykleny skript by jinak zablokoval workera vsem firmam |
| vysledek do 256 kB | vetsi objekt uz stejne nikdo dal nezpracuje |
| kod do 20 000 znaku | delsi uz neni prevod, ale aplikace |
| zadny stav mezi behy | stav, ktery prezije beh, je zdroj nejhur hledanych chyb |
**Cim to neni.** `node:vm` neni bezpecnostni hranice proti nekomu, kdo se chce
dostat ven. Je to izolace proti nehode a proti zacykleni. Skript pise spravce
@@ -307,12 +480,12 @@ ukazovalo 373 za automatizaci, ktera bezi tri mesice.
### Nove soubory
| Soubor | Co dela |
| --- | --- |
| `src/data/invites.ts` | Entita pozvanky, kod, platnost. |
| `src/routes/invites.ts` | Verejne cesty (prohlednuti a prijeti) a sprava. |
| `web/src/pages/Invite.tsx` | Stranka za odkazem: jmeno, e-mail, heslo. |
| `web/src/components/dashboard/InvitePanel.tsx` | Sprava pozvanek v zalozce Lide. |
| Soubor | Co dela |
| ---------------------------------------------- | ----------------------------------------------- |
| `src/data/invites.ts` | Entita pozvanky, kod, platnost. |
| `src/routes/invites.ts` | Verejne cesty (prohlednuti a prijeti) a sprava. |
| `web/src/pages/Invite.tsx` | Stranka za odkazem: jmeno, e-mail, heslo. |
| `web/src/components/dashboard/InvitePanel.tsx` | Sprava pozvanek v zalozce Lide. |
## 2026-08-13 - vyrizeno je vyslovny priznak, ne hadani ze stavu