Parivar — Module Reference (NestJS)
Every endpoint from the original system is documented below, grouped by NestJS module rather than by role. For each endpoint you get: the original Supabase call, the suggested NestJS route, permission/guards, request and response fields, side effects (the events that replace Postgres triggers), and errors.
Field tables use Req to mean required. Types are kept as in the source (uuid, text, timestamptz, jsonb, float8, numeric, int, enum).
Module 1 — AuthModule
Shared login, session, 2FA, and account-lifecycle endpoints for all roles. The default role assigned on signup is customer (the old handle_new_user trigger → AuthService.onUserCreated()).
1.1 send-login-otp
| Original | POST /functions/v1/send-login-otp |
| NestJS | POST /auth/otp/send |
| Permission | Public (@Public()). Anyone may request an OTP for an email they own. |
| Guards / limits | Max 5 OTPs per email per hour. 6-digit code, 10-min TTL. Apply @Throttle. |
| Side effects | Insert otp_verifications row; send email via Resend (FROM_EMAIL_NOREPLY). |
| Dependencies | Resend |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| string | yes | Email of the account | |
| purpose | enum(login|2fa) | no | Defaults to login |
{ "email": "anita@example.com" }
Response
| Field | Type | Notes |
|---|---|---|
| success | boolean | |
| expires_at | timestamptz | OTP TTL (10 min) |
{ "success": true, "expires_at": "2026-05-24T14:10:00Z" }
Errors
| Status | Code | Message |
|---|---|---|
| 429 | RATE_LIMITED | Too many OTP requests for this email |
| 404 | NOT_FOUND | No account with this email |
1.2 verify-login-otp
| Original | POST /functions/v1/verify-login-otp |
| NestJS | POST /auth/otp/verify |
| Permission | Public. Exchanges OTP for an authenticated session. |
| Side effects | Mints access + refresh tokens (replaces Supabase session). |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| string | yes | ||
| code | string | yes | 6-digit |
| purpose | enum | no | Default login |
{ "email": "anita@example.com", "code": "482910" }
Response
| Field | Type | Notes |
|---|---|---|
| session | object | { access_token, refresh_token } |
| user | object | User payload |
Errors
| Status | Code | Message |
|---|---|---|
| 400 | INVALID_OTP | Code does not match |
| 400 | OTP_EXPIRED | Code older than 10 min |
1.3 check-email-exists
| Original | POST /functions/v1/check-email-exists |
| NestJS | POST /auth/email/check |
| Permission | Public. Used by the signup screen. |
| Response | { exists: boolean, active: boolean } |
1.4 self-delete-customer-account
| Original | POST /functions/v1/self-delete-customer-account |
| NestJS | POST /auth/account/delete (role-routed) |
| Permission | Authenticated customer only. Soft-deactivates and bans the account for 876600h. |
| Side effects | Cancel Stripe subscription, anonymise profile, revoke sessions, remove parents. |
| Dependencies | Stripe |
Request — { confirmation: "DELETE" } (must equal "DELETE"). Response — { success: boolean }.
1.5 self-deactivate-account / request-reactivation / self-delete-(nurse|doctor)-account
📄 The full deactivation/reactivation/purge system is documented in
account-lifecycle— soft-deactivation states, enforcement layers, the 3-request/7-day and 15-day rules, session revocation via the Redis token denylist, and the FK-ordered purge. The summary below is the endpoint surface only.
These share the lifecycle pattern across roles. Customer/doctor variants live here; nurse-specific reactivation is detailed under Module 8 §8.6.
| Endpoint | Roles | NestJS | Behaviour |
|---|---|---|---|
| self-deactivate-account | customer, nurse, doctor | POST /auth/account/deactivate | Sets is_active=false; bans login 876600h; cancels future visits. Starts 15-day reactivation window (nurse). |
| request-reactivation | nurse | POST /auth/account/reactivate | Within 15-day window; fail-fast if expired → 422 REACTIVATION_WINDOW_EXPIRED. |
| self-delete-nurse-account | nurse | POST /auth/account/delete | Hard self-delete (after window or by request). Body { confirmation: "DELETE" }. |
| self-delete-doctor-account | doctor | POST /auth/account/delete | Open cases must be re-assigned first → 422 OPEN_CASES. Body { confirmation: "DELETE" }. |
🔁 Migration note. The "ban for 876600h" trick is a Supabase Auth construct (a 100-year ban ≈ permanent). In the rebuild, model account state explicitly (
status: active | deactivated | deleted,reactivation_deadline) and reject login in the Auth service rather than relying on a ban timestamp.
1.6 invite-admin / accept-admin-invite / suspend-admin-user
Admin-only; documented under Module 2 — UsersModule §2.x (admin invites). Listed here only for the auth relationship.
Module 2 — UsersModule
Profiles, role assignment, and admin user management.
2.1 List customers (admin)
| Original | .from('profiles').select('*', user_roles!inner(role)).eq('user_roles.role','customer') |
| NestJS | GET /admin/customers |
| Permission | Admin. |
2.2 invite-admin / accept-admin-invite
| Original | POST /functions/v1/invite-admin · /accept-admin-invite |
| NestJS | POST /admin/invites · POST /admin/invites/accept |
| Permission | Super admin creates a token-based invite; recipient accepts via a signed one-time link. accept is @Public() (validated by token). |
invite request
| Field | Type | Req | Notes |
|---|---|---|---|
| text | yes | ||
| admin_level | enum(admin|super_admin) | no |
Response — { invite_url: string } (one-time signed URL).
2.3 suspend-admin-user
| Original | POST /functions/v1/suspend-admin-user |
| NestJS | PATCH /admin/users/:id/suspend |
| Permission | Super admin only. |
| Request | { target_user_id: uuid, suspend: boolean } |
Module 3 — ParentsModule
Parent (patient) records and the long-form admission intake. Customers own and write; nurses/doctors read scoped rows; admins have full access.
3.1 List my parents
| Original | .from('parents').select('*') (GET) |
| NestJS | GET /parents |
| Permission | Read own rows only (RLS parents.user_id = auth.uid() → service scoping). |
Response fields
| Field | Type | Notes |
|---|---|---|
| id | uuid | |
| name | text | |
| date_of_birth | date | |
| city | text | Nepal city |
| landmark | text | Preferred over street address |
| lat / lng | float8 | Map pin |
| relationship | text | mother/father/… |
[{ "id":"...", "name":"Mr. Sharma", "city":"Kathmandu", "landmark":"Near Bir Hospital" }]
3.2 Create parent
| Original | .from('parents').insert(...) (POST) |
| NestJS | POST /parents |
| Permission | Insert with user_id = auth.uid(). |
| Side effects | trigger_parent_created_alert → admin alert (emit parent.created event). |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| name | text | yes | |
| date_of_birth | date | yes | |
| relationship | text | yes | |
| city | text | yes | |
| landmark | text | no | Strongly recommended |
| lat | float8 | no | |
| lng | float8 | no |
Response — { id: uuid }.
3.3 Admission form
| Original | .from('admission_forms').upsert(...) (POST/PATCH) |
| NestJS | PUT /parents/:parentId/admission-form |
| Permission | Create/update the long-form intake. Bi-directional sync with parent fields. |
| Side effects | trigger_admission_form_alert → admin alert (emit admission_form.saved). |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| parent_id | uuid | yes | |
| full_name | text | yes | |
| conditions | jsonb | no | Array of chronic conditions |
| medications | jsonb | no | |
| allergies | jsonb | no |
Response — { id: uuid, created_by: uuid }.
Nurses and doctors get read-only access to parents/admission forms for patients tied to their visits/escalations (see Modules 8 and 9).
Module 4 — VisitsModule
Booking wizard, admin assignment, reschedule, the visit lifecycle, and the auto-built event timeline. This is the busiest module — it touches customers, nurses, admins, and doctors (read).
Visit status lifecycle (live enum visit_status): pending_admin_assignment → assigned_to_nurse → in_progress → report_submitted → approved; plus completed (visit ended without a report) and cancelled. Admin rejection sends report_submitted → in_progress. The timeline (visit_events) is appended on each transition.
⚠️ Enum correction. The live
visit_typeenum isroutine/follow_uponly — notregular/first_assessment/on_demand. "First visit" is not a visit_type; it is detected by the absence of afirst_visit_assessmentsrow for the parent (see Clinical Reporting doc). On-demand is a billing path (Module 13), not an enum value. There is also norequested_time_windowenum in the live schema — visits carry ascheduled_date; confirm whether a time-window column exists before building the wizard.
4.1 Create visit (booking wizard) — customer
| Original | .from('visits').insert(...) (POST) |
| NestJS | POST /visits |
| Permission | Customer-initiated; status starts pending_admin_assignment. |
| Guards / limits | First-ever visit triggers the First Visit Assessment flow (no prior first_visit_assessments row). |
| Side effects | Insert visit_events (visit_requested); create admin alert (emit visit.requested). |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| parent_id | uuid | yes | |
| scheduled_date | date | yes | |
| visit_type | enum(routine|follow_up) | yes | Live visit_type enum |
| notes | text | no | |
| client_user_id | uuid | auto | Defaults to caller; denormalised for scoping |
{"parent_id":"p_123","scheduled_date":"2026-06-01","visit_type":"routine"}
Response — { id: uuid, status: "pending_admin_assignment" }.
4.2 reschedule-visit — customer
| Original | POST /functions/v1/reschedule-visit |
| NestJS | POST /visits/:id/reschedule |
| Permission | Customer reschedules an upcoming visit. 24h cutoff; recurring visits limited to 1 reschedule/month. |
Request — { visit_id: uuid, new_date: date, new_time_window: enum }. Response — { visit: object }.
Errors
| Status | Code | Message |
|---|---|---|
| 422 | RESCHEDULE_CUTOFF | Within 24h of visit |
| 422 | MONTHLY_LIMIT | Recurring reschedule limit reached |
4.3 List my visits
| Original | .from('visits').select('*', parents(name), nurses(name)) |
| NestJS | GET /visits |
| Permission | Scoped: customer by client_user_id, nurse by nurse_id, admin all, doctor by linked escalation/visit. |
Response — id, status (enum), nurse_id (null until assigned), start_time, end_time.
4.4 Visit timeline
| Original | .from('visit_events').select('*').eq('visit_id', id) |
| NestJS | GET /visits/:id/events |
| Permission | Read-only log auto-built by trigger_visit_event_on_status_change (rebuild: append in the status-change handler). |
Response
| Field | Type | Notes |
|---|---|---|
| event_type | enum | visit_requested, nurse_assigned, visit_started, report_submitted, report_approved, visit_completed |
| actor_type | enum(customer|nurse|admin) | |
| created_at | timestamptz |
4.5 Assign nurse to visit — admin
| Original | .from('visits').update({ nurse_id, status:'assigned_to_nurse' }) |
| NestJS | PATCH /admin/visits/:id/assign |
| Permission | Admin sets nurse_id. |
| Side effects | trigger_nurse_assignment_email → Resend; insert visit_events.nurse_assigned. |
| Dependencies | Resend |
4.6 Scheduling roster (weekly view) — admin
| Original | .from('visits').select('*', nurses(name), parents(name)).gte('requested_date', …) |
| NestJS | GET /admin/schedule?from=…&to=… |
| Permission | Admin. Color-coded calendar source. |
4.7 Start visit — nurse
| Original | .from('visits').update({ status:'in_progress', start_time: now() }) |
| NestJS | PATCH /visits/:id/start |
| Permission | Nurse may only update their own assigned visits. |
| Side effects | Insert visit_events.visit_started; open monitoring window (90-min overdue alert). |
On-demand booking (
create-ondemand-checkout/verify-ondemand-payment) lives in Module 13 — BillingModule because it is Stripe-driven, but it creates a visit row here on success.
Module 5 — ClinicalModule
Vitals/assessment capture (draft → submit → approve), wound photos, and clinical report PDFs. Nurses write during a visit; admins approve; doctors review; customers read approved reports.
📄 This module is documented in full in
clinical-reporting. The live system is a multi-form workspace —assessments(mandatory regular report),first_visit_assessments(lock-once intake),wound_care_forms(additive), andclinical_modules(optional) — with drafts, a 24-hour edit window,assessment_versionssnapshots, and an admin approve/reject loop. The summary below is reconciled to the live schema; defer to the companion doc for field-level specs.
⚠️ Enum/column corrections. Approval uses
approval_status(pending/approved/rejected) andadmin_viewed, not astatus='approved'field. Vitals arebp_systolic_1/bp_diastolic_1(+ optional second reading),respiratory_rate,pulse_rate,temperature,spo2,blood_sugar_value/blood_sugar_type.general_conditionuses the 8-value live enum.nurse_idis thenursesrow id, not the user id.
Assessment lifecycle: nurse drafts (is_draft=true) → nurse submits (is_draft=false, visits.status='report_submitted') → admin approves (approval_status='approved', visits.status='approved', triggers payout + customer email) or rejects (approval_status='rejected', back to in_progress).
5.1 Capture vitals (draft assessment) — nurse
| Original | .from('assessments').upsert({ is_draft:true, ... }) |
| NestJS | PUT /visits/:visitId/assessment/draft |
| Permission | Nurse writes vitals during the visit. Field-level restriction: cannot set approved_by or approved_at (enforce via DTO whitelist). |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| visit_id | uuid | yes | |
| parent_id | uuid | yes | |
| bp_systolic | int | yes | Color-coded urgency thresholds apply |
| bp_diastolic | int | yes | |
| pulse | int | yes | |
| temperature | numeric | yes | °C |
| spo2 | int | yes | |
| blood_sugar | numeric | no | |
| notes | text | no | Mandatory template sections must be filled before submit |
5.2 Upload wound / documentation photos — nurse
| Original | POST storage → bucket wound-photos |
| NestJS | POST /assessments/:id/photos (pre-signed upload via StorageService) |
| Permission | Up to 5 photos × 10 MB each per assessment. Path assessment-photos/<assessment_id>/<uuid>.jpg. |
Response — { key: string } (storage object key).
5.3 Submit assessment — nurse
| Original | .from('assessments').update({ is_draft:false, doctor_review_required, emergency_escalation }) |
| NestJS | POST /assessments/:id/submit |
| Permission | Locks vitals; triggers admin alert + report email queue. |
| Side effects | trigger_assessment_alert → admin alert; admin approval later triggers payout calculation. |
Request — doctor_review_required (boolean, optional), emergency_escalation (boolean, optional; critical → severity=critical alert).
5.4 Approve assessment — admin (or doctor override)
| Original | .from('assessments').update({ status:'approved', approved_by: auth.uid(), approved_at: now() }) |
| NestJS | POST /admin/assessments/:id/approve |
| Permission | Admin (or doctor with override) approves a submitted report → triggers payment & customer email. |
| Side effects | Insert nurse_earnings (pending → credited on payout); fire send-purchase-email with PDF link. |
| Dependencies | Resend |
5.5 View approved clinical report — customer
| Original | .from('assessments').select('*').eq('parent_id', pid).eq('is_draft', false) |
| NestJS | GET /parents/:parentId/reports |
| Permission | Customer reads only assessments where visit.client_user_id = auth.uid() and status approved. PDF in reports bucket. |
Response
| Field | Type | Notes |
|---|---|---|
| id | uuid | |
| bp_systolic / bp_diastolic | int | |
| pulse / temperature / spo2 / blood_sugar | numeric | |
| summary | text | Plain-language summary |
| pdf_path | text | reports/<parent>/<visit>.pdf |
Module 6 — EscalationsModule
Clinical escalations raised by nurses, responded to by doctors and admins. Drives the doctor queue and on-call routing.
⚠️ Enum corrections (live schema).
escalation_typeis one ofdoctor_review,urgent_health,medication_issue,fall_risk,hospital_support,family_followup,admin_support— notclinical/safety/logistical.escalation_statusisopen→under_review→actioned→closed— notopen/in_review/resolved.escalation_severityislow/medium/high/critical.
Escalation status: open → under_review → actioned → closed.
6.1 Create escalation — nurse
| Original | .from('escalations').insert(...) |
| NestJS | POST /escalations |
| Permission | Nurses raise escalations during or after a visit. |
| Side effects | trigger_escalation_alert → admin alert; routed to on-call doctor if clinical. |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| parent_id | uuid | yes | |
| visit_id | uuid | no | |
| escalation_type | escalation_type enum | yes | doctor_review|urgent_health|medication_issue|fall_risk|hospital_support|family_followup|admin_support |
| severity | escalation_severity enum | yes | low|medium|high|critical |
| reason | text | yes |
6.2 Doctor escalation queue
| Original | .from('escalations').select('*', parents(*), visits(*), assessments(*)).or('assigned_doctor_id.eq.{me},status.eq.unassigned') |
| NestJS | GET /doctor/escalations |
| Permission | Doctor sees unassigned + their own assigned escalations. |
Response — escalation_type, severity, reason, status (open/under_review/actioned/closed).
6.3 Claim escalation — doctor
| Original | .from('escalations').update({ assigned_doctor_id: auth.uid(), status:'under_review' }) |
| NestJS | POST /escalations/:id/claim |
| Permission | Doctor self-assigns. Field restriction: cannot edit parent_id, visit_id, or severity. |
6.4 Add doctor response
| Original | .from('escalation_responses').insert(...) |
| NestJS | POST /escalations/:id/responses |
| Permission | Free-text clinical review note. No prescription field — language guard enforces "review/advice" wording. |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| escalation_id | uuid | yes | |
| response_text | text | yes | No prescribe/Rx terms |
| recommended_next_step | enum | yes | observe|escalate_to_hospital|book_followup|refer_specialist |
Errors — 422 SAFETY_LANGUAGE_VIOLATION (response contained prescribing terminology).
6.5 Escalations queue (admin) / Close escalation
| Original (admin read) | .from('escalations').select('*').order('created_at',desc) → GET /admin/escalations |
| Original (close) | .from('escalations').update({ status:'closed', resolved_at: now() }) → PATCH /escalations/:id/close (intermediate actioned state before closed) |
| Permission | Admin reads all; doctor/admin can respond and close. |
🔁 Migration note. The "no prescribing terminology" guard (§6.4) and the on-call doctor routing (§6.1) are business rules to re-implement in the EscalationsService — a content classifier/keyword guard for the former, and an on-call lookup for the latter.
Module 7 — IncidentsModule
Risk/safety event log.
⚠️ No
incidentstable exists in the live schema. Risk/safety events are modelled throughescalations(typesfall_risk,urgent_health,medication_issue, etc.) and surfaced viaadmin_alerts. Recommendation: drop the standalone IncidentsModule and route incident reporting through EscalationsModule (Module 6). Only add a dedicatedincidentstable if a distinct, non-clinical incident workflow is genuinely required — in which case define it explicitly in the data model first.
7.1 Incident log — nurse (deprecated — use escalations)
| Original | .from('incidents').insert(...) (not in live schema) |
| NestJS | Prefer POST /escalations with escalation_type = fall_risk/urgent_health/… |
| Permission | Nurse writes; admin full; customer/doctor read scoped. |
If retained as a separate concept: incident_type (text), severity (escalation_severity enum), description (text).
Module 8 — NurseModule
The largest role-specific module: onboarding/application, KYC (Didit), document signing, availability, working hours, leave, live location + SOS, earnings, and bank details.
Onboarding gate: application approval → KYC (Didit v3) → sign 3 mandatory documents. RLS scopes rows by nurses.user_id = auth.uid() or visits.nurse_id = nurse.id.
8.1 submit-professional-application
| Original | POST /functions/v1/submit-professional-application (multipart) |
| NestJS | POST /nurse/applications (multipart) |
| Permission | Submit/resubmit application. Requires personal info + uploaded credential documents. Also used by doctors. |
| Side effects | Admin alert ("New Nurse Application"). Resubmit transitions requires_more_info → pending_verification. |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| full_name | text | yes | |
| professional_role | enum | yes | nurse_general|nurse_specialist|doctor_* |
| city | text | yes | |
| license_number | text | yes | |
| documents[] | files | yes | License, ID, certifications → staff-documents bucket |
Response — { application_id: uuid, verification_status: "pending_verification" }.
8.2 create-didit-session
| Original | POST /functions/v1/create-didit-session |
| NestJS | POST /nurse/kyc/session |
| Permission | Starts a Didit v3 KYC session; returns the hosted verification URL. |
| Dependencies | Didit (DIDIT_WORKFLOW_ID) |
Response — { session_url: string, session_id: string }.
8.3 didit-webhook (system)
| Original | POST /functions/v1/didit-webhook |
| NestJS | POST /webhooks/didit (@Public(), HMAC-verified) |
| Permission | Inbound from Didit; not called by nurses. HMAC-verified with DIDIT_WEBHOOK_SECRET. |
| Side effects | Update nurses.kyc_status; log kyc_events. A reconciler (reconcile-didit-kyc) runs every 10 min for stuck verifications. |
8.4 complete-nurse-document-signing
| Original | POST /functions/v1/complete-nurse-document-signing |
| NestJS | POST /nurse/documents/sign |
| Permission | Marks a mandatory onboarding document as signed. |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| document_type | enum | yes | code_of_conduct|contractor_agreement|privacy_policy |
| signature_data | string | yes | Typed name or base64 signature |
Response — { signed: boolean, onboarding_complete: boolean } (true after all 3 signed).
8.5 Shift & availability
Set availability — RPC set_nurse_availability({ p_is_available }) → PATCH /nurse/availability. Toggle online/offline; cannot change while on_visit. Response { nurse: object }.
Working hours — .from('nurse_working_hours').upsert(...) → PUT /nurse/working-hours. Weekly schedule; validation start_time < end_time.
| Field | Type | Req | Notes |
|---|---|---|---|
| day_of_week | int | yes | 0–6 |
| start_time | time | yes | |
| end_time | time | yes |
Leave request — .from('nurse_leave_requests').insert(...) → POST /nurse/leave. Full-day or partial; conflict trigger blocks overlap with existing visits.
| Field | Type | Req | Notes |
|---|---|---|---|
| leave_date | date | yes | |
| leave_type | enum(full_day|partial) | yes | |
| start_time | time | if partial | |
| end_time | time | if partial | |
| reason | text | no |
Errors — 409 LEAVE_CONFLICT (active visit(s) on this date — contact admin). Admins can also create leave on behalf of nurses (admin_created:true) via POST /admin/nurses/:id/leave.
8.6 Account lifecycle (nurse)
| Endpoint | NestJS | Behaviour |
|---|---|---|
| self-deactivate-account | POST /auth/account/deactivate | 15-day reactivation window starts; is_active=false; cancels future visits. Response { reactivation_deadline: timestamptz } (+15 days). |
| request-reactivation | POST /auth/account/reactivate | Within window; { status: "pending_admin_review" }. Error 422 REACTIVATION_WINDOW_EXPIRED. |
| self-delete-nurse-account | POST /auth/account/delete | Hard self-delete; body { confirmation: "DELETE" }. |
8.7 SOS & live location
Trigger SOS — .from('sos_alerts').insert(...) → POST /nurse/sos. Must include current lat/lng. Edge function then dispatches emails.
| Field | Type | Req | Notes |
|---|---|---|---|
| nurse_id | uuid | yes | Own id |
| lat | float8 | yes | |
| lng | float8 | yes | |
| note | text | no |
Side effects — send-sos-email fires to admin distribution; realtime room sos_alerts notifies admin web/mobile. Dependency: Resend.
Push GPS ping — .from('nurse_locations').upsert(...) → POST /nurse/location. Live tracking during active visit; >5 min stale → gps_location_stale admin alert.
| Field | Type | Req | Notes |
|---|---|---|---|
| nurse_id | uuid | yes | |
| lat | float8 | yes | |
| lng | float8 | yes | |
| battery_pct | int | no | |
| accuracy_m | int | no |
8.8 Earnings & payouts
My earnings — .from('nurse_earnings').select('*').eq('nurse_id', me) → GET /nurse/earnings. Client-side aggregation pending vs paid; created when an assessment is approved.
| Field | Type | Notes |
|---|---|---|
| visit_id | uuid | |
| amount_npr | numeric | |
| status | enum(pending|paid|reversed) | |
| paid_at | timestamptz |
Bank details (ConnectIPS) — .from('nurse_bank_details').upsert(...) → PUT /nurse/bank-details. Required for payouts; admin verifies before first payout. ⚠️ Live table is nurse_bank_details (the old ref called it user_bank_details). Withdrawals are tracked in withdrawal_requests.
| Field | Type | Req | Notes |
|---|---|---|---|
| account_name | text | yes | |
| bank_name | text | yes | |
| account_number | text | yes | |
| connectips_id | text | no |
Module 9 — DoctorModule
Clinical oversight: review escalations, run teleconsultations, read patient records. Doctors cannot prescribe — all guidance is review/advice only. Read access to parents/assessments is granted only for cases tied to an escalation/visit the doctor is assigned to or self-claims.
9.1 Case review
Escalation queue, claim, respond, and close are documented under Module 6 — EscalationsModule (§6.2–6.5). The doctor's view is GET /doctor/escalations.
9.2 Patient records (read-only)
| Action | Original | NestJS | Permission |
|---|---|---|---|
| Read parent | .from('parents').select('*').eq('id', pid) | GET /doctor/parents/:id | Allowed when the parent has an active escalation/visit involving this doctor |
| Read admission form | .from('admission_forms').select('*').eq('parent_id', pid) | GET /doctor/parents/:id/admission-form | Read-only |
| Read assessments / vitals | .from('assessments').select('*').eq('parent_id', pid) | GET /doctor/parents/:id/assessments | Read-only. May flag doctor_review_required=true on a follow-up via escalation response, but cannot directly edit nurse-submitted vitals |
| Read visit timeline | .from('visit_events').select('*').eq('visit_id', vid) | GET /doctor/visits/:id/events | Read-only chronological log |
9.3 Teleconsultations
| Original | .from('teleconsultations').insert(...) |
| NestJS | POST /doctor/teleconsultations |
| Permission | Doctor schedules/logs a video consult linked to an escalation. |
| Side effects | Notification to conversations:<…>; admin alert if severity escalates. |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| parent_id | uuid | yes | |
| escalation_id | uuid | no | |
| scheduled_at | timestamptz | yes | |
| mode | enum(video|audio|chat) | yes | |
| summary | text | no | Filled post-call |
9.4 self-delete-doctor-account
| Original | POST /functions/v1/self-delete-doctor-account |
| NestJS | POST /auth/account/delete |
| Permission | Doctor self-removal. Open cases must be re-assigned first (server check). |
| Request | { confirmation: "DELETE" } |
| Errors | 422 OPEN_CASES — re-assign all in-review escalations before deletion |
Module 10 — AdminModule
Operations. Two admin levels: admin and super_admin (checked via is_super_admin() → @Roles('super_admin')). Admins have broad access; sensitive lifecycle actions go through dedicated endpoints for auditability.
10.1 Staff management
approve-staff-application — POST /functions/v1/approve-staff-application → POST /admin/applications/:id/decision. Approves a pending application and provisions a nurse/doctor account.
| Field | Type | Req | Notes |
|---|---|---|---|
| application_id | uuid | yes | |
| decision | enum(approve|reject|requires_more_info) | yes | |
| notes | text | no | Required when rejecting / requesting more info |
Side effects — calls create-staff-user, links via link_user_to_nurse, sends welcome email (Resend). Response { user_id: uuid } (if approved).
create-staff-user — POST /functions/v1/create-staff-user → POST /admin/staff. Admin-only: provisions an auth user and nurse/doctor profile.
| Field | Type | Req | Notes |
|---|---|---|---|
| text | yes | ||
| name | text | yes | |
| role | enum(nurse|doctor) | yes | |
| professional_role | enum | no |
Response — { user_id: uuid, nurse_id: uuid } (nurse_id when role=nurse).
deactivate-staff — POST /functions/v1/deactivate-staff → POST /admin/staff/:id/deactivate. Soft-deactivate a nurse/doctor; starts 15-day reactivation window. Side effects: cancels future assignments; revokes sessions. Request { nurse_id: uuid, reason: text }.
purge-deleted-accounts — POST /functions/v1/purge-deleted-accounts (cron) → POST /admin/staff/purge (scheduled). Hard-deletes accounts past their 15-day window. Super admin only. Response { purged_count: int }.
10.2 Scheduling & visits
Assign-nurse, weekly roster, and admin-created leave are documented under Module 4 — VisitsModule (§4.5, §4.6) and Module 8 (§8.5).
10.3 Clinical oversight
Approve assessment — documented under Module 5 (§5.4).
Escalations queue — documented under Module 6 (§6.5).
Admin alerts — .from('admin_alerts').select('*').eq('status','open') → GET /admin/alerts. Deduped ops alerts (5-min window). Severity low/medium/high/critical.
| Field | Type | Notes |
|---|---|---|
| alert_type | text | |
| severity | text | |
| related_visit_id | uuid | |
| metadata | jsonb |
Acknowledge / resolve alert — .from('admin_alerts').update({ status:'resolved', resolved_by: auth.uid() }) → PATCH /admin/alerts/:id/resolve.
send-admin-alert-email (system) — POST /functions/v1/send-admin-alert-email → internal POST /internal/emails/admin-alert. Fired by trigger_admin_alert_email; not called by humans. Dependency: Resend.
check_monitoring_alerts (cron) — .rpc('check_monitoring_alerts') → @Cron('* * * * *') job. Sweeps for overdue reports (>90 min) and stale GPS (>5 min). Runs every minute.
10.4 Customer & subscription oversight
List customers — GET /admin/customers (see Module 2 §2.1).
Subscriptions overview — .from('subscriptions').select('*', profiles(name,email)) → GET /admin/subscriptions. Read all; tied to Stripe customer ids.
delete-family-member — POST /functions/v1/delete-family-member → POST /admin/customers/:id/delete. Admin-initiated removal (abuse, refund). Side effects: cancels Stripe subscription, anonymises, bans login. Request { user_id: uuid, reason: text }. Dependency: Stripe.
10.5 App config — see Module 17.
Module 11 — HospitalModule
Coordination with Kathmandu hospitals plus escort tasks.
11.1 Create hospital request — customer
| Original | .from('hospital_requests').insert(...) |
| NestJS | POST /hospital-requests |
| Permission | Request admin help coordinating with a hospital. Optional WhatsApp routing. |
| Side effects | trigger_hospital_request_alert → admin alert (severity high if urgent). |
Request
| Field | Type | Req | Notes |
|---|---|---|---|
| parent_id | uuid | yes | |
| hospital_name | text | yes | |
| urgency | enum(normal|urgent) | yes | |
| reason | text | yes |
Response — { id: uuid }. Nurses read scoped; admins manage escort_tasks.
Module 12 — PariAIModule
Streaming clinical-guidance assistant and voice transcription.
12.1 pariai-chat — customer
| Original | POST /functions/v1/pariai-chat (SSE stream) |
| NestJS | POST /pariai/chat (SSE / text/event-stream) |
| Permission | Streaming clinical-guidance assistant. Cannot diagnose or prescribe. |
| Guards / limits | Rate-limit 30 msgs / 10 min per user. Safety guard rewrites prescription-style asks. |
| Side effects | Persist message rows; auto-create an escalation if the safety classifier returns high-severity. |
| Dependencies | AI provider (was Lovable AI Gateway, google/gemini-2.5-flash default) |
Request — conversation_id (uuid, optional; created if omitted), message (string, yes), parent_id (uuid, optional; anchors conversation to a patient). Response — SSE token stream + conversation_id.
12.2 transcribe-voice — all roles
| Original | POST /functions/v1/transcribe-voice (multipart) |
| NestJS | POST /pariai/transcribe (multipart) |
| Permission | Whisper transcription for voice messages (max 120s). |
| Dependencies | AI provider (Whisper) |
Request — file (blob, yes; audio/m4a or audio/webm, ≤120s), language (string, optional, default auto). Response — { text: string, duration_seconds: number }.
🔁 Migration note. The original AI calls went through the Lovable AI Gateway. In the rebuild, call the provider SDK directly (Gemini/OpenAI for chat, Whisper/equivalent for transcription) behind an
AiService, and keep the rate-limit + safety-classifier logic inPariAIService.
Module 13 — BillingModule
Stripe subscriptions ($29.99/mo per parent), the on-demand $45 same-day visit, the billing portal, and the Stripe webhook.
13.1 create-checkout — customer
| Original | POST /functions/v1/create-checkout |
| NestJS | POST /billing/checkout |
| Permission | Starts Stripe Checkout for the $29.99/mo per-parent subscription. |
| Dependencies | Stripe |
Request — quantity (int, yes; number of parents), billing_type (enum monthly|yearly, default monthly). Response — { url: string }.
13.2 customer-portal — customer
POST /functions/v1/customer-portal → POST /billing/portal. Returns a Stripe billing-portal URL. Response { url: string }. Dependency: Stripe.
13.3 check-subscription
GET /functions/v1/check-subscription → GET /billing/subscription. Returns current subscription status (customer reads own; admin reads).
| Field | Type | Notes |
|---|---|---|
| subscribed | boolean | |
| tier | string | |
| parent_count | int | |
| current_period_end | timestamptz |
13.4 update-subscription-quantity
POST /functions/v1/update-subscription-quantity → PATCH /billing/subscription/quantity. Adjusts seat count when a parent is added/removed. Request { quantity: int }. Response { subscription: object }. Dependency: Stripe.
13.5 cancel-subscription / reactivate-subscription
POST /functions/v1/cancel-subscription · /reactivate-subscription → POST /billing/subscription/cancel · /reactivate. Cancel at period end, or undo a pending cancellation. Response { canceled_at_period_end: boolean }. Dependency: Stripe.
13.6 On-demand same-day visit
create-ondemand-checkout — POST /functions/v1/create-ondemand-checkout → POST /billing/ondemand/checkout. Stripe Checkout for a $45 on-demand same-day visit. Slots offered with a 1h buffer, today only.
| Field | Type | Req | Notes |
|---|---|---|---|
| parent_id | uuid | yes | |
| slot_start | timestamptz | yes | Must be ≥ now+1h, same day |
| notes | text | no |
Response — { url: string, session_id: string }. Dependency: Stripe.
verify-ondemand-payment — POST /functions/v1/verify-ondemand-payment → POST /billing/ondemand/verify. Called from the booking success page after Stripe redirect to finalise the visit. Side effects: insert payment row; create visit (pending_admin_assignment). Request { session_id: string }. Response { visit_id: uuid, paid: boolean }. Dependency: Stripe.
13.7 stripe-webhook (system)
POST /functions/v1/stripe-webhook → POST /webhooks/stripe (@Public(), verified with STRIPE_WEBHOOK_SECRET). Handled events are listed in Appendix B.
Module 14 — ReferralsModule
Referral accounts, referral tracking, and commission (5% via process_referral_commission).
14.1 My referral account — customer
| Original | .from('referral_accounts').select('*').eq('user_id', auth.uid()).single() |
| NestJS | GET /referrals/account |
| Permission | Read-only summary: code, link, balance, total earned. Auto-created on profile insert. |
| Field | Type | Notes |
|---|---|---|
| referral_code | text | e.g. PARI-ANITA482 |
| referral_link | text | |
| available_balance | numeric | NPR |
| total_earned | numeric |
Payouts are currently "Coming Soon" for customers. Commission rate 5% via
process_referral_commission; reversed on refund viareverse_referral_commission(Stripecharge.refunded).
Module 15 — NotificationsModule
Per-user in-app notifications and transactional email dispatch.
| Action | Original | NestJS | Notes |
|---|---|---|---|
| List notifications | .from('notifications')… | GET /notifications | Read own; realtime room notifications:<user_id>. All roles. |
| send-notification-email (system) | POST /functions/v1/send-notification-email | POST /internal/emails/notification | System-fired; Resend. |
| send-purchase-email (report) | trigger | POST /internal/emails/purchase | Fired on assessment approval with PDF link; Resend. |
| send-sos-email (system) | POST /functions/v1/send-sos-email | POST /internal/emails/sos | Fired on SOS insert; Resend. |
| send-admin-alert-email (system) | POST /functions/v1/send-admin-alert-email | POST /internal/emails/admin-alert | Fired by alert trigger; Resend. |
| auth-email-hook | Supabase auth event | POST /webhooks/auth-email | Routes auth emails through Resend templates. |
🔁 Migration note. All
send-*-emailendpoints are system-only (fired by triggers/events, never by humans). In the rebuild, model them as internal event handlers (or aMailServiceconsuming a queue), not public routes. Guard them so only the app itself can call them.
Module 16 — AlertsModule
Admin operations alerts, SOS alerts, and the monitoring sweep. Most of this surface is consumed by admins (read/resolve) and produced by the system (events + cron). Endpoints are documented under Module 10 §10.3 (admin alerts, monitoring) and Module 8 §8.7 (SOS). This module owns:
- Alert dedup (5-min window) and severity tagging.
- The
check_monitoring_alertscron: overdue reports (>90 min) and stale GPS (>5 min). - Emitting to realtime rooms
admin_alerts,sos_alerts,nurse_locations.
Module 17 — ConfigModule
App configuration, version gating, and the maps key.
17.1 app_config (CRUD) — admin
.from('app_config').upsert(...) → PUT /admin/config/:key. Force-update versions, feature flags, maintenance mode. Request { key: text, value: jsonb } (e.g. min_app_version_ios).
17.2 check-app-version
POST /functions/v1/check-app-version → POST /config/version-check. Mobile pings on launch; returns whether a force update is needed.
| Field | Type | Req | Notes |
|---|---|---|---|
| platform | enum(ios|android) | yes | |
| version | string | yes |
Response — { force_update: boolean, min_version: string }.
17.3 get-maps-key — all roles
POST/GET /functions/v1/get-maps-key → GET /config/maps-key. Returns the Google Maps key for the client. All roles.