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>
18 KiB
18 KiB
Rejstřík znovupoužitelných funkcí a komponent
K čemu je co, aby se za rok nepsalo znovu něco, co už existuje. Když píšeš druhou funkci, která dělá skoro totéž jako něco odsud, je to skoro vždycky chyba - buď se má použít ta původní, nebo se má rozšířit.
Podrobný popis je vždycky v komentáři u samotné funkce. Tady je jen jedna věta a kdy to použít.
Ukládání dat (server)
Rozhoduje se na jednom místě, viz 14-databaze.md. Volající nikdy nezjišťuje, jestli běží Postgres, soubor, nebo pamět.
| Co | Kde | K čemu |
|---|---|---|
defineStore<T>(kind) |
src/data/store/index.ts |
Založí úložiště pro nový druh záznamu. Jeden řádek na entitu. |
initStores({databaseReady}) |
src/data/store/index.ts |
Vybere režim. Volá se jednou při startu, nikde jinde. |
flushStores() |
src/data/store/index.ts |
Dopíše rozepsané zápisy. Jen při ukončení procesu. |
withCache(store) |
src/data/store/cached.ts |
Kopie v paměti pro konfigurační entity, které se čtou při každém requestu (uživatelé, role, firmy). Čte se synchronně, obnovuje se po zápisu. |
withMirror(store) |
src/data/store/mirror.ts |
Opačný směr než withCache: data se mění v paměti a po každé změně se celý záznam zapíše. Pro provozní data (tickety, automatizace, incidenty, rozložení). |
isVisible(entity, options) |
src/data/store/types.ts |
Vidí volající tenhle záznam? Prázdný seznam firem znamená "nic", ne "vše". |
nowIso() |
src/data/store/types.ts |
Časová značka. Ať se nepíše new Date().toISOString() na třiceti místech. |
memorySnapshot / fileSnapshot |
src/data/snapshot.ts |
Nižší vrstva pod createLocalStore: atomický zápis JSONu s debounce. Přímo se nepoužívá. |
db(), query, queryOne, transaction |
src/db/pool.ts |
Postgres. dbFor(tenantId) je připravený šev pro rozdělení na víc databází. |
seal, open, sealAll, openAll |
src/db/secretBox.ts |
Šifrování přístupových údajů konektorů (AES-256-GCM). Nic tajného se neukládá jinak. |
runMigrations() |
src/db/migrate.ts |
Migrace pod zámkem, jeden soubor = jedna transakce. |
Entity a práva (server)
| Co | Kde | K čemu |
|---|---|---|
crudRouter(options) |
src/routes/crud.ts |
Celý CRUD nad jednou entitou: seznam, detail, vytvoření, úprava, mazání, právo, audit. Nová entita v nastavení = jeden crudRouter, ne pět handlerů. |
readScope(req) |
src/routes/crud.ts |
Ze které firmy smí request číst. Povinný argument všech list volání. |
accessFor(user, tenantId?) |
src/data/access.ts |
Co uživatel smí: práva, záložky, výchozí firma. Klient si nic nedovozuje sám. |
permissionsOf(user, tenantId) |
src/data/permissions.ts |
Efektivní práva z rolí. Pětisekundová cache, invalidatePermissions() po zápisu. |
hasPermission(...) |
src/data/permissions.ts |
Jedna kontrola. Používá ji crudRouter i ruční handlery. |
navFor(...) |
src/data/tenantFeatures.ts |
Průnik toho, co firma má, a toho, na co má člověk právo. Navigace chodí ze serveru. |
recordAudit(input) |
src/data/audit.ts |
Zápis do auditu. Nevrací chybu a nečeká se - rozbitý audit nesmí rozbít aplikaci. |
enqueue(input) |
src/runtime/queue.ts |
Zařadí běh. Klíč proti dvojímu zařazení drží jeden běh na jednu událost. |
claimBatch(limit) |
src/runtime/queue.ts |
Vezme další práci, spravedlivě po firmách. Místo, kde nad Postgresem musí být SKIP LOCKED. |
onTicketEvent(kind, ticket) |
src/runtime/triggers.ts |
Změna ticketu zařadí navázané automatizace, včetně ochrany proti smyčce. |
withRun(marker, work) |
src/runtime/context.ts |
Označí, který běh práci způsobil. Bez toho automatizace spouští sama sebe. |
findBuiltinStep(...) |
src/runtime/builtinSteps.ts |
Kroky, které sahají do našeho úložiště, ne ven přes HTTP. |
findPersonByExternalId(...) |
src/data/people.ts |
Řešitel podle ID z cizí aplikace, například voicebotId. |
notify(input) |
src/data/notifications.ts |
Upozorní člověka. Nečeká se a nevyhazuje chyby, stejně jako audit. |
runFlow(steps, context, options) |
src/runtime/executor.ts |
Vykoná strom kroků. Nikdy nevyhodí výjimku, chyba je výsledek. Používá to akce na ticketu i webhook, aby se strom choval všude stejně. |
widgetCatalog(tenantIds, userId) |
src/data/widgets.ts |
Jediná definice toho, co jde položit na dashboard. Používá ji nabídka i kontrola ukládaného rozložení. |
intakeEvent(input) |
src/data/ticketStore.ts |
Přijme událost zvenku: podle externího ID buď založí ticket, nebo ji navěsí na existující. Jediná cesta, kterou se událost stává ticketem. |
getAgentStats(...) |
src/data/ticketStore.ts |
Výkon řešitelů: odbavené, mediány časů, vrácené, fronta. Používá to widget i detail osoby, aby čísla seděla. |
findByExternalId(...) |
src/data/ticketStore.ts |
Ticket firmy podle externího ID. Klíč je dvojice firma a ID. |
findByIntakeToken(token) |
src/data/tenants.ts |
Firma podle tokenu příjmu. Určuje i to, v jakém rozsahu je externí ID unikátní. |
refreshCaches() |
src/data/bootstrap.ts |
Obnoví všechny kopie v paměti. Volá se po zápisu, který je může změnit. |
bootstrapData({databaseReady}) |
src/data/bootstrap.ts |
Seznam všech entit a provozních dat. Nová entita se přidává tady, ne rozesetě po modulech. |
Skripty a konektory (server)
| Co | Kde | K čemu |
|---|---|---|
runScript(id, inputs, ctx) |
src/scripts/runner.ts |
Spustí skript. Nikdy nevyhodí výjimku, chybu vrací jako výsledek s celým hlášením. |
validateValues(...) |
src/scripts/values.ts |
Jedna kontrola pro vstupy i výstupy skriptu podle manifestu. |
scriptUtil |
src/scripts/util.ts |
Nádobíčko pro skripty: pick, first, num, date, need, get, applyRules, fillJson. Skript nemá sahat na nic jiného. |
createRedactor(...) |
src/scripts/util.ts |
Vyškrtá tajemství z textu před logováním. Používá se u všeho, co jde do logu. |
applyRules, fillJson |
src/scripts/mapping.ts |
Transformace dat: pole na pole s převody, nebo objekt na objekt. Viz 13-transformace-dat.md. |
getPath(obj, path) |
src/scripts/mapping.ts |
Čtení zakaznik.adresa.mesto z neznámého objektu. |
resolveTarget(...) |
src/scripts/connections.ts |
Z konektoru poskládá adresu a hlavičky. Přístupové údaje nikam jinam nevedou. |
serviceBaseUrl(service) |
src/scripts/connections.ts |
Adresa služby: naše aplikace ze SERVICES_BASE_URL, cizí (OpenAI) z jejího baseUrl. Přebít jde přes <SLUZBA>_BASE_URL. |
targetSecrets(target) |
src/scripts/connections.ts |
Co se musí vyškrtat z logu. Vrací i holý klíč bez předpony Bearer , protože v něm ho cizí služby vracejí v chybách. |
createHttp(...) |
src/scripts/http.ts |
HTTP se timeoutem, limitem odpovědi a rozlišením "zkusit znovu" a "marné". |
isPrivateHost(host) |
src/scripts/http.ts |
Míří jméno do vnitřní sítě? Jedno pravidlo pro HTTP i pro SMTP server z konektoru. |
sendMail(target, message) |
src/mail/smtp.ts |
Odešle e-mail přes SMTP z konektoru. Nikdy nevyhodí výjimku, vrací i to, jestli má smysl zkusit znovu. |
verifySmtp(target) |
src/mail/smtp.ts |
Přihlásí se na server bez odeslání zprávy. Tím se ověřuje konektor e-mailu. |
escapeHtml(value) |
src/data/templates.ts |
Escapuje dosazenou hodnotu v HTML šabloně. Značky autora šablony zůstávají, ostré závorky od zákazníka ne. |
ctx.http.postForm(...) |
src/scripts/http.ts |
Odeslání souboru (multipart/form-data). Obsah přichází jako Base64, hranici dopisuje runtime. |
scriptIdFor(serviceId, operationId) |
src/scripts/lookup.ts |
Který skript obsluhuje operaci z katalogu. |
Klient
| Co | Kde | K čemu |
|---|---|---|
EntityAdmin |
components/dashboard/EntityAdmin.tsx |
Celá správa jedné entity: tabulka, modál, validace, mazání. Nová záložka nastavení = popis sloupců a polí, ne nová stránka. |
parseJsonField |
components/dashboard/EntityAdmin.tsx |
Textové pole s JSONem na hodnotu, s hlášením, kde je chyba. |
TicketActions |
components/dashboard/TicketActions.tsx |
CTA akcí na ticketu plus typ, tagy a vlastní pole. Seznam akcí chodí ze serveru už vyfiltrovaný. |
ErrorDetail |
components/dashboard/ErrorDetail.tsx |
Rozbalovací celé chybové hlášení s kopírováním. Chyba se nikdy nezkracuje. |
CustomWidgetCard |
components/dashboard/widgets/CustomWidget.tsx |
Vykreslí widget, jehož data počítá server: číslo, pruhy, tabulka výkonu, časová řada, seznam, data z konektoru. |
TicketTable |
components/dashboard/TicketTable.tsx |
Tabulka ticketů pro všechna místa. Na mobilu se místo posouvání do strany kreslí karty. |
TicketEvents |
components/dashboard/TicketEvents.tsx |
Příchozí události ticketu včetně celého přijatého JSONu. |
ViewSwitch |
components/dashboard/ViewSwitch.tsx |
Přepínač tabulka nebo dlaždice. Používají ho všechny seznamy. |
FlowCanvas s start |
components/dashboard/flow/FlowCanvas.tsx |
Tentýž strom kroků i bez spouštěče - pro tělo akce, které spouští člověk. |
MappingEditor |
components/dashboard/flow/MappingEditor.tsx |
Editor transformací v obou režimech (pole na pole, JSON). |
DataState |
components/dashboard/DataState.tsx |
Načítání, chyba, prázdno. Ať to každá stránka nekreslí po svém. |
apiFetch<T> |
lib/api.ts |
Jediná cesta na API: base path, token, ApiError s celým hlášením ze serveru. |
useApiQuery<T> |
lib/useApiQuery.ts |
Načtení dat do stránky včetně reload. S body pošle POST (dávkové načtení), s enabled: false se neptá vůbec. |
cn(...) |
lib/cn.ts |
Skládání tříd. Podmíněné třídy nikdy ručně přes šablonu. |
format* |
lib/format.ts |
Čísla, procenta, datum, relativní čas, trvání. Formátování se nepíše v komponentě. |
serviceIcon(key) |
lib/serviceIcons.ts |
Klíč ikony ze serveru na komponentu. Server neposílá komponenty. |
usePageMeta |
lib/usePageMeta.ts |
Titulek stránky. |
Badge, Button, Modal, Card, ... |
components/ui/ |
Základní prvky. Nový vzhled tlačítka patří sem, ne do stránky. |
Pravidla, která z toho plynou
- Nová entita v nastavení:
defineStorev modulu entity, řádek vbootstrap.ts,crudRoutervsettings.ts, popis vSettings.tsx. Nic jiného se psát nemusí. - Data, která se mění za provozu, jdou přes
withMirror. Data, která se čtou při každém requestu a mění zřídka, přeswithCache. Obojí nikdy. - Chybu se nesmí zkracovat. Server vrací celé hlášení, klient ho umí
zobrazit (
ErrorDetail). - Klient nepočítá práva. Co smí, říká
accessFor.