# 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`. Runtime zavislosti jsou v `dependencies`, nastroje pro build webu v `devDependencies` - runtime image je pak instaluje pres `--omit=dev`. ## Build ``` tsc src/**.ts -> dist/*.js vite web/ -> dist/public/ ``` Server obsluhuje `dist/public` jako statiku. Dockerfile kopiruje do vysledneho image jen `dist`, takze staci jedna slozka. ## Mapa kodu - server | Cesta | K cemu je | | ------------------------------ | ------------------------------------------------------------- | | `src/index.ts` | vstupni bod: middleware, mount routeru, statika, SPA, Swagger | | `src/config.ts` | cteni environment variables, normalizace `ROOT_PATH` | | `src/openapi.ts` | OpenAPI definice vcetne `servers` s prefixem proxy | | `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/dashboard.ts` | data portalu, tickety, behy, CRUD automatizaci | | `src/routes/ticketActions.ts` | akce nad ticketem vcetne vestavenych, pravo za firmu ticketu | | `src/routes/settings.ts` | CRUD entit pres `crud.ts`, uzivatele, ARES | | `src/routes/ares.ts` | firma z registru ARES, jen spravce platformy | | `src/routes/connectors.ts` | konektory firmy, overeni, nastroje MCP | | `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` | tickety, jejich resitele, log prubehu, prehled vytizeni | | `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` | automatizace, strom akci, tokeny webhooku | | `src/data/services.ts` | katalog sluzeb, jejich spousteču a akci | | `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, sandbox skriptu firmy | | `src/scripts/` | skripty konektoru: registr, runner, HTTP, pomocne funkce | | `src/mcp/` | klient MCP, prihlaseni, dialekty, `errors.ts` se spolecnou chybou prihlaseni | ## 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/useApiQuery.ts` | nacitani dat, cache, spolecny debounce, `refreshing` misto odmontovani | | `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/useSubmit.ts` | odeslani formulare: `saving`, chyba, reset na jednom miste | | `web/src/lib/options.ts` | pevne ciselniky (priority) | | `web/src/lib/useUnsavedChanges.ts` | varovani pri odchodu z rozepsaneho formulare | | `web/src/lib/flow.ts` | ciste funkce nad stromem automatizace | | `web/src/types/` | fasada nad `src/shared` (alias `@shared/*`), zadne vlastni typy API | | `web/src/components/ui/` | zakladni prvky, `Chip` | | `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/flow/` | strom akci: `FlowCanvas` a karty `ActionCard`, `ConditionCard`, `ForeachCard`, `StepControls` | | `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 | ## 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.