Spaces:
Paused
Smart Cargo Monitoring Backend (Enterprise)
Production-style Node.js + Express backend for Smart Cargo Monitoring with:
- EMQX Serverless MQTT TLS ingestion
- PostgreSQL persistence for telemetry, alerts, admin entities
- Service/repository architecture with background jobs
- JWT authentication with refresh tokens and tenant-aware RBAC
- REST APIs for dashboard + admin operations
Project structure
backend/
migrations/
001_initial_schema.sql
seeds/
001_demo_seed.sql
src/
config/
env.js
logger.js
db/
pool.js
transaction.js
migrator.js
migrate.js
seed.js
mqtt/
client.js
topicParser.js
repositories/
adminRepository.js
alertRulesRepository.js
alertsRepository.js
assetRepository.js
auditRepository.js
authRepository.js
reportsRepository.js
telemetryRepository.js
services/
adminService.js
alertEngineService.js
alertsService.js
authService.js
fleetService.js
reportsService.js
runtimeState.js
telemetryIngestService.js
routes/
adminRoutes.js
alertsRoutes.js
authRoutes.js
fleetRoutes.js
healthRoutes.js
index.js
reportsRoutes.js
telemetryRoutes.js
middleware/
authMiddleware.js
errorHandler.js
requestContext.js
jobs/
offlineScannerJob.js
validators/
historyQueryValidator.js
telemetryValidator.js
utils/
appError.js
asyncHandler.js
passwordPolicy.js
time.js
app.js
server.js
Prerequisites
- Node.js 20+
- PostgreSQL 14+
- EMQX Serverless MQTT endpoint
Environment setup
- Copy
.env.exampleto.env - Fill PostgreSQL, EMQX, and JWT credentials
- Ensure the MQTT topic filter remains:
tenant/+/truck/+/container/+/telemetry
Required JWT variables:
JWT_ACCESS_SECRET(>= 32 characters)JWT_REFRESH_SECRET(>= 32 characters)JWT_ACCESS_EXPIRES_IN(default:15m)JWT_REFRESH_EXPIRES_IN(default:7d)JWT_ISSUER(default:smart-cargo-backend)JWT_AUDIENCE(default:smart-cargo-api)
Note:
backend/.envis for standalone backend runs only.- Docker Compose runs use root
.envin the repository root.
SQL source-of-truth and mirrors
Canonical SQL files:
../database/schema.sql../database/seed.sql
Backend runtime mirror files (used by migration runner and container packaging):
migrations/001_initial_schema.sqlseeds/001_demo_seed.sql
When canonical SQL changes, sync mirror files:
- PowerShell:
../scripts/sync-db-sql.ps1 - Bash:
../scripts/sync-db-sql.sh
Database bootstrap (migration-based)
- Install dependencies:
npm install
- Run schema migrations:
npm run db:migrate
- Seed baseline demo data:
npm run db:seed
Optional auto-run flags at startup:
RUN_MIGRATIONS_ON_BOOT=trueRUN_SEEDS_ON_BOOT=true
Run backend
Development:
npm run dev
Production:
npm start
MQTT ingest flow
- Backend connects to EMQX over TLS (
mqtts) using env credentials. - Subscribes to:
tenant/+/truck/+/container/+/telemetry - Each message is queued through a bounded concurrent ingest queue.
- Topic is parsed to extract tenant/truck/container codes.
- Payload is validated (
env.temperatureC,gas.mq2Raw,motion.shockrequired). - Tenant/fleet/truck/container references are resolved from PostgreSQL.
- In one transaction:
- insert into
telemetry_history - upsert into
telemetry_latest - evaluate/open/update/resolve alerts
- write
alert_eventson state transitions
- insert into
This keeps MQTT handling non-blocking and isolates DB-heavy logic in service/repository layers.
Alert engine design
Implemented rules:
HIGH_TEMPERATURE: open/update whenenv.temperatureC > thresholdGAS_SPIKE: open/update whengas.mq2Raw > thresholdSHOCK_DETECTED: open/update whenmotion.shock == trueGPS_LOST: open/update whengpsFix == falseOFFLINE: opened by scanner when no telemetry forOFFLINE_THRESHOLD_MS
Resolution behavior:
- High temperature, gas spike, gps lost, offline auto-resolve when condition clears.
- Shock auto-resolve controlled by
ALERT_AUTO_RESOLVE_SHOCK(default false).
Rule sources:
- Uses
alert_rulesrows when configured/enabled. - Falls back to env defaults when no rule exists.
Offline scanner job
- Runs every
OFFLINE_SCAN_INTERVAL_MS. - Scans
telemetry_latestfor stale units. - Opens/updates
OFFLINEalerts transactionally.
REST API endpoints
Authentication:
POST /api/auth/loginGET /api/auth/mePOST /api/auth/refresh
Public endpoint:
GET /api/health
All other endpoints require Authorization: Bearer <accessToken>.
Telemetry and dashboard:
GET /api/fleet/summaryGET /api/fleet/unitsGET /api/trucks/:truckId/containers/:containerId/latestGET /api/trucks/:truckId/containers/:containerId/historyGET /api/alertsGET /api/alerts/:alertId/eventsGET /api/alerts/historyPATCH /api/alerts/:alertId(body: action=ACKNOWLEDGE|RESOLVE, optional message)GET /api/reports/fleet-summaryGET /api/reports/alert-summaryGET /api/reports/device-health-summary
Admin and business:
GET /api/admin/tenantsGET /api/admin/rolesGET /api/admin/usersPOST /api/admin/usersPATCH /api/admin/users/:idPOST /api/admin/users/:id/reset-passwordGET /api/admin/device-registryGET /api/admin/audit-logs
Compatibility endpoints retained for existing frontend:
GET /api/latestGET /api/latest/:truckId/:containerIdGET /api/history/:truckId/:containerIdGET /api/history?truckId=...&containerId=...GET /api/telemetry/history/:truckId/:containerId
RBAC model
super_admin: full cross-tenant access.tenant_admin: tenant-scoped user/admin management and read/write alert operations.fleet_manager: tenant-scoped telemetry/fleet/alert reads and alert state transitions.viewer: tenant-scoped read-only access.
Note: admin role is still accepted for backward compatibility and treated as tenant admin scope.
Tenant scope rules:
- Non-super users are automatically constrained to their own tenant.
- Super admins may query across tenants by setting
tenantCode. - Cross-tenant requests by non-super users are rejected with
403.
History query parameters
GET /api/trucks/:truckId/containers/:containerId/history
from(ISO timestamp)to(ISO timestamp)limit(clamped byHISTORY_MAX_LIMIT)bucketMinutes(chart bucket size in minutes)interval(5m,15m,1h, or text like5 minutes)
Behavior:
- Without
bucketMinutes/interval: raw time-ordered points. - With
bucketMinutesorinterval: bucketed/aggregated points optimized for charts.
GET /api/alerts/history query params:
status(CSV:OPEN,ACKNOWLEDGED,RESOLVED)severity(CSV:INFO,WARNING,CRITICAL)tenantId(UUID, super admin support)truckIdcontainerIdfromtolimit(clamped byHISTORY_MAX_LIMIT)
GET /api/reports/* query params:
tenantId(UUID, optional for super admin)fromandtofor time-windowed summaries (fleet/alert)bucketMinutesfor timeline series density (fleet/alert)offlineMinutesandlimitfor device health summary
Operational notes
- Admin write operations and password resets generate
audit_logsentries. - Alert transitions and ingestion writes run in DB transactions.
- Runtime health includes DB status, MQTT status, queue backlog, and offline scanner telemetry.
Detailed reporting query explanations and response examples:
docs/reporting-and-history-apis.md