§ unswayed-backend
Platform infrastructure (Phase 1)
Phase 1 builds the cross-cutting platform layer every later domain stands on. It ships no business feature of its own except the notifications surface — instead it lands the shared machinery (queue, storage, push, realtime, mailer, audit, seed tooling) behind clean ports, so Phases 2–9 plug into a stable, tested surface instead of each reinventing infrastructure.
What it adds
| Module | What it is |
|---|---|
src/queue/ |
A durable background-job queue behind QueuePort. BullMQ when Redis is present, a Prisma-backed DB queue otherwise — see Background queue (Redis-optional). |
src/storage/ |
MediaStoragePort + a Cloudinary adapter (resumes, posts, photos, blog). |
src/push/ |
FCM v1 PushPort + a DeviceToken table + the §23 set-token endpoint. |
src/notifications/ |
The §23 read/write surface — see Notifications. |
src/realtime/ |
A Socket.IO gateway with authenticated ralli.* channels. |
src/mailer/ |
A queued transactional email catalogue — one job per mailable. |
src/audit/ |
A real AuditLog + an opt-in interceptor for security events. |
src/cli/ |
Idempotent import:countries / sync:permissions commands. |
The one rule that shapes it: Redis is optional
The original plan demanded BullMQ + Redis and "fail fast and loud on missing Redis." The product owner overrode that: if Redis is unset or unreachable, fall back to a database queue — never crash. That single decision (ADR-0020) is why the queue is a port with two interchangeable adapters, and it gives a happy side-effect: the whole test suite (npm run verify) runs with no Redis at all.
How it's wired
Everything follows the repo's ports-and-adapters rule (the same shape as the existing NotificationService): a service depends on an abstract port; an adapter is chosen at boot by a factory from typed config; tests inject a fake. The QueueModule, StorageModule, RealtimeModule, and AuditModule are @Global, so any module injects their ports without re-importing.
The legacy system had three infrastructure bugs that silently broke things; Phase 1 fixes each in the internals while keeping the wire identical:
- OTP emails never delivered until someone manually ran
queue:restart→ a managed worker with stale-lock recovery + retries. - Every FCM push silently failed (empty body from a non-existent
messagefield; a malformed URL with a literal{env('FCM_PROJECT_ID')}) → body = description, URL built from typed config. - Any client could read any user's private realtime stream (
return trueon every channel) → real per-user / per-participant authorization.
Proof it works
npm run verify is green: 707 unit tests at 99.98% coverage + 44 e2e. The Redis-optional behavior is verified live against the GET /api/health/queue probe in all three modes (Redis up → bullmq; Redis down → database with a warning; explicit database).
See the subpages for the queue internals, the notifications surface, push + realtime, and the storage/mailer/audit/CLI ports.
Subpages