Skip to main content

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

OriginalPOST /functions/v1/send-login-otp
NestJSPOST /auth/otp/send
PermissionPublic (@Public()). Anyone may request an OTP for an email they own.
Guards / limitsMax 5 OTPs per email per hour. 6-digit code, 10-min TTL. Apply @Throttle.
Side effectsInsert otp_verifications row; send email via Resend (FROM_EMAIL_NOREPLY).
DependenciesResend

Request

FieldTypeReqNotes
emailstringyesEmail of the account
purposeenum(login|2fa)noDefaults to login
{ "email": "anita@example.com" }

Response

FieldTypeNotes
successboolean
expires_attimestamptzOTP TTL (10 min)
{ "success": true, "expires_at": "2026-05-24T14:10:00Z" }

Errors

StatusCodeMessage
429RATE_LIMITEDToo many OTP requests for this email
404NOT_FOUNDNo account with this email

1.2 verify-login-otp

OriginalPOST /functions/v1/verify-login-otp
NestJSPOST /auth/otp/verify
PermissionPublic. Exchanges OTP for an authenticated session.
Side effectsMints access + refresh tokens (replaces Supabase session).

Request

FieldTypeReqNotes
emailstringyes
codestringyes6-digit
purposeenumnoDefault login
{ "email": "anita@example.com", "code": "482910" }

Response

FieldTypeNotes
sessionobject{ access_token, refresh_token }
userobjectUser payload

Errors

StatusCodeMessage
400INVALID_OTPCode does not match
400OTP_EXPIREDCode older than 10 min

1.3 check-email-exists

OriginalPOST /functions/v1/check-email-exists
NestJSPOST /auth/email/check
PermissionPublic. Used by the signup screen.
Response{ exists: boolean, active: boolean }

1.4 self-delete-customer-account

OriginalPOST /functions/v1/self-delete-customer-account
NestJSPOST /auth/account/delete (role-routed)
PermissionAuthenticated customer only. Soft-deactivates and bans the account for 876600h.
Side effectsCancel Stripe subscription, anonymise profile, revoke sessions, remove parents.
DependenciesStripe

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.

EndpointRolesNestJSBehaviour
self-deactivate-accountcustomer, nurse, doctorPOST /auth/account/deactivateSets is_active=false; bans login 876600h; cancels future visits. Starts 15-day reactivation window (nurse).
request-reactivationnursePOST /auth/account/reactivateWithin 15-day window; fail-fast if expired → 422 REACTIVATION_WINDOW_EXPIRED.
self-delete-nurse-accountnursePOST /auth/account/deleteHard self-delete (after window or by request). Body { confirmation: "DELETE" }.
self-delete-doctor-accountdoctorPOST /auth/account/deleteOpen 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')
NestJSGET /admin/customers
PermissionAdmin.

2.2 invite-admin / accept-admin-invite

OriginalPOST /functions/v1/invite-admin · /accept-admin-invite
NestJSPOST /admin/invites · POST /admin/invites/accept
PermissionSuper admin creates a token-based invite; recipient accepts via a signed one-time link. accept is @Public() (validated by token).

invite request

FieldTypeReqNotes
emailtextyes
admin_levelenum(admin|super_admin)no

Response{ invite_url: string } (one-time signed URL).

2.3 suspend-admin-user

OriginalPOST /functions/v1/suspend-admin-user
NestJSPATCH /admin/users/:id/suspend
PermissionSuper 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)
NestJSGET /parents
PermissionRead own rows only (RLS parents.user_id = auth.uid() → service scoping).

Response fields

FieldTypeNotes
iduuid
nametext
date_of_birthdate
citytextNepal city
landmarktextPreferred over street address
lat / lngfloat8Map pin
relationshiptextmother/father/…
[{ "id":"...", "name":"Mr. Sharma", "city":"Kathmandu", "landmark":"Near Bir Hospital" }]

3.2 Create parent

Original.from('parents').insert(...) (POST)
NestJSPOST /parents
PermissionInsert with user_id = auth.uid().
Side effectstrigger_parent_created_alertadmin alert (emit parent.created event).

Request

FieldTypeReqNotes
nametextyes
date_of_birthdateyes
relationshiptextyes
citytextyes
landmarktextnoStrongly recommended
latfloat8no
lngfloat8no

Response{ id: uuid }.

3.3 Admission form

Original.from('admission_forms').upsert(...) (POST/PATCH)
NestJSPUT /parents/:parentId/admission-form
PermissionCreate/update the long-form intake. Bi-directional sync with parent fields.
Side effectstrigger_admission_form_alert → admin alert (emit admission_form.saved).

Request

FieldTypeReqNotes
parent_iduuidyes
full_nametextyes
conditionsjsonbnoArray of chronic conditions
medicationsjsonbno
allergiesjsonbno

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_assignmentassigned_to_nursein_progressreport_submittedapproved; plus completed (visit ended without a report) and cancelled. Admin rejection sends report_submittedin_progress. The timeline (visit_events) is appended on each transition.

⚠️ Enum correction. The live visit_type enum is routine / follow_up only — not regular / first_assessment / on_demand. "First visit" is not a visit_type; it is detected by the absence of a first_visit_assessments row for the parent (see Clinical Reporting doc). On-demand is a billing path (Module 13), not an enum value. There is also no requested_time_window enum in the live schema — visits carry a scheduled_date; confirm whether a time-window column exists before building the wizard.

4.1 Create visit (booking wizard) — customer

Original.from('visits').insert(...) (POST)
NestJSPOST /visits
PermissionCustomer-initiated; status starts pending_admin_assignment.
Guards / limitsFirst-ever visit triggers the First Visit Assessment flow (no prior first_visit_assessments row).
Side effectsInsert visit_events (visit_requested); create admin alert (emit visit.requested).

Request

FieldTypeReqNotes
parent_iduuidyes
scheduled_datedateyes
visit_typeenum(routine|follow_up)yesLive visit_type enum
notestextno
client_user_iduuidautoDefaults 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

OriginalPOST /functions/v1/reschedule-visit
NestJSPOST /visits/:id/reschedule
PermissionCustomer 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

StatusCodeMessage
422RESCHEDULE_CUTOFFWithin 24h of visit
422MONTHLY_LIMITRecurring reschedule limit reached

4.3 List my visits

Original.from('visits').select('*', parents(name), nurses(name))
NestJSGET /visits
PermissionScoped: customer by client_user_id, nurse by nurse_id, admin all, doctor by linked escalation/visit.

Responseid, status (enum), nurse_id (null until assigned), start_time, end_time.

4.4 Visit timeline

Original.from('visit_events').select('*').eq('visit_id', id)
NestJSGET /visits/:id/events
PermissionRead-only log auto-built by trigger_visit_event_on_status_change (rebuild: append in the status-change handler).

Response

FieldTypeNotes
event_typeenumvisit_requested, nurse_assigned, visit_started, report_submitted, report_approved, visit_completed
actor_typeenum(customer|nurse|admin)
created_attimestamptz

4.5 Assign nurse to visit — admin

Original.from('visits').update({ nurse_id, status:'assigned_to_nurse' })
NestJSPATCH /admin/visits/:id/assign
PermissionAdmin sets nurse_id.
Side effectstrigger_nurse_assignment_emailResend; insert visit_events.nurse_assigned.
DependenciesResend

4.6 Scheduling roster (weekly view) — admin

Original.from('visits').select('*', nurses(name), parents(name)).gte('requested_date', …)
NestJSGET /admin/schedule?from=…&to=…
PermissionAdmin. Color-coded calendar source.

4.7 Start visit — nurse

Original.from('visits').update({ status:'in_progress', start_time: now() })
NestJSPATCH /visits/:id/start
PermissionNurse may only update their own assigned visits.
Side effectsInsert 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), and clinical_modules (optional) — with drafts, a 24-hour edit window, assessment_versions snapshots, 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) and admin_viewed, not a status='approved' field. Vitals are bp_systolic_1/bp_diastolic_1 (+ optional second reading), respiratory_rate, pulse_rate, temperature, spo2, blood_sugar_value/blood_sugar_type. general_condition uses the 8-value live enum. nurse_id is the nurses row 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, ... })
NestJSPUT /visits/:visitId/assessment/draft
PermissionNurse writes vitals during the visit. Field-level restriction: cannot set approved_by or approved_at (enforce via DTO whitelist).

Request

FieldTypeReqNotes
visit_iduuidyes
parent_iduuidyes
bp_systolicintyesColor-coded urgency thresholds apply
bp_diastolicintyes
pulseintyes
temperaturenumericyes°C
spo2intyes
blood_sugarnumericno
notestextnoMandatory template sections must be filled before submit

5.2 Upload wound / documentation photos — nurse

OriginalPOST storage → bucket wound-photos
NestJSPOST /assessments/:id/photos (pre-signed upload via StorageService)
PermissionUp 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 })
NestJSPOST /assessments/:id/submit
PermissionLocks vitals; triggers admin alert + report email queue.
Side effectstrigger_assessment_alert → admin alert; admin approval later triggers payout calculation.

Requestdoctor_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() })
NestJSPOST /admin/assessments/:id/approve
PermissionAdmin (or doctor with override) approves a submitted report → triggers payment & customer email.
Side effectsInsert nurse_earnings (pending → credited on payout); fire send-purchase-email with PDF link.
DependenciesResend

5.5 View approved clinical report — customer

Original.from('assessments').select('*').eq('parent_id', pid).eq('is_draft', false)
NestJSGET /parents/:parentId/reports
PermissionCustomer reads only assessments where visit.client_user_id = auth.uid() and status approved. PDF in reports bucket.

Response

FieldTypeNotes
iduuid
bp_systolic / bp_diastolicint
pulse / temperature / spo2 / blood_sugarnumeric
summarytextPlain-language summary
pdf_pathtextreports/<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_type is one of doctor_review, urgent_health, medication_issue, fall_risk, hospital_support, family_followup, admin_support — not clinical/safety/logistical. escalation_status is openunder_reviewactionedclosed — not open/in_review/resolved. escalation_severity is low/medium/high/critical.

Escalation status: openunder_reviewactionedclosed.

6.1 Create escalation — nurse

Original.from('escalations').insert(...)
NestJSPOST /escalations
PermissionNurses raise escalations during or after a visit.
Side effectstrigger_escalation_alert → admin alert; routed to on-call doctor if clinical.

Request

FieldTypeReqNotes
parent_iduuidyes
visit_iduuidno
escalation_typeescalation_type enumyesdoctor_review|urgent_health|medication_issue|fall_risk|hospital_support|family_followup|admin_support
severityescalation_severity enumyeslow|medium|high|critical
reasontextyes

6.2 Doctor escalation queue

Original.from('escalations').select('*', parents(*), visits(*), assessments(*)).or('assigned_doctor_id.eq.{me},status.eq.unassigned')
NestJSGET /doctor/escalations
PermissionDoctor sees unassigned + their own assigned escalations.

Responseescalation_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' })
NestJSPOST /escalations/:id/claim
PermissionDoctor self-assigns. Field restriction: cannot edit parent_id, visit_id, or severity.

6.4 Add doctor response

Original.from('escalation_responses').insert(...)
NestJSPOST /escalations/:id/responses
PermissionFree-text clinical review note. No prescription field — language guard enforces "review/advice" wording.

Request

FieldTypeReqNotes
escalation_iduuidyes
response_texttextyesNo prescribe/Rx terms
recommended_next_stepenumyesobserve|escalate_to_hospital|book_followup|refer_specialist

Errors422 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)
PermissionAdmin 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 incidents table exists in the live schema. Risk/safety events are modelled through escalations (types fall_risk, urgent_health, medication_issue, etc.) and surfaced via admin_alerts. Recommendation: drop the standalone IncidentsModule and route incident reporting through EscalationsModule (Module 6). Only add a dedicated incidents table 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)
NestJSPrefer POST /escalations with escalation_type = fall_risk/urgent_health/…
PermissionNurse 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

OriginalPOST /functions/v1/submit-professional-application (multipart)
NestJSPOST /nurse/applications (multipart)
PermissionSubmit/resubmit application. Requires personal info + uploaded credential documents. Also used by doctors.
Side effectsAdmin alert ("New Nurse Application"). Resubmit transitions requires_more_infopending_verification.

Request

FieldTypeReqNotes
full_nametextyes
professional_roleenumyesnurse_general|nurse_specialist|doctor_*
citytextyes
license_numbertextyes
documents[]filesyesLicense, ID, certifications → staff-documents bucket

Response{ application_id: uuid, verification_status: "pending_verification" }.

8.2 create-didit-session

OriginalPOST /functions/v1/create-didit-session
NestJSPOST /nurse/kyc/session
PermissionStarts a Didit v3 KYC session; returns the hosted verification URL.
DependenciesDidit (DIDIT_WORKFLOW_ID)

Response{ session_url: string, session_id: string }.

8.3 didit-webhook (system)

OriginalPOST /functions/v1/didit-webhook
NestJSPOST /webhooks/didit (@Public(), HMAC-verified)
PermissionInbound from Didit; not called by nurses. HMAC-verified with DIDIT_WEBHOOK_SECRET.
Side effectsUpdate nurses.kyc_status; log kyc_events. A reconciler (reconcile-didit-kyc) runs every 10 min for stuck verifications.

8.4 complete-nurse-document-signing

OriginalPOST /functions/v1/complete-nurse-document-signing
NestJSPOST /nurse/documents/sign
PermissionMarks a mandatory onboarding document as signed.

Request

FieldTypeReqNotes
document_typeenumyescode_of_conduct|contractor_agreement|privacy_policy
signature_datastringyesTyped name or base64 signature

Response{ signed: boolean, onboarding_complete: boolean } (true after all 3 signed).

8.5 Shift & availability

Set availabilityRPC 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.

FieldTypeReqNotes
day_of_weekintyes0–6
start_timetimeyes
end_timetimeyes

Leave request.from('nurse_leave_requests').insert(...)POST /nurse/leave. Full-day or partial; conflict trigger blocks overlap with existing visits.

FieldTypeReqNotes
leave_datedateyes
leave_typeenum(full_day|partial)yes
start_timetimeif partial
end_timetimeif partial
reasontextno

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)

EndpointNestJSBehaviour
self-deactivate-accountPOST /auth/account/deactivate15-day reactivation window starts; is_active=false; cancels future visits. Response { reactivation_deadline: timestamptz } (+15 days).
request-reactivationPOST /auth/account/reactivateWithin window; { status: "pending_admin_review" }. Error 422 REACTIVATION_WINDOW_EXPIRED.
self-delete-nurse-accountPOST /auth/account/deleteHard 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.

FieldTypeReqNotes
nurse_iduuidyesOwn id
latfloat8yes
lngfloat8yes
notetextno

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.

FieldTypeReqNotes
nurse_iduuidyes
latfloat8yes
lngfloat8yes
battery_pctintno
accuracy_mintno

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.

FieldTypeNotes
visit_iduuid
amount_nprnumeric
statusenum(pending|paid|reversed)
paid_attimestamptz

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.

FieldTypeReqNotes
account_nametextyes
bank_nametextyes
account_numbertextyes
connectips_idtextno

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)

ActionOriginalNestJSPermission
Read parent.from('parents').select('*').eq('id', pid)GET /doctor/parents/:idAllowed 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-formRead-only
Read assessments / vitals.from('assessments').select('*').eq('parent_id', pid)GET /doctor/parents/:id/assessmentsRead-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/eventsRead-only chronological log

9.3 Teleconsultations

Original.from('teleconsultations').insert(...)
NestJSPOST /doctor/teleconsultations
PermissionDoctor schedules/logs a video consult linked to an escalation.
Side effectsNotification to conversations:<…>; admin alert if severity escalates.

Request

FieldTypeReqNotes
parent_iduuidyes
escalation_iduuidno
scheduled_attimestamptzyes
modeenum(video|audio|chat)yes
summarytextnoFilled post-call

9.4 self-delete-doctor-account

OriginalPOST /functions/v1/self-delete-doctor-account
NestJSPOST /auth/account/delete
PermissionDoctor self-removal. Open cases must be re-assigned first (server check).
Request{ confirmation: "DELETE" }
Errors422 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-applicationPOST /functions/v1/approve-staff-applicationPOST /admin/applications/:id/decision. Approves a pending application and provisions a nurse/doctor account.

FieldTypeReqNotes
application_iduuidyes
decisionenum(approve|reject|requires_more_info)yes
notestextnoRequired 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-userPOST /functions/v1/create-staff-userPOST /admin/staff. Admin-only: provisions an auth user and nurse/doctor profile.

FieldTypeReqNotes
emailtextyes
nametextyes
roleenum(nurse|doctor)yes
professional_roleenumno

Response — { user_id: uuid, nurse_id: uuid } (nurse_id when role=nurse).

deactivate-staffPOST /functions/v1/deactivate-staffPOST /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-accountsPOST /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.

FieldTypeNotes
alert_typetext
severitytext
related_visit_iduuid
metadatajsonb

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 customersGET /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-memberPOST /functions/v1/delete-family-memberPOST /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(...)
NestJSPOST /hospital-requests
PermissionRequest admin help coordinating with a hospital. Optional WhatsApp routing.
Side effectstrigger_hospital_request_alert → admin alert (severity high if urgent).

Request

FieldTypeReqNotes
parent_iduuidyes
hospital_nametextyes
urgencyenum(normal|urgent)yes
reasontextyes

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

OriginalPOST /functions/v1/pariai-chat (SSE stream)
NestJSPOST /pariai/chat (SSE / text/event-stream)
PermissionStreaming clinical-guidance assistant. Cannot diagnose or prescribe.
Guards / limitsRate-limit 30 msgs / 10 min per user. Safety guard rewrites prescription-style asks.
Side effectsPersist message rows; auto-create an escalation if the safety classifier returns high-severity.
DependenciesAI provider (was Lovable AI Gateway, google/gemini-2.5-flash default)

Requestconversation_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

OriginalPOST /functions/v1/transcribe-voice (multipart)
NestJSPOST /pariai/transcribe (multipart)
PermissionWhisper transcription for voice messages (max 120s).
DependenciesAI provider (Whisper)

Requestfile (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 in PariAIService.


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

OriginalPOST /functions/v1/create-checkout
NestJSPOST /billing/checkout
PermissionStarts Stripe Checkout for the $29.99/mo per-parent subscription.
DependenciesStripe

Requestquantity (int, yes; number of parents), billing_type (enum monthly|yearly, default monthly). Response{ url: string }.

13.2 customer-portal — customer

POST /functions/v1/customer-portalPOST /billing/portal. Returns a Stripe billing-portal URL. Response { url: string }. Dependency: Stripe.

13.3 check-subscription

GET /functions/v1/check-subscriptionGET /billing/subscription. Returns current subscription status (customer reads own; admin reads).

FieldTypeNotes
subscribedboolean
tierstring
parent_countint
current_period_endtimestamptz

13.4 update-subscription-quantity

POST /functions/v1/update-subscription-quantityPATCH /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-subscriptionPOST /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-checkoutPOST /functions/v1/create-ondemand-checkoutPOST /billing/ondemand/checkout. Stripe Checkout for a $45 on-demand same-day visit. Slots offered with a 1h buffer, today only.

FieldTypeReqNotes
parent_iduuidyes
slot_starttimestamptzyesMust be ≥ now+1h, same day
notestextno

Response — { url: string, session_id: string }. Dependency: Stripe.

verify-ondemand-paymentPOST /functions/v1/verify-ondemand-paymentPOST /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-webhookPOST /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()
NestJSGET /referrals/account
PermissionRead-only summary: code, link, balance, total earned. Auto-created on profile insert.
FieldTypeNotes
referral_codetexte.g. PARI-ANITA482
referral_linktext
available_balancenumericNPR
total_earnednumeric

Payouts are currently "Coming Soon" for customers. Commission rate 5% via process_referral_commission; reversed on refund via reverse_referral_commission (Stripe charge.refunded).


Module 15 — NotificationsModule

Per-user in-app notifications and transactional email dispatch.

ActionOriginalNestJSNotes
List notifications.from('notifications')…GET /notificationsRead own; realtime room notifications:<user_id>. All roles.
send-notification-email (system)POST /functions/v1/send-notification-emailPOST /internal/emails/notificationSystem-fired; Resend.
send-purchase-email (report)triggerPOST /internal/emails/purchaseFired on assessment approval with PDF link; Resend.
send-sos-email (system)POST /functions/v1/send-sos-emailPOST /internal/emails/sosFired on SOS insert; Resend.
send-admin-alert-email (system)POST /functions/v1/send-admin-alert-emailPOST /internal/emails/admin-alertFired by alert trigger; Resend.
auth-email-hookSupabase auth eventPOST /webhooks/auth-emailRoutes auth emails through Resend templates.

🔁 Migration note. All send-*-email endpoints are system-only (fired by triggers/events, never by humans). In the rebuild, model them as internal event handlers (or a MailService consuming 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_alerts cron: 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-versionPOST /config/version-check. Mobile pings on launch; returns whether a force update is needed.

FieldTypeReqNotes
platformenum(ios|android)yes
versionstringyes

Response — { force_update: boolean, min_version: string }.

17.3 get-maps-key — all roles

POST/GET /functions/v1/get-maps-keyGET /config/maps-key. Returns the Google Maps key for the client. All roles.