# Developer guide How the codebase is organized, how the main subsystems work, and where to extend them. ## Layout ``` server.js Express entry, middleware stack, route mount Dockerfile node:20-alpine, argon2 native compile deps docker-compose.yml app + postgres services migrations/ node-pg-migrate versioned schema changes scripts/ maintenance.js REINDEX / drift CLI release.sh semver bump + tag + push import-milestones.js one-off seed import src/ db/ database.js pool, baseline init, query helpers migrate.js programmatic node-pg-migrate runner middleware/ auth.js JWT + session-table validation + sliding idle logging.js request log utils/ ai.js callAI() multi-provider router + model whitelist models.js built-in model registry prompts.js templates (DB-overridable) promptSafe.js wrapping + INJECTION_GUARD crypto.js AES-256-GCM (PHI at rest) passwords.js argon2id + bcrypt fallback + rehash sessions.js token hash, UA parse, session id platform.js isMobileClient() redact.js PHI redactor for audit details auditQueue.js batched audit/api/access writer fileType.js magic-byte upload verifier errors.js generic 500 responder logger.js audit + api + access + Loki shipper embeddings.js LiteLLM embeddings notify.js ntfy push transcribe.js, tts.js LiteLLM STT / TTS routes routes/ Express routers for auth, AI workflows, education, logs, and user data public/ index.html SPA shell, version-stamped asset refs sw.js cache shell, network-first API manifest.json PWA js/ 24 vanilla JS modules (no bundler) components/ per-tab HTML fragments loaded on demand css/styles.css template-guide.md downloadable user template guide mobile/ Capacitor 6 wrapper (Android + iOS) .github/workflows/ CI (auto-version, APK, docker) ``` ## Backend ### Middleware stack (`server.js`) ``` request → helmet CSP, HSTS, X-Content-Type-Options → CORS fail-closed in prod if APP_URL/CORS_ORIGINS missing → cookieParser → express.json 10 MB cap → rate limiters 200/min general; 10/15min login; 20/15min sensitive → static public/ with per-filetype Cache-Control; ?v=BUILD_ID cache-busts HTML on deploy → route handlers → 404 fallback serves index.html for SPA routes ``` On boot: - `APP_VERSION` read from `package.json`, printed + returned by `/api/health/detailed`. - `BUILD_ID` = short git HEAD SHA (or random on non-git deploys). Rewritten into HTML at startup. - `JWT_SECRET` / `DATA_ENCRYPTION_KEY` fail-fast if missing in production. - `initDatabase()` → `runMigrations()` → collation drift check. - SIGTERM / SIGINT handler drains the audit queue and closes the pool. ### DB helpers (`src/db/database.js`) ```js await db.get(sql, params); // first row or null await db.all(sql, params); // array of rows await db.run(sql, params); // { lastInsertRowid, changes } await db.query(sql, params); // raw pg result await db.getSetting(key); // app_settings value (2 min cache) await db.setSetting(key, v); // writes + invalidates cache db.pool // pg.Pool instance for transactions ``` SQL uses `?` placeholders (auto-converted to `$1, $2, ...`) OR native `$N`. INSERTs without explicit RETURNING get `RETURNING id` appended. ### Auth middleware (`src/middleware/auth.js`) ```js var { authMiddleware, adminMiddleware, moderatorMiddleware } = require('../middleware/auth'); router.post('/thing', authMiddleware, handler); // requires auth router.post('/admin-thing', authMiddleware, adminMiddleware, handler); ``` Sets `req.user` with `{ id, email, name, role, totp_enabled, disabled }` and `req.sessionId`. ### AI (`src/utils/ai.js`) ```js var { callAI } = require('../utils/ai'); var result = await callAI([ { role: 'system', content: PROMPTS.hpiEncounter + INJECTION_GUARD }, { role: 'user', content: wrapUserText('transcript', transcript) } ], { model, maxTokens: 4000 }); // result = { content, model, usage } ``` Throws `{ code: 'model_not_permitted' }` if the requested model isn't in the active allowlist. ### Prompts (`src/utils/prompts.js`) Flat `PROMPTS` object. Any template can be overridden live by writing a `prompt.{name}` row in `app_settings`. Admin Panel's Prompt Editor is the UX for that. ### Settings (`src/utils/config.js`) ```js var v = await config.get('feature.read_aloud', 'false'); // key, default await config.set('registration_enabled', 'true'); ``` 2-minute in-memory cache. Writes invalidate immediately. ### Logger (`src/utils/logger.js`) ```js logger.audit(userId, 'action', 'details', req, { category: 'auth' }); logger.apiCall(userId, endpoint, { model, tokensInput, tokensOutput, duration }); logger.access(userId, 'login', req, true); logger.error('scope', err.message); ``` Writes go to the database (batched), the daily JSONL file, and Loki (if configured). ## Frontend No framework, no bundler, no build step. `public/index.html` is the only document. Modules communicate through `window.*` globals and `CustomEvent` on `document`. ### Tab system `app.js` owns `activateTab(name)`: 1. Fetch `/components/{name}.html` (browser-cached for 1 h, bust by `?v=BUILD_ID` that the server injects). 2. Inject into `.app-body`. 3. Dispatch `CustomEvent('tabChanged', { detail: { tab: name } })`. 4. Feature modules (`soap.js`, `encounters.js`, etc.) listen for their own tab name and initialize DOM references inside the just-injected fragment. ### Auth model (client) `auth.js` branches on `isNativeApp()`: - **Web** — no localStorage token; relies on the `ped_auth` httpOnly cookie set by the server. `/api/auth/me` on boot verifies the session; failed verification shows the login screen. - **Mobile** — token lives in `capacitor-secure-storage-plugin` (iOS Keychain / Android EncryptedSharedPreferences). `getAuthHeaders()` emits `Authorization: Bearer `. `authFetch.js` wraps `window.fetch` to catch 401 on authenticated `/api/*` requests and force re-login. `BroadcastChannel('pedscribe-auth')` propagates logout to sibling tabs. ### Module load order Fixed in `index.html`: ``` secureStorage → authFetch → auth → (feature modules) ``` All `