Provozovatel portalu: firma s priznakem portalOperator (jen jedna, zapnuti odebere ostatnim) a novymi poli contactEmail, contactPhone, website vedle ico, dic, adresy a pravni formy. Verejny GET /api/public/brand vraci jeji udaje a web je bere pres useBrand() na kontaktu, v paticce, O nas, prihlaseni i v titulku; brand.ts je jen zaloha. Poptavka z webu zaklada u provozovatele ticket kanalu form: predmet "Poptavka: tema", telo JSON s poli formulare, tag Poptavka plus tema, zakaznik z formulare, poznamka v logu. Bez provozovatele se jen zaloguje. Prilohy ticketu: formular az 3 soubory po 5 MB, ticket az 10; nahrani, seznam, stazeni a smazani (pravo ticket.comment, strop viditelnosti, poznamky v logu, audit). Soubor jde v JSON jako Base64 a lezi v beznem ulozisti, bez nove zavislosti; strop tela jen na techto cestach. Vlastni widget s kreslenim Graf umi i pocet ticketu se seskupenim jako kolac (PieChart.tsx, ciste SVG, osm barev z tokenu, zbytek jako ostatni). OpenAPI rozdelene na mensi soubory (102 cest, 28 schemat overeno shodnych), 28 novych testu (135 celkem), dokumentace aktualizovana.
22 KiB
03 - Architektura a mapa kodu
Technologie
| Vrstva | Technologie |
|---|---|
| Server | Node.js 20, Express 4, TypeScript, ESM |
| Web | React 18, Vite 6, TypeScript, Tailwind 4, React Router 6 |
| Auth | JWT (jsonwebtoken), hesla bcrypt |
| Validace | zod |
| Docs | swagger-ui-express nad rucne psanou OpenAPI definici |
Jeden package.json pro server i web (znamy stav, viz nize). Runtime zavislosti
jsou v dependencies, nastroje pro build webu v devDependencies - runtime
image je pak instaluje pres --omit=dev.
Build a kontroly
tsc src/**.ts -> dist/*.js
vite web/ -> dist/public/
Server obsluhuje dist/public jako statiku. Dockerfile kopiruje do vysledneho
image dist a connectors/ - skripty konektoru se ctou za behu ze souboru,
ne z buildu.
| Prikaz | Co dela |
|---|---|
npm run build |
server (tsc) a web (vite build) |
npm run typecheck |
tsc --noEmit pro oba tsconfig, nic nezapisuje |
npm run lint |
eslint .: server, web, testy i connectors/ |
npm run format |
prettier --write .; format:check jen kontroluje |
npm run test |
vitest run nad tests/**; test:watch pri vyvoji |
Build, lint i testy se spousti jen se svolenim uzivatele (pravidlo 2
v D:\GitHubRepository\CLAUDE.md).
Oba tsconfig maji strict, noUncheckedIndexedAccess, noUnusedLocals
a noFallthroughCasesInSwitch. Index do pole nebo slovniku je tak
T | undefined a kod to musi osetrit; ! na umlceni se nepouziva. Prave
tahle volba odhalila dve skryte chyby (prodleva fronty a Retry-After),
viz 99-zmeny.md.
Nastroje v korenu
| Soubor | K cemu |
|---|---|
eslint.config.js |
typescript-eslint, react-hooks v7 pro web, connectors/** jako obycejny JS bez globalu; zadne any, zadny prazdny catch |
.prettierrc, .prettierignore |
jednotne formatovani (jednoduche uvozovky, sirka 100) |
.editorconfig |
odsazeni, konce radku a kodovani pro editor |
.nvmrc |
Node 20, stejne jako engines v package.json |
.env.example |
vsechny promenne prostredi s popisem; .env neni v gitu |
vitest.config.ts |
testy z tests/**/*.test.ts, alias @shared, tests/setup.ts pred kazdym souborem |
tests/tsconfig.json |
typecheck testu nad src/ bez emitu |
Dockerfile |
vicefazovy build, runtime jen s --omit=dev, kopiruje dist a connectors |
Mapa kodu - server
| Cesta | K cemu je |
|---|---|
src/index.ts |
start: nacteni dat, worker, listen, signaly, process.on; nic jineho |
src/app.ts |
createApp(): middleware, routery, health, Swagger, statika, SPA. Bez listen, aby sla postavit v testu (supertest) |
src/config.ts |
jedine misto, kde se cte process.env; serviceBaseUrlOverride(variable) pro <SLUZBA>_BASE_URL |
src/openapi/index.ts |
buildOpenApiDocument(): sklada dokument, servers s prefixem proxy |
src/openapi/helpers.ts |
crudPaths a opakujici se parametry, tela a odpovedi |
src/openapi/components/ |
schemata a zabezpeceni po domenach (common, auth, tickets, attachments, automations, connectors, scripts, settings), index.ts je sklada |
src/openapi/paths/*.ts |
cesty po routerech: ops, auth, dashboard, tickets, automations, settings, connectors, scripts, helpdesk, invites, admin, contact, attachments, public, webhook |
src/types.ts |
typy uzivatele a JWT payloadu |
src/shared/ |
ciste typove moduly API, jediny zdroj typu pro server i web |
src/middleware/auth.ts |
requireAuth, requireRole, requirePlatformAdmin |
src/middleware/asyncHandler.ts |
wrap, safeRouter: odchyceni odmitnute promise v handleru |
src/middleware/tenant.ts |
attachAccess, tenantOrDeny, scopeOrDeny: firma requestu na jednom miste |
src/middleware/rateLimit.ts |
limit requestu v pameti, 429 s Retry-After |
src/middleware/validation.ts |
validationError, jeden tvar chyby validace |
src/lib/secure.ts |
timingSafeEqualString pro tokeny v adrese |
src/net/guard.ts |
kontrola adresy, cteni tela s limitem, popis chyby site - pro vsechno, co vola ven |
src/events/bus.ts |
sbernice udalosti, ze ktere cerpa SSE stream, udalost nese firmu |
src/routes/auth.ts |
prihlaseni, odhlaseni, kdo jsem |
src/routes/crud.ts |
crudRouter: fabrika CRUD nad jednou entitou |
src/routes/dashboard/index.ts |
mount routeru dashboardu, attachAccess jednou za request |
src/routes/dashboard/misc.ts |
prava, prehled, uloziste, katalog sluzeb |
src/routes/dashboard/tickets.ts |
seznam s filtrem, rucni zalozeni, stavy, vytizeni, detail |
src/routes/dashboard/people.ts |
resitele pro nabidky a detail cloveka |
src/routes/dashboard/automations.ts |
strom akci, validace, webhook, fronta behu |
src/routes/dashboard/incidents.ts |
incidenty: seznam, detail, posun stavu |
src/routes/dashboard/layout.ts |
katalog widgetu a ulozene rozlozeni |
src/routes/dashboard/intake.ts |
adresa prijmu udalosti a jeji obnova |
src/routes/dashboard/notifications.ts |
upozorneni a pocet otevrenych ticketu |
src/routes/dashboard/clientCrash.ts |
hlaseni padu portalu, z nej incident |
src/routes/dashboard/shared.ts |
strankovani: pageFrom, paginate |
src/routes/dashboard/attachments.ts |
prilohy ticketu: seznam, nahrani, stazeni, smazani; pravo ticket.comment |
src/routes/ticketActions.ts |
akce nad ticketem vcetne vestavenych, pravo za firmu ticketu |
src/routes/settings/index.ts |
mount routeru nastaveni |
src/routes/settings/{tenants,users,roles,people,groups,features,ticketTypes,actions,widgets}.ts |
jedna entita = jeden soubor nad crudRouter; people a users maji vlastni handlery |
src/routes/settings/catalog.ts |
co jde v nastaveni zvolit: prava, moduly, limity, widgety |
src/routes/settings/shared.ts |
memberOf pro ucty a resitele |
src/routes/ares.ts |
firma z registru ARES, jen spravce platformy |
src/routes/connectors.ts |
konektory firmy, overeni, nastroje MCP |
src/routes/scripts.ts, tenantScripts.ts |
skripty konektoru a skripty firmy |
src/routes/stream.ts |
SSE stream zmen, filtr podle firem uzivatele |
src/routes/webhook.ts |
verejny prijem dat do automatizace |
src/routes/contact.ts |
poptavkovy formular z webu, zaklada ticket provozovateli portalu |
src/routes/public.ts |
verejne udaje bez prihlaseni: GET /api/public/brand z provozovatele |
src/routes/bodyLimit.ts |
jsonLimitFor, hasOwnBodyLimit: strop tela pro routy se soubory v base64 |
src/ares/client.ts |
klient verejneho API ARES |
src/data/store/ |
tri rezimy uloziste, withCache, withMirror, initStores |
src/data/snapshot.ts |
atomicky zapis JSONu pro rezim file |
src/data/ticketStore.ts |
fasada nad src/data/tickets/, importy zustavaji |
src/data/tickets/ |
index (verejne API), model (tvar, toTicket), state (pamet a indexy), persist (zapis, initTickets), queries (seznam, detail, strop viditelnosti), store (zapisy: zalozeni, stav, resitel, typ, tagy, skupina, komentar, poznamka o priloze), intake (udalost zvenku), trace (log prubehu), stats (vytizeni a vykon), seed, remap |
src/data/people.ts |
resitele jako pohled na clenstvi uctu (personView), skupiny |
src/data/migratePeople.ts |
jednorazovy prevod starych zaznamu resitelu ppl_ na ucty |
src/data/tenants.ts |
firmy, ktere portal pouzivaji, vcetne udaju z ARES; operatorTenant, clearOtherOperators |
src/data/attachments.ts |
prilohy ticketu: kontrola davky, zapis s obsahem v base64, cteni, mazani |
src/data/access.ts |
kdo co vidi - jedno misto pro cely portal |
src/data/widgets.ts |
katalog widgetu prehledu |
src/data/dashboardLayouts.ts |
rozlozeni dashboardu za dvojici uzivatel a firma |
src/data/incidentStore.ts |
incidenty vcetne zmen a udalosti, filtr na firmu povinny |
src/data/automationStore.ts |
fasada nad src/data/automations/ |
src/data/automations/ |
index, model (tvar, rulesOf, matchOf), state (pamet, citac ID), persist (zapis, initAutomations), store (cteni a zapisy, recordRun), validation (pocet kroku, nedodelky, druh), webhook (token, posledni volani), runs (historie po dnech), seed, seedDemo, remap |
src/data/services.ts |
fasada nad src/data/services/ |
src/data/services/index.ts |
katalog za behu: findService, actionsFor, setScriptActions, setMcpOperations, withRuntimeOptions, serviceCatalog |
src/data/services/catalog/ |
staticky zapis po skupinach: triggers, incident, ticket, crm, finance, logistics, email, messaging, social, office, analytics, ai, mcp, tools, polstryn; index.ts urcuje poradi v nabidce |
src/data/conditions.ts |
typy parametru a operatory podminek |
src/data/templates.ts |
sablony {{parametr}} v nastaveni kroku |
src/data/flowScope.ts |
co je videt v kterem miste stromu |
src/data/users.ts |
uzivatele portalu, demo ucty |
src/data/mock.ts |
souhrn pro prehled a casova rada grafu |
src/runtime/ |
fronta, worker, executor stromu, vestavene kroky, planovac, sandbox skriptu firmy |
src/runtime/scripts/ |
runtime skriptu konektoru: registr, runner, HTTP, napojeni, manifest, kontrola hodnot, mapping, util (vcetne ctx.util) |
connectors/ |
skripty konektoru (obycejny JS) a _sablona.js; cesta z config.scriptsDir, promenna SCRIPTS_DIR |
src/mcp/ |
klient MCP, prihlaseni, dialekty, errors.ts se spolecnou chybou prihlaseni |
tests/ |
vitest, stejna cesta jako modul (tests/data/tickets.test.ts pro src/data/tickets/); setup.ts nastavi rezim pameti a umlci console.info |
Mapa kodu - web
| Cesta | K cemu je |
|---|---|
web/src/main.tsx |
vstupni bod, basename routeru podle prefixu proxy |
web/src/App.tsx |
routovani, portal se nacita lazy |
web/src/index.css |
design tokeny a vlastni utility Tailwindu |
web/src/config/brand.ts |
nazev, claim a staticka zaloha kontaktu; kontakty bere web z provozovatele pres useBrand |
web/src/lib/files.ts |
soubor na base64, kontrola poctu a velikosti, limity stejne jako na serveru |
web/src/lib/api.ts |
fetch wrapper, sprava tokenu, skladani adres, auth:expired na 401 |
web/src/lib/eventStream.ts |
cteni SSE streamu pres fetch |
web/src/lib/collections.tsx |
klientsky sklad ciselniku za firmu, opravovany z udalosti |
web/src/lib/ticketEvents.ts |
oprava seznamu ticketu z payload.ticket bez dotazu |
web/src/lib/options.ts |
pevne ciselniky (priority) |
web/src/lib/flow.ts |
ciste funkce nad stromem automatizace |
web/src/lib/exampleBody.ts |
vzorove telo spoustece pro ukazku a strom modelu |
web/src/lib/serviceIcons.ts |
klic ikony ze serveru na komponentu lucide |
web/src/hooks/useApiQuery.ts |
nacitani dat, cache, spolecny debounce, refreshing misto odmontovani |
web/src/hooks/useSubmit.ts |
odeslani formulare: busy, chyba, issues na jednom miste |
web/src/hooks/useUnsavedChanges.ts |
varovani pri odchodu z rozepsaneho formulare |
web/src/hooks/useLatest.ts |
ref s posledni hodnotou pro callbacky mimo zavislosti effectu |
web/src/hooks/useSyncFromSource.ts |
prevzeti dat ze zdroje do rozepsaneho stavu pri vykresleni, ne v effectu |
web/src/hooks/useMediaQuery.ts |
sirka obrazovky pres useSyncExternalStore |
web/src/hooks/usePageMeta.ts |
titulek a popis stranky |
web/src/hooks/useBrand.ts |
udaje provozovatele z /api/public/brand nad zalohou brand.ts, jeden sdileny dotaz |
web/src/types/ |
fasada nad src/shared (alias @shared/*), zadne vlastni typy API |
web/src/components/ui/ |
zakladni prvky: Badge, Button, Card, Chip, Modal, Section, Spinner, ... |
web/src/components/ui/Table.tsx |
Table, TableHead, Th, TableRow, Td: jedna tabulka seznamu pro EntityAdmin, InvitePanel, People, AuditView |
web/src/components/ui/ServiceIcon.tsx |
ikona sluzby podle klice z katalogu, misto const Icon = serviceIcon() v JSX |
web/src/components/ui/form/ |
Field, Input, Select, Textarea, FilePicker, controlClass: jedna sada trid |
web/src/components/dashboard/ |
shell portalu, dlazdice, graf, stream, TicketCard |
web/src/components/dashboard/EntityAdmin.tsx, EntityForm.tsx |
sprava jedne entity: tabulka a formular v modalu |
web/src/components/dashboard/flow/ |
strom akci: FlowCanvas a karty ActionCard, ConditionCard, ForeachCard, StepControls; k tomu TriggerConfig, SampleBody, ModelTree, WebhookCalls, MappingEditor |
web/src/components/dashboard/scripts/ |
TestPanel (zkusebni spusteni) a CodeEditor (uprava kodu) pro stranku Skripty |
web/src/components/dashboard/settings/ |
TenantsAdmin (firmy, provozovatel, kontakt), FeaturesAdmin (zalozky a limity), AuditView, types |
web/src/components/dashboard/TicketAttachments.tsx, TicketAssignPanel.tsx |
sekce priloh a panel resitele v detailu ticketu |
web/src/components/dashboard/widgets/ |
WidgetCard, WidgetPicker, CustomWidget, EditBar (lista uprav rozlozeni) |
web/src/components/dashboard/TicketTrace.tsx |
log ticketu jako strom |
web/src/components/dashboard/TicketWorkload.tsx |
prehled, kdo co ma u sebe |
web/src/components/home/ |
sekce homepage |
web/src/pages/ |
jedna stranka je jeden soubor |
web/src/pages/dashboard/connectors/ |
casti stranky Konektory: karta, editor, log, nastroje |
Soubory nad 500 radku
Zasada rika, ze soubor nad 500 radku je signal k rozdeleni. Po rozdeleni
zustavaji tri, kazdy z duvodu (TicketDetail sel pod hranici vyclenenim
TicketAssignPanel a TicketAttachments, Settings vyclenenim
TenantsAdmin):
| Soubor | Proc zustava |
|---|---|
web/src/pages/dashboard/AutomationDetail.tsx |
stranka drzi stav stromu a ukladani; casti bez stavu uz jsou ve flow/ |
web/src/components/dashboard/flow/MappingEditor.tsx |
dva rezimy editoru nad jednim stavem, deleni by stav zdvojilo |
src/data/services/catalog/ticket.ts |
jedna sluzba s nejvic operacemi; deleni jedne sluzby do dvou souboru by rozbilo "jedna vec v jednom souboru" |
Na serveru zustava dalsich sedm, ktere strukturalni pruchod nedelil, protoze
nebyly v zadani a kazdy je jeden souvisly modul: runtime/builtinSteps.ts
(929, vestavene kroky; kandidat na slozku runtime/steps/ po kroku),
mcp/client.ts (884, protokol MCP; kandidat na oddeleni handshake a volani
nastroju), runtime/executor.ts (779; kandidat na vycleneni vyhodnoceni
podminek), routes/connectors.ts (677; kandidat na routes/connectors/),
routes/ticketActions.ts (599), routes/widgetData.ts (587), mcp/auth.ts
(527). Deli se pri nejblizsi praci v nich, ne naraz.
Dalsi velke soubory (builtinSteps.ts, mcp/client.ts, executor.ts,
routes/connectors.ts) jsou kandidati na priste, az se do nich bude sahat.
Klicova rozhodnuti
Jeden container misto dvou. AppFactory nasazuje jednu aplikaci, proto Express obsluhuje i statiku. Odpada CORS i druha deploy jednotka.
SSE misto WebSocketu. Tok dat je jednosmerny, server ke klientovi. Klient posila zmeny beznym REST volanim. SSE prochazi reverse proxy bez zvlastni konfigurace.
Stream pres fetch, ne pres EventSource. EventSource neumi poslat hlavicku
Authorization a token by musel byt v adrese, odkud se dostane do access logu.
Cenou je rucni parsovani a rucni znovupripojeni v web/src/lib/eventStream.ts.
Ceske cesty v URL. /sluzby, /o-nas, /prihlaseni, /dashboard/tickety.
Kod zustava anglicky.
Uloziste je za rozhranim. Tri rezimy (Postgres, soubor, pamet), jedno
rozhrani a jedno rozhodnuti v initStores. Routy nevedi, ktery rezim jede.
Podrobnosti v 14-databaze.md.
Typy API jsou jednou. Ciste typove moduly v src/shared ctou server i web,
web pres alias @shared/*. Kopie typu na klientovi se jednou rozejde se
serverem a prekladac to nepozna. web/src/types/ je jen fasada, ktera je
re-exportuje; ucet uzivatele bere i AuthContext ze @shared/users.
Kazdy async handler je odchyceny. Routy vznikaji pres safeRouter,
odmitnuta promise skonci jako 500 s logem, ne padem procesu. Bez toho stacil
jeden zapomenuty try a AppFactory restartovala container.
Firma requestu se pocita jednou. attachAccess da do req.access, co
uzivatel smi, a tenantOrDeny z toho odvodi firmu. Kazda route, ktera si to
pocitala sama, to delala trochu jinak.
Filtr na firmu je povinny argument. listTickets, listPeople
i listAutomations vyzaduji tenantIds. Zapomenuty filtr tak neznamena "vse",
ale nezkompiluje se. Podrobnosti v 07-firmy-a-prava.md.
Prava se nikdy nedovozuji na klientovi. Server vraci GET /api/dashboard/access
s tim, co uzivatel smi. Kdyby si to klient pocital sam, pocitalo by se to na dvou
mistech a jednou se to rozejde.
Resitel je clenstvi uctu. ID resitele je ID uctu, Person je jen pohled
na ucet a jedno jeho clenstvi ve firme (jmeno a e-mail z uctu, popisek,
kapacita a externi ID z clenstvi). Driv byl resitel vlastni zaznam spojeny
s uctem pres e-mail a zmena e-mailu vazbu tise rozbila. Stara uloziste
prevadi migratePeople pri startu. Podrobnosti v 06-tickety.md.
Filtrovani ticketu dela server. Klient posila query parametry a dostane hotovy seznam. Kdyby filtroval sam, ukazoval by jina cisla nez prehled vytizeni.
Krok vidi jen to, co je pred nim. Parametry spoustece plus vystupy
predchozich kroku. Vetev podminky nepridava nic do sekvence za podminkou, protoze
nemusela probehnout. Vypocet je v flowScope.ts, priklady
v 06-tickety.md.
Sablony odkazuji jmenem, ne ID. Opak podminek, a je to zamer: {{subject}}
uzivatel napise a precte, {{f_42}} ne. Rozbite odkazy po prejmenovani se hlasi
jako nedodelek.
Zadna ticha selhani. Kazdy catch loguje a uzivatel se o chybe dozvi.