Parivar — Technical Architecture & API Reference
Rebuild blueprint for the NestJS (backend) + Next.js (frontend) implementation.
Parivar is a cross-border healthcare platform. Family members living abroad subscribe to arrange in-home nursing visits for their elderly parents in Nepal, view clinical reports, coordinate with hospitals, and chat with an AI assistant. Field nurses execute visits and capture vitals; doctors provide remote clinical oversight; admins run operations.
The original system was built on Lovable + Supabase (Postgres, Edge Functions, Auth, Storage, Realtime). This document re-frames that system as a NestJS + Next.js + Flutter/React Native architecture: it maps every Supabase concept to its NestJS equivalent, organises the surface area into clean modules, and preserves the full endpoint-level detail so the rebuild can be implemented without referring back to the Supabase project.
This is part of a documentation set
This blueprint is the hub. Three companion documents go deeper where the surface is dense, and are authoritative for their areas:
| Document | Authoritative for |
|---|---|
data-model | The real Postgres schema — all 41 tables, enums, constraints, FKs, indexing. Reconstructed from live-DB exports + ERD. |
clinical-reporting | The nurse reporting workflow — four form types, field specs, drafts, versioning, 24h edit window, approval loop. |
account-lifecycle | Deactivation, reactivation (3/7-day + 15-day rules), session revocation, and purge — re-architected for NestJS. |
Source-of-truth rule. The companion docs and the live-DB exports were extracted after the original API reference and are more current. Where they disagree with this hub (notably enum values and the assessment model), they win. This hub has been reconciled to match; remaining ⚠️ markers flag spots to confirm during build.
How to read this document
| Part | Contents |
|---|---|
| Part 1 — Architecture | Stack comparison, concept mapping (Supabase → NestJS), and the proposed module map. |
| Part 2 — Cross-cutting infrastructure | Auth/authorization, error model, pagination, realtime, storage, caching, and the role × endpoint access matrix. |
| Part 3 — Module reference | Every module with its endpoints in full detail: request/response fields, guards, side effects, errors, and the NestJS route that replaces the Supabase call. |
| Part 4 — Integrations & operations | Third-party services, background jobs, the web + mobile client feature map, environment variables, and appendices (auth flows, Stripe webhooks). |
Throughout, a 🔁 Migration note callout flags anything that needs a deliberate decision when moving off Supabase.
Part 1 — Architecture
1.1 Platform at a glance
Four user roles drive the entire domain:
| Role | Who they are | Core jobs |
|---|---|---|
Family Member (customer) | Subscriber abroad (default role on signup) | Manage parent profiles, book visits, read approved reports, pay subscriptions, use PariAI chat, request hospital coordination, earn referrals. |
| Nurse | Field staff in Nepal | Onboard (application → KYC → document signing), manage availability, execute visits, capture vitals, raise escalations, broadcast live location / SOS, track earnings. |
| Doctor | Remote clinical oversight | Review escalations, run teleconsultations, read patient records. Cannot prescribe through the platform. |
| Admin / Super Admin | Operations team | Approve staff, assign nurses, approve reports, manage subscriptions, configure the app, monitor alerts. |
1.2 Stack comparison
| Concern | Current (Supabase / Lovable) | Target (NestJS + Next.js) |
|---|---|---|
| API layer | Edge Functions (Deno) + PostgREST auto REST + RPC | NestJS controllers + services (REST, optionally tRPC/GraphQL) |
| Data access | PostgREST .from() queries from the browser | NestJS service layer over an ORM (Prisma or TypeORM); no direct DB access from the client |
| AuthZ | Row-Level Security (RLS) policies + has_role() | Nest Guards + @Roles() decorators + query-level ownership scoping in services |
| AuthN | Supabase Auth (JWT, OTP, OAuth, identity linking) | Custom Auth module issuing JWTs (or keep an IdP); OTP via Resend; OAuth via Passport strategies |
| Background logic | Postgres triggers + pg_cron | Nest event emitters / queues (BullMQ) + @Cron scheduled jobs; retain a few DB triggers if desired |
| Realtime | Supabase Realtime (Postgres CDC over WebSocket) | Nest WebSocket Gateway (Socket.IO) with rooms, fed by domain events |
| File storage | Supabase Storage buckets | S3-compatible object storage (AWS S3 / Cloudflare R2 / MinIO) via a Storage service |
| AI | Lovable AI Gateway (Gemini, Whisper) | Direct provider SDKs (e.g. Google Gemini / OpenAI) behind an AI service |
| Caching / rate-limit / sessions | (implicit in Supabase) | Redis — response cache, throttling counters, token denylist, BullMQ broker, Socket.IO adapter |
| Mobile | (web-only / responsive) | Flutter or React Native native app (nurse app needs camera, GPS, push, offline drafts) |
| Hosting | Supabase-managed | Containerised NestJS (Node) + Next.js (Vercel/Node), managed Postgres, managed Redis |
Target stack, explicitly: Next.js (web frontend) · NestJS (backend API) · Flutter / React Native (mobile) · Postgres (primary DB) · Redis (cache/queues/realtime) · S3-compatible object storage · plus the unchanged third parties (Stripe, Google OAuth, Didit KYC, Resend, Google Maps, ConnectIPS). Anything not named here follows current industry best practice (e.g. OpenTelemetry for observability, a secrets manager, CI/CD with migrations).
Where Redis is used
| Use | Detail |
|---|---|
| Rate limiting | OTP (5/email/hr), SOS, PariAI chat (30/10min) — @nestjs/throttler with a Redis store |
| Caching | app_config/feature flags, maps key, subscription status, on-call doctor lookup |
| Token denylist | Revoked access-token jtis until expiry (logout, deactivation) — see Account Lifecycle doc |
| Queues | BullMQ for email (Resend), PDF generation, KYC reconciliation, push notifications |
| Realtime scale-out | Socket.IO Redis adapter so the gateway runs multi-instance |
| Idempotency | Submit/payment idempotency keys to dedupe retries |
🔁 Migration note — the biggest shift is the trust boundary. In Supabase the browser talks to Postgres directly and RLS is the only thing standing between a user and other users' rows. In the NestJS rebuild the client never touches the database; every read and write goes through a controller, and authorization becomes explicit guard + service logic. Wherever the source says "RLS scopes rows to
auth.uid()", the rebuild must enforce the same scoping inside the service query.
1.3 Concept mapping (Supabase → NestJS)
| Supabase concept | Appears in source as | NestJS equivalent |
|---|---|---|
| Edge Function | /functions/v1/<name> (POST) | A controller route backed by a service method |
| PostgREST table read/write | .from('table').select/insert/update() | Repository/ORM call inside a service, exposed via a controller |
| RPC | .rpc('fn_name', {...}) | A service method (logic moved from SQL into TypeScript, or a DB function called via ORM) |
| RLS policy | "Permission / RLS: …" rows | RolesGuard + ownership checks in the service where clause |
has_role(auth.uid(),'x') | Role-gated endpoints | @Roles('x') + RolesGuard |
is_super_admin() | Super-admin-only actions | @Roles('super_admin') |
Postgres trigger (trigger_*) | "Side Effects: trigger_… → admin alert" | Domain event emitted by the service → handler creates the alert/notification |
pg_cron job | "(cron)" functions | @Cron() scheduled provider (or external scheduler hitting a protected route) |
| Realtime channel | Section 2 Realtime table | Socket.IO room joined after auth; service emits to the room |
| Storage bucket | reports, wound-photos, staff-documents | S3 buckets/prefixes fronted by a StorageService issuing pre-signed URLs |
handle_new_user trigger | "default role assigned on signup" | AuthService.onUserCreated() assigns the default customer role |
| JWKS JWT validation | "each function validates the JWT" | JwtAuthGuard (Passport JWT strategy) applied globally |
1.4 Proposed NestJS module map
Each domain area becomes a Nest module (*.module.ts with its controller, service, DTOs, and entities). Modules are grouped below by bounded context. Part 3 documents each in full.
| # | Module | Responsibility | Primary roles |
|---|---|---|---|
| 1 | AuthModule | OTP login, session issuance, email checks, 2FA, OAuth/identity linking, account deletion/deactivation/reactivation | All |
| 2 | UsersModule | Profiles, user_roles, admin invites/suspension | Admin |
| 3 | ParentsModule | Parent (patient) CRUD + admission forms | Customer (write), Nurse/Doctor (read), Admin |
| 4 | VisitsModule | Booking wizard, scheduling/assignment, reschedule, visit lifecycle, visit-event timeline | Customer, Nurse, Admin, Doctor (read) |
| 5 | ClinicalModule | Assessments/vitals (draft→submit→approve), wound photos, clinical reports/PDFs | Nurse (write), Admin/Doctor (review), Customer (read) |
| 6 | EscalationsModule | Escalations + responses, doctor routing | Nurse (create), Doctor/Admin (respond) |
| 7 | IncidentsModule ⚠️ | Risk/safety log — no incidents table in live schema; fold into EscalationsModule | Nurse (write), Admin |
| 8 | NurseModule | Onboarding/application, KYC (Didit), document signing, availability, working hours, leave, location/SOS, earnings, bank details | Nurse, Admin |
| 9 | DoctorModule | Case review queue, claim/respond/close, teleconsultations, read-only records | Doctor |
| 10 | AdminModule | Staff lifecycle, customer/subscription oversight, app config, monitoring | Admin / Super Admin |
| 11 | HospitalModule | Hospital requests + escort tasks | Customer (create), Admin |
| 12 | PariAIModule | Streaming clinical-guidance chat + voice transcription | Customer (chat), all (transcribe) |
| 13 | BillingModule | Stripe checkout, customer portal, subscription lifecycle, on-demand payments, webhook | Customer, Admin (read), system |
| 14 | ReferralsModule | Referral accounts, referrals, commission | Customer (read), Admin |
| 15 | NotificationsModule | In-app notifications + transactional email dispatch | All (read own), system |
| 16 | AlertsModule | Admin operations alerts, SOS alerts, monitoring sweeps | Admin, system |
| 17 | ConfigModule | app_config, version gating, maps key | All (read), Admin (write) |
| — | RealtimeGateway | Socket.IO gateway shared across modules | All |
| — | StorageService | Object storage + pre-signed URLs shared across modules | All |
Part 2 — Cross-cutting infrastructure
These concerns are shared by every module. Implement them once (global guards, interceptors, filters, shared services) and reuse.
2.1 API surface & base paths
In the rebuild there is a single API origin (e.g. https://api.parivar.com.au) instead of the multiple Supabase scopes. Map the old scopes as follows:
| Old Supabase scope | Old shape | New NestJS shape |
|---|---|---|
| Edge Functions | …/functions/v1/<fn> | POST /<resource>/<action> (REST controllers) |
| REST / RPC | …/rest/v1/<table> | GET/POST/PATCH/DELETE /<resource> |
| Realtime | wss://…/realtime/v1/websocket | wss://api.parivar.com.au (Socket.IO namespace) |
| Storage | …/storage/v1/object/<bucket>/<path> | Pre-signed URLs from StorageService; download proxy at GET /files/:bucket/:path |
| Web app | https://app.parivar.com.au | Unchanged (Next.js app) |
Each module in Part 3 lists both the original Supabase call and the suggested NestJS route. Routes are suggestions — keep them RESTful and consistent.
2.2 Authentication
All roles share one login flow: email + 6-digit OTP. See Appendix A for the full flow.
Request headers (rebuild). Replace the Supabase apikey + Authorization pair with a single bearer token:
Authorization: Bearer <access_token>
Content-Type: application/json
Token handling.
- Issue a short-lived access token (JWT) and a longer-lived refresh token.
- A global
JwtAuthGuard(Passportjwtstrategy) validates the access token on every protected route and attachesreq.user({ id, role, … }). - Public routes (OTP send/verify, email check, webhooks) opt out with a
@Public()decorator.
🔁 Migration note. Supabase Edge Functions ran with
verify_jwt = falseand validated the JWT in code against the project JWKS. In Nest this becomes the globalJwtAuthGuard; there is no per-function opt-in. Webhook routes (Stripe, Didit) must be explicitly@Public()and instead verified by signature/HMAC.
2.3 Authorization (RBAC + ownership)
Authorization has two layers that together replace RLS:
- Role gate —
@Roles('admin')+RolesGuardreadsreq.user.role(andis_super_adminfor super-admin actions). Equivalent tohas_role(auth.uid(), 'x'). - Ownership scope — even when the role is allowed, services must scope queries to the caller's own rows, exactly as RLS did. Common scoping keys from the source:
parents.user_id = auth.uid()(customer owns parent)visits.client_user_id = auth.uid()(customer owns visit)visits.nurse_id = nurse.id(nurse assigned to visit)created_by = auth.uid()(generic ownership)- Doctor access: parent/assessment is visible only when tied to an escalation/visit the doctor is assigned to or self-claims.
🔁 Migration note. Field-level RLS restrictions must become DTO/service rules. Examples from the source: a nurse capturing vitals cannot set
approved_by/approved_at; a doctor claiming an escalation cannot editparent_id,visit_id, orseverity. Enforce these by whitelisting allowed fields in the update DTO, not by trusting the client.
2.4 Error model
Keep the existing error envelope so clients need no changes:
{
"error": {
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action.",
"details": { "required_role": "admin" }
}
}
Implement with a global HttpExceptionFilter that serialises Nest exceptions into this shape.
| HTTP | Code | Meaning | Nest exception |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Body/query failed validation (Zod or class-validator) | BadRequestException |
| 401 | UNAUTHENTICATED | Missing/invalid JWT | UnauthorizedException |
| 403 | FORBIDDEN | Authenticated but role/ownership denies | ForbiddenException |
| 404 | NOT_FOUND | Resource missing or not visible to caller | NotFoundException |
| 409 | CONFLICT | State conflict (duplicate, overlapping leave) | ConflictException |
| 422 | BUSINESS_RULE | Domain rule blocked the request (e.g. 24h reschedule cutoff) | UnprocessableEntityException |
| 429 | RATE_LIMITED | Throttled (OTP, SOS, AI chat) | ThrottlerException |
| 500 | INTERNAL_ERROR | Unhandled server error | InternalServerErrorException |
Domain-specific codes used by individual endpoints (e.g. INVALID_OTP, RESCHEDULE_CUTOFF, LEAVE_CONFLICT, REACTIVATION_WINDOW_EXPIRED, SAFETY_LANGUAGE_VIOLATION, OPEN_CASES) are listed with each endpoint in Part 3 and should be surfaced in error.code.
2.5 Validation
Use DTOs with class-validator/class-transformer (or Zod via a pipe) and a global ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }). whitelist strips unknown fields — this is also how field-level RLS restrictions are enforced (clients can't smuggle approved_by, etc.).
2.6 Pagination
Replace PostgREST Content-Range with a consistent envelope. Accept ?page=N&pageSize=M (default 25, max 100) and return:
{
"data": [ /* rows */ ],
"page": 1,
"pageSize": 25,
"total": 137
}
Provide a shared paginate() helper (the rebuild's equivalent of src/services/_base.ts). Optionally still emit a Content-Range header for parity.
2.7 Realtime (Socket.IO gateway)
Replace Supabase Realtime channels with a single authenticated Socket.IO gateway. Clients connect with their access token, then join role/ownership-scoped rooms. Services emit domain events to rooms instead of relying on Postgres change-data-capture.
| Room (channel) | Role(s) | Emitted when |
|---|---|---|
visits:client:<user_id> | customer | Visit lifecycle updates for the signed-in family member |
visits:nurse:<nurse_id> | nurse | Assignments and status transitions for the nurse |
visits:admin | admin | All visit changes platform-wide |
assessments:parent:<parent_id> | customer, doctor | New clinical reports for a patient |
escalations | admin, doctor | New/updated escalations |
admin_alerts | admin | Operations alerts (deduped, severity-tagged) |
sos_alerts | admin | Nurse-triggered SOS events |
nurse_locations | admin | Live GPS pings during active visits |
notifications:<user_id> | all | Per-user in-app notifications |
conversations:<conversation_id> | customer, nurse, admin | Chat & voice message stream |
🔁 Migration note. Supabase Realtime broadcast raw table changes; the rebuild emits curated events, so the gateway must enforce that a socket only joins rooms it's authorised for (validate
user_id/nurse_idagainstreq.user). This closes a class of over-broadcast issues that RLS-on-realtime handled implicitly.
2.8 Storage
Replace Supabase Storage with S3-compatible object storage behind a StorageService that issues pre-signed upload/download URLs and enforces size/count limits.
| Bucket / prefix | Contents | Limits (from source) | Path pattern |
|---|---|---|---|
reports | Approved clinical report PDFs | — | reports/<parent>/<visit>.pdf |
wound-photos | Visit documentation photos | ≤ 5 photos × 10 MB per assessment | assessment-photos/<assessment_id>/<uuid>.jpg |
staff-documents | Nurse/doctor onboarding credentials | — | per application |
Access remains role/ownership-scoped: customers read only their own report PDFs; nurses write to their own assessment photos; admins read all.
2.9 Role × endpoint access matrix
Legend: ✓ = full access · R = read-only · W = write/create · — = no access · * = scoped to own records.
| Endpoint / Domain | Family | Nurse | Admin | Doctor |
|---|---|---|---|---|
| — Auth & Account — | ||||
| send-login-otp / verify-login-otp | ✓ | ✓ | ✓ | ✓ |
| check-email-exists | ✓ | ✓ | ✓ | ✓ |
| self-delete-customer-account | ✓* | — | — | — |
| self-delete-nurse-account | — | ✓* | — | — |
| self-delete-doctor-account | — | — | — | ✓* |
| self-deactivate-account | ✓* | ✓* | — | ✓* |
| request-reactivation | — | ✓* | — | — |
| invite-admin / accept-admin-invite | — | — | ✓ | — |
| suspend-admin-user | — | — | ✓ | — |
| — Parents / Patients — | ||||
| parents (CRUD) | ✓* | R* | ✓ | R* |
| admission_forms | ✓* | R* | ✓ | R* |
| — Booking & Scheduling — | ||||
| visits create | ✓* | — | ✓ | — |
| visits read | R* | R* | ✓ | R* |
| reschedule-visit | ✓* | — | ✓ | — |
| create-ondemand-checkout / verify-ondemand-payment | ✓* | — | — | — |
| nurse_leave_requests | — | ✓* | ✓ | — |
| nurse_working_hours | — | ✓* | ✓ | — |
| — Visits & Clinical — | ||||
| visit_events (timeline) | R* | W* | ✓ | R* |
| vitals capture | — | W* | R | R |
| assessments draft / submit | — | W* | R/approve | R/review |
| assessments approve | — | — | ✓ | — |
| wound-photos upload | — | W* | R | R |
| escalations create | — | W* | ✓ | R/respond |
| escalations respond | — | — | ✓ | ✓ |
| incidents | R* | W* | ✓ | R |
| — Reports & PDFs — | ||||
| reports storage | R* | W* | ✓ | R |
| send-purchase-email (report) | — | — | ✓ (trigger) | — |
| — Hospital Coordination — | ||||
| hospital_requests / escort_tasks | ✓* | R* | ✓ | R |
| — PariAI Chat — | ||||
| pariai-chat | ✓* | — | — | — |
| transcribe-voice | ✓ | ✓ | ✓ | ✓ |
| — Payments & Billing — | ||||
| create-checkout | ✓* | — | — | — |
| customer-portal | ✓* | — | — | — |
| check-subscription | ✓* | — | R | — |
| cancel-subscription / reactivate-subscription | ✓* | — | ✓ | — |
| update-subscription-quantity | ✓* | — | ✓ | — |
| stripe-webhook | — | — | system | — |
| nurse_bank_details / withdrawal_requests | — | ✓* | ✓ | — |
| — Nurse Lifecycle — | ||||
| submit-professional-application | — | ✓ | — | ✓ |
| create-staff-user / approve-staff-application | — | — | ✓ | — |
| deactivate-staff / purge-deleted-accounts | — | — | ✓ | — |
| create-didit-session / didit-webhook / reconcile-didit-kyc | — | ✓* | ✓ | ✓* |
| complete-nurse-document-signing | — | ✓* | — | — |
| — Location & SOS — | ||||
| nurse_locations | — | W* | R | — |
| sos_alerts / send-sos-email | — | W* | R | — |
| — Referrals — | ||||
| referral_accounts / referrals | R* | — | ✓ | — |
| — Notifications / Email — | ||||
| notifications | R* | R* | ✓ | R* |
| send-notification-email | — | — | system | — |
| send-admin-alert-email | — | — | system | — |
| — Admin Ops — | ||||
| admin_alerts | — | — | ✓ | R |
| app_config / check-app-version | R | R | ✓ | R |
| get-maps-key | ✓ | ✓ | ✓ | ✓ |
Part 3 — Module reference
📄 The full module-by-module endpoint reference now lives on its own page: Module Reference.
It documents all 17 NestJS modules — every endpoint with its original Supabase call, suggested NestJS route, guards, request/response fields, side effects (the events that replace Postgres triggers), and errors. It was split out of this document to keep the architecture overview readable.
Part 4 — Integrations & operations
4.1 Third-party integrations
| Service | Used for | Touchpoints | Secrets | Rebuild note |
|---|---|---|---|---|
| Stripe | Subscriptions, on-demand payments, billing portal | create-checkout, customer-portal, check/update/cancel/reactivate-subscription, create/verify-ondemand, stripe-webhook | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | Use the official stripe Node SDK in BillingModule. Verify webhook signature in a @Public() route. |
| Resend | All transactional email | OTP, nurse assignment, purchase/report, SOS, admin alerts, welcome, auth templates | RESEND_API_KEY, FROM_EMAIL_NOREPLY | Centralise in a MailService consuming a queue. |
| Didit (v3) | Nurse/doctor KYC | create-didit-session, didit-webhook, reconcile-didit-kyc (10-min cron) | DIDIT_WORKFLOW_ID, DIDIT_WEBHOOK_SECRET | HMAC-verify the webhook; keep the reconciler as a @Cron job. |
| AI provider (was Lovable AI Gateway) | PariAI chat (Gemini), voice transcription (Whisper) | pariai-chat, transcribe-voice | provider API key | Replace the gateway with a direct provider SDK behind AiService. |
| Google Maps | Map pins, location display | get-maps-key, parent lat/lng, nurse_locations | GOOGLE_MAPS_API_KEY | Serve the key via GET /config/maps-key; restrict by referrer/app. |
| ConnectIPS | Nurse payouts (NPR) | nurse_bank_details, withdrawal_requests | — | Payout rails for Nepal; integrate when payouts go live (currently "Coming Soon"). |
| Object storage (S3/R2/MinIO) | Report PDFs, wound photos, staff documents | reports, wound-photos, staff-documents buckets | storage creds | Replaces Supabase Storage; serve via pre-signed URLs. |
| WhatsApp (optional) | Hospital request routing | hospital_requests | — | Optional routing channel noted in the source. |
4.2 Background jobs, crons & event handlers
The original system leaned heavily on Postgres triggers and pg_cron. In the rebuild these become domain events (BullMQ/event emitter) and @Cron providers.
Scheduled jobs
| Job | Cadence | Replaces | Action |
|---|---|---|---|
| Monitoring sweep | every 1 min | check_monitoring_alerts | Flag overdue reports (>90 min) and stale GPS (>5 min) |
| Didit reconciler | every 10 min | reconcile-didit-kyc | Resolve stuck KYC verifications |
| Purge deleted accounts | daily (cron) | purge-deleted-accounts | Hard-delete accounts past the 15-day window (super admin) |
Event handlers (replacing triggers)
| Source trigger | Event | Handler action |
|---|---|---|
handle_new_user | user.created | Assign default customer role; create referral account |
trigger_parent_created_alert | parent.created | Create admin alert |
trigger_admission_form_alert | admission_form.saved | Create admin alert |
trigger_visit_event_on_status_change | visit.status_changed | Append visit_events row |
trigger_nurse_assignment_email | visit.assigned | Send nurse assignment email (Resend) |
trigger_assessment_alert | assessment.submitted | Create admin alert; queue report email |
| (on approve) | assessment.approved | Insert nurse_earnings; send purchase email with PDF |
trigger_escalation_alert | escalation.created | Create admin alert; route to on-call doctor |
trigger_hospital_request_alert | hospital_request.created | Create admin alert (high if urgent) |
trigger_admin_alert_email | admin_alert.created | Send admin alert email (Resend) |
| (on SOS insert) | sos.created | Send SOS email; emit to sos_alerts room |
4.3 Client applications (web + mobile)
There are two clients: a Next.js web app (customers + admin console + doctors) and a Flutter / React Native mobile app (primarily the nurse field app; optionally a customer companion app). Both talk only to the NestJS API and the Socket.IO gateway.
| Client | Audience | Why this client |
|---|---|---|
| Next.js web | Customers, Admins, Doctors | Rich dashboards, tables, report review/approval, scheduling calendar, billing portal |
| Flutter / RN mobile | Nurses (field) | Native camera (wound/report photos), background GPS pings, push notifications, offline draft capture, SOS button |
🔁 Mobile rationale. The nurse workflow needs device capabilities the web can't reliably provide: continuous GPS during a visit (
nurse_locations+location_audit_log), camera capture withcapture="environment", push for new assignments/escalations, and offline-tolerant draft reports that sync when connectivity returns. Build the nurse app native; keep the customer experience on web first and add a customer mobile app later if needed.
Next.js web — feature map
The web app is one Next.js project with role-gated areas behind the shared OTP login. Suggested route groups and the modules they consume:
Shared / auth (all roles) Login (email → OTP), 2FA, account settings (deactivate/delete), in-app notifications, voice transcription helper. Consumes AuthModule, NotificationsModule, ConfigModule (version gate, maps key).
Family Member (customer) app
- Parents: list/create/edit profiles, admission-form wizard → ParentsModule.
- Booking: 4-step visit wizard, reschedule, on-demand same-day checkout → VisitsModule + BillingModule.
- Visits: list, status timeline (live via
visits:client:<id>), approved report viewer/PDF → VisitsModule + ClinicalModule. - Hospital coordination requests → HospitalModule.
- PariAI chat (SSE stream) → PariAIModule.
- Billing: subscription status, checkout, customer portal, seat count → BillingModule.
- Referrals dashboard (read-only) → ReferralsModule.
Flutter / React Native mobile — nurse app feature map
This is the field client. Screens (from the mobile implementation guide):
Dashboard — greeting + availability toggle, alert banners, four metric cards, active-visit card, today's visits, upcoming visits, route snapshot, recent escalations, quick actions.
My Visits — visit cards with contextual actions: Start Visit, Submit Report, End Visit, View/Edit Report. Tapping a visit opens a patient-summary drawer.
Visit lifecycle (client view): assigned_to_nurse --Start Visit→ in_progress --Submit Report→ report_submitted --Admin Approve→ approved (or Admin Reject→ back to in_progress); End Visit without a report → completed.
Feature areas → modules:
- Onboarding: application + document upload, Didit KYC handoff, sign 3 documents → NurseModule.
- Availability toggle, weekly working hours, leave requests → NurseModule.
- Visit execution: assigned visits, Start Visit, the four-form reporting workspace (regular / first-visit / wound-care / clinical-modules), photo upload, drafts, submit/resubmit → VisitsModule + ClinicalModule (see
clinical-reporting). - Escalations (clinical categories incl.
fall_risk,urgent_health) → EscalationsModule. - Background GPS pings + SOS button → NurseModule (location/SOS); realtime to admin.
- Earnings dashboard, bank details (ConnectIPS), withdrawal requests → NurseModule.
Mobile-specific concerns: offline draft cache (sync on reconnect), secure token storage (Keychain/Keystore), background-location permission UX, push notifications (FCM/APNs) for assignments/escalations/SOS acknowledgements, and the app-version force-update gate (/config/version-check).
Doctor app
- Escalation queue (live via
escalations), claim/respond/close → EscalationsModule. - Read-only patient records (parent, admission form, assessments, timeline) → DoctorModule.
- Teleconsultations scheduling/logging → DoctorModule.
Admin console
- Staff management: applications review, create/deactivate staff, admin invites → AdminModule + UsersModule.
- Scheduling: weekly roster, nurse assignment, admin-created leave → VisitsModule + NurseModule.
- Clinical oversight: approve assessments, escalations queue → ClinicalModule + EscalationsModule.
- Operations: admin alerts (live via
admin_alerts), SOS map (sos_alerts,nurse_locations), monitoring → AlertsModule. - Customer & subscription oversight, delete family member → AdminModule + BillingModule.
- App config: versions, feature flags, maintenance mode → ConfigModule.
4.4 Suggested NestJS project layout
src/
main.ts
app.module.ts
common/
guards/ jwt-auth.guard.ts, roles.guard.ts
decorators/ public.decorator.ts, roles.decorator.ts, current-user.decorator.ts
filters/ http-exception.filter.ts
interceptors/ pagination.interceptor.ts
pipes/ validation
realtime/ realtime.gateway.ts (Socket.IO)
storage/ storage.service.ts (S3 pre-signed URLs)
mail/ mail.service.ts (Resend, queue consumer)
ai/ ai.service.ts (chat + transcription)
modules/
auth/ users/ parents/ visits/ clinical/ escalations/
incidents/ nurse/ doctor/ admin/ hospital/ pariai/
billing/ referrals/ notifications/ alerts/ config/
webhooks/ stripe.controller.ts, didit.controller.ts, auth-email.controller.ts
4.5 Environment variables (consolidated)
| Variable | Used by |
|---|---|
DATABASE_URL | Postgres (ORM) |
JWT_ACCESS_SECRET, JWT_REFRESH_SECRET | AuthModule |
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | BillingModule |
RESEND_API_KEY, FROM_EMAIL_NOREPLY | MailService |
DIDIT_WORKFLOW_ID, DIDIT_WEBHOOK_SECRET | NurseModule (KYC) |
AI_PROVIDER_API_KEY | AiService |
GOOGLE_MAPS_API_KEY | ConfigModule |
S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY | StorageService |
REDIS_URL | Cache, BullMQ queues, throttler, token denylist, Socket.IO adapter |
FCM_SERVER_KEY / APNS_* | Mobile push notifications (nurse app) |
JWT_ACCESS_TTL, JWT_REFRESH_TTL | Token lifetimes (align denylist TTL) |
APP_WEB_URL | Email links, Stripe redirects |
Appendix A — Shared auth & OTP
All roles share one login flow: email + OTP via send-login-otp → verify-login-otp. Optional 2FA uses a 6-digit OTP. Mobile native auth (Apple/Google) used Supabase Identity Linking; web used Google OAuth (configure_social_auth). In the rebuild, implement OAuth with Passport strategies and link identities to the same user record.
Password recovery (admin-managed users): 8-digit native code (was via Supabase auth email templates). Custom 2FA OTPs are 6-digit.
Edge-function lifecycle map → NestJS
| Function | Trigger | Outcome | NestJS |
|---|---|---|---|
| send-login-otp | User clicks "Send code" | Insert otp_verifications + Resend email | POST /auth/otp/send |
| verify-login-otp | User submits code | Mints session | POST /auth/otp/verify |
| check-email-exists | Signup screen | Returns existence + active flag | POST /auth/email/check |
| accept-admin-invite | Signed invite link | Creates admin profile + role | POST /admin/invites/accept |
| auth-email-hook | Auth event | Routes auth emails through Resend templates | POST /webhooks/auth-email |
Appendix B — Stripe webhook events
POST /webhooks/stripe (verified with STRIPE_WEBHOOK_SECRET) handles:
| Event | Action |
|---|---|
checkout.session.completed | Activate subscription / create on-demand visit |
customer.subscription.updated | Sync tier, quantity, period dates |
customer.subscription.deleted | Mark inactive; revoke premium features |
invoice.payment_succeeded | Insert payments row; run process_referral_commission |
invoice.payment_failed | Notify customer; flag account in admin alerts |
charge.refunded | Run reverse_referral_commission |
Appendix C — Source mapping & open questions
This document was reconciled against the live-database exports (database_enums.csv, database_constraints.csv), the ERD (Parivar_ERD.mmd), and the implementation guides — see the companion docs. The live schema is authoritative; pull it into the rebuild's ORM as the first build step.
Resolved by the latest stack decision:
- Mobile: Flutter / React Native (nurse field app).
- Caching/queues/realtime scale-out/rate-limiting: Redis.
- DB: Postgres (own-managed, no Supabase).
- Third parties unchanged: Stripe, Google OAuth, Didit KYC, Resend, Google Maps, ConnectIPS.
Still to confirm before building:
- ORM choice (Prisma vs TypeORM) and whether to keep any Postgres triggers as defense-in-depth (most move to app events).
- Identity strategy: self-issued JWT (assumed) vs a managed IdP.
- AI provider for PariAI chat + transcription and the safety-classifier implementation.
- Object storage provider + CDN for report PDFs/photos.
- Whether the
incidentsconcept is dropped in favour ofescalations(recommended — see Module 7). - Pricing finality (on-demand $45 / subscription $29.99/mo) and whether
parents.ageordate_of_birthis the stored field. - Push provider (FCM/APNs) and whether a customer mobile app is in scope.
Reconciliation log (this revision): corrected visit_type (routine/follow_up), escalation_type (7 clinical values), escalation_status (open/under_review/actioned/closed), visit_status (added cancelled), general_condition (8 values); renamed user_bank_details → nurse_bank_details; flagged incidents as absent from the live schema; pointed the assessment model at the Clinical Reporting doc; added Redis + mobile to the stack.
End of document.