CODEX // oaoisme wiki

§ unswayed-backend · Platform infrastructure (Phase 1)

Storage, mailer, audit & seed CLI

updated 2026-06-04

The remaining Phase 1 ports — each a thin, swappable seam the later domains consume.

Media storage (MediaStoragePort)

One port — upload / delete / assetUrl — behind a single Cloudinary adapter, so resumes, posts/reels, profile photos, and blog thumbnails all share one implementation instead of each reimplementing uploads. The adapter detects resource_type (image vs video), and per-endpoint size/MIME limits live in the consuming DTOs, not the adapter, so they're validated before any I/O. When Cloudinary isn't configured a LogStorageService keeps dev flows working. assetUrl ports the legacy secureAsset helper (videos under /video/storage/, everything else under /storage/).

Transactional mailer

TransactionalMailerService is both a producer and a consumer. A queue* method per mailable enqueues a job onto the mail queue with attempts:3 + backoff (mirroring the one legacy job that got retries right); in onModuleInit it registers a processor per job that renders the mailable from a pure catalogue and sends it through the SMTP transport. The catalogue is 15 mailables with exact subjects and from-names preserved verbatim (most are "Ralli Technologies", the hiring-pipeline ones "Unswayed"). Two carry extra behavior: JobApplicationToEmployer resolves attachment bytes (fetch, with a graceful skip on failure), and JobSkillNotification is gated by NotificationPreference.jobRelatedEmails. OTP and welcome now run through this queue — the auth NotificationService adapter enqueues them instead of sending inline, so a transient SMTP failure is retried, not lost. Dead legacy stubs (JobEmail/SendJobEmail) are not ported, and SMS OTP is intentionally disabled behind a flag (no fake send).

Audit log

The legacy logs table was dead scaffolding — never written to. Phase 1 builds a real AuditLog + an AuditLogService for security-relevant events (device-token set, account-state changes, …), recorded via an opt-in @AuditLog('…') decorator + interceptor (not registered globally — each route opts in). Writes are best-effort so an audit failure never breaks the request.

Seed/import CLI

Two nest-commander commands run via src/cli.ts (outside the HTTP app):

  • import:countries — pulls country/state/city data from countrystatecity.in and upserts it, with retry/backoff and a --resume mode for the heavy sequential import.
  • sync:permissionsupserts the fixed admin permission list.

Both replace the legacy commands that truncated tables on every run (a transient mid-import failure could wipe the reference data). Now a re-run is a safe no-op. These operate on minimal Country/State/City/Permission tables; Phase 2 owns their full definition and the list endpoints that read them.