# 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](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 `_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.ts` | schemata a zabezpeceni | | `src/openapi/paths/*.ts` | cesty po routerech: `ops`, `auth`, `dashboard`, `tickets`, `automations`, `settings`, `connectors`, `scripts`, `helpdesk`, `invites`, `admin`, `contact`, `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/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 | | `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), `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 | | `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` | vsechny firemni udaje na jednom miste | | `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/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`, `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/` | `FeaturesAdmin` (zalozky a limity), `AuditView`, `types` | | `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 ctyri, kazdy z duvodu: | Soubor | Proc zustava | | ---------------------------------------------- | ---------------------------------------------------------------------- | | `web/src/pages/dashboard/AutomationDetail.tsx` | stranka drzi stav stromu a ukladani; casti bez stavu uz jsou ve `flow/` | | `web/src/pages/dashboard/TicketDetail.tsx` | detail sklada sest komponent, zbytek je stav a odeslani akci | | `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" | 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](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](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](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](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.