Backend and API
Purpose
static-web-app/api/ is the main backend surface. It is an Azure Functions v4 application that exposes HTTP routes for auth, administration, telemetry ingestion, emergency workflows, diagnostics, and device management.
Entry point and module model
src/functions/index.jsregisters route modules with safe loading so one broken route file does not stop the entire API from starting.- Route files live under
src/routes/. - Shared business logic lives under
src/services/. - Shared concerns such as auth, RBAC, rate limiting, response envelopes, and DB access live under
src/middleware/andsrc/utils/.
Major route domains
| Domain | Files |
|---|---|
| Auth and users | auth.js, users.js |
| Core locations/devices | buildings.js, floors.js, zones.js, devices.js, receivers.js |
| Personnel and assets | personnel.js, personnel-photo.js, assets.js, departments.js |
| Telemetry and RTLS | iotHub.js, raddecIngest.js, positions.js, context.js, ambient.js, calibration.js |
| Emergency | emergency.js, communications.js, rules.js, musterPoints.js |
| Monitoring | dashboard.js, battery.js, diagnostics.js, weather.js, health.js, analytics.js, activity.js |
| Gateway support | desktopGateway.js, knotIngest.js, arubaIngest.js, phoneBeacon.js |
| Administration | admin.js (BLE runtime settings, ambient config), settings.js (BLE tracking settings), audit.js (audit log reads/writes) |
| Platform support | realtime.js (Centrifugo negotiate — issues HS256 JWT for the self-hosted relay), upload.js (file/image upload), docs.js (docs asset serving) |
| Dev/debug | dev.js (development-only diagnostic endpoints) |
API behavior conventions
- Route handlers usually return the shared
{ success, data|error }response envelope. - Many endpoints are anonymous at the Functions host level and enforce auth in middleware instead.
- RBAC uses
requirePermission(resource, action). - Health and public routes use
withPublic; authenticated routes commonly usewithAuth.
Auth and RBAC
Current login paths
- email/password login
- Okta-backed upstream login when
oktaflag is provided (active — Auth0 removed)
Important current behavior
- The backend accepts Okta login payloads (
{email, name, okta: true, groups}). - Okta SSO is the active frontend provider via
nuxt-oidc-auth. - JWT issuance, refresh, revocation, and permission loading are handled in backend services.
Backend service domains
| Service area | Files |
|---|---|
| Emergency orchestration | notificationOrchestrator.js, employeeEscalationService.js, emergencyService.js, escalationScheduler.js |
| Real-time messaging | webPubSubService.js |
| Positioning | trilaterationService.js, positionSmoother.js, kalmanFilter.js |
| External providers | twilioSmsService.js, twilioVoiceService.js, smsService.js, voiceService.js, emailService.js, weatherService.js |
| Rules/automation | ruleEngineService.js, ruleTriggerService.js, scheduledRuleService.js |
| Voice IVR | ivrStateMachineService.js — multi-level interactive voice response for emergency calls; manages state transitions (main menu → floor capture → transfer → confirmation) |
| Outage detection | outageDetectionService.js — monitors gateway heartbeats and detects power outage patterns (single gateway, building partial/full, campus-level) using configurable thresholds |
| Provider health | providerHealthService.js — tracks external service availability (SMS, email, voice) and surfaces status in the emergency UI |
| Token management | tokenService.js — JWT issuance, refresh, and revocation |
Middleware layer
Route handlers pass through middleware in src/middleware/ and auth wrappers in src/utils/withAuth.js:
| File | Purpose |
|---|---|
middleware/auth.js | JWT decode and user attachment; used by withAuth and route-level auth checks |
middleware/rbac.js | requirePermission(resource, action) — checks loaded role/permission set |
middleware/rateLimit.js | Configurable per-route rate limiting |
middleware/performance.js | Request timing and performance logging |
utils/withAuth.js | withAuth wrapper (authenticated routes) and withPublic wrapper (unauthenticated routes) |
Utils layer
Shared concerns in src/utils/:
| File | Purpose |
|---|---|
database.js | getDb() — returns a pooled PostgreSQL client |
response.js | respond() helper and CORS headers; toCamelCase() and toSnakeCase() key converters |
validate.js | Zod-based request body and parameter validation helpers |
cache.js | In-memory and optional Redis-backed caching |
constants.js | Shared application-level constants (RBAC resources, scenario types, etc.) |
password.js | bcrypt hashing and comparison |
rpaResolver.js | Resolves Bluetooth Resolvable Private Addresses (RPAs) against stored IRKs using the BT Core Spec ah() function |
sniffypedia.js | Sniffypedia integration — maps BLE company IDs and service UUIDs to device metadata |
serviceHelpers.js | Common patterns for service-layer error handling and retries |
timestamps.js | parseKnotTimestamp(), parseBattery(), parseTemp() — normalises hardware-format telemetry strings |
Storage abstraction layer
src/storage/ provides a backend-agnostic database interface. Most route files use getDb() from utils/database.js directly, but the storage layer is used for heavier query operations and exists to support potential future adapter swaps.
| File | Purpose |
|---|---|
storage/index.js | getStorage() singleton factory — reads STORAGE_BACKEND env var (default: postgresql) |
storage/adapters/postgresql.js | PostgreSQLAdapter class — wraps a pg.Pool with domain-specific query methods |
PostgreSQL adapter pool configuration
The adapter is tuned for Azure B1ms hosting constraints:
| Setting | Value | Reason |
|---|---|---|
max pool connections | 4 | Azure B1ms: 50 Postgres max, 10 superuser-reserved = 40 for app; 4/instance supports 10 scale-out instances |
min pool connections | 0 | Pool shrinks to zero when idle to avoid exhausting the 40-slot limit |
idleTimeoutMillis | 10 000 ms | Release idle connections quickly |
connectionTimeoutMillis | 10 000 ms | Azure Burstable cold-start headroom |
statement_timeout | 15 000 ms | Hard query timeout |
| SSL | rejectUnauthorized: false | Azure PostgreSQL Flexible Server with self-signed cert |
The adapter includes helper functions gatewayOnlineStatusSql() and gatewayLastSeenSql() to centralize the gateway online/offline determination logic used across multiple queries.
Timer triggers
In addition to HTTP routes, the Functions app registers a timer-triggered function:
| File | Schedule | Purpose |
|---|---|---|
src/functions/auditCleanup.js | Configurable via AUDIT_CLEANUP_CRON env var | Deletes audit log records older than a configured retention window from the audit_log table |
The cleanup job prevents unbounded audit table growth on long-running deployments.
Current backend coding conventions
- CommonJS modules are the dominant pattern
- Existing route/service files use semicolons and 4-space indentation; follow local file style instead of forcing frontend formatting rules into backend files
- Defensive startup and runtime patterns are common: safe requires, lazy-loading, query timeouts, bounded concurrency, input validation helpers
Useful commands
cd static-web-app/api
npm start
npm run lint
npm testBackend-specific handoff notes
iotHub.jsis central and high-risk: ingestion, receiver selection, runtime BLE settings, positioning, and realtime broadcast all meet there.desktopGateway.jsis the contract boundary for the Windows service.- Locked files exist for the IoT/ambient route area; check
.claude/LOCKS.mdbefore editing them. - The health endpoint reports database, cache, Azure Storage, and basic runtime metrics and is the quickest backend sanity check.