Backend API for MeduzaAI — a multi-tenant hospital platform that connects hospitals, departments, doctors, and patients through QR check-in, AI-assisted intake interviews, queue notifications, and real-time staff tools.
Swagger title in the app: meduza.ai REST-API. Production CORS allows meduzaai.uz.
MeduzaAI helps clinics digitize the front desk:
- A doctor shares a public check-in link / QR code.
- The patient opens the link, starts a session, and talks to an AI intake assistant (OpenAI).
- Structured facts, risk level, and a summary are stored on a
PatientResponsesession. - The patient joins a queue; doctors see updates over WebSocket; Telegram can notify the patient about queue progress.
- Hospital admins manage org data, analytics, news, and department chat.
Hospitals
└── Departments
└── Doctors
└── Patients / PatientResponse (intake sessions)
├── IntakeMessages
├── IntakeFacts
└── Answers
Staff accounts live in Users with roles super_admin, admin, and doctor. Admins may be hospital-scoped or platform-wide (Admins.is_super).
| Layer | Technology |
|---|---|
| Runtime | Node.js, TypeScript |
| Framework | NestJS 11 |
| Database | PostgreSQL via Prisma 7 (@prisma/adapter-pg) |
| Auth | JWT + httpOnly cookies (access_token, refresh_token), bcrypt |
| AI intake | OpenAI (openai SDK, default model gpt-4o-mini) |
| Messaging | Telegram bot (Grammy) |
| Realtime | Socket.IO (@nestjs/websockets) |
| Media | Cloudinary (doctor avatars, news images) |
| Docs | Swagger UI at /api/docs |
| Validation | class-validator / class-transformer global ValidationPipe |
- Multi-tenant hospitals — hospitals, departments, doctors, patients, scoped by role
- Role-based access —
SUPER_ADMIN,HOSPITAL_ADMIN,DOCTOR - Public QR check-in — HMAC
checkin_token, session start, optional drafts - AI intake chat — layered prompts (global + department + doctor), fact extraction, risk levels, doctor dashboard summary
- Queue + Telegram — deep-link subscribe, queue progress / turn notifications
- Realtime — doctor patient-list updates; department chat over Socket.IO
- Questions & answers — template vs doctor-scoped intake questions
- Notifications, categories, priorities
- Audit logs — login and sensitive actions
- Analytics — dashboard, peak hours, doctor-scoped stats (
APP_TIMEZONE, defaultAsia/Tashkent) - News — specialty/department articles with image upload
- Department chat — REST + WebSocket rooms for staff
meduzaai/
├── prisma/
│ ├── schema.prisma # Data model
│ └── clear-checkin-sessions.ts # Utility: wipe check-in sessions
├── providers/
│ ├── cloudinary/ # Image upload service
│ └── prisma/ # PrismaModule / PrismaService
├── scripts/
│ └── add-super-admin-once.mjs # Bootstrap a super admin user
└── src/
├── main.ts # Bootstrap, CORS, Swagger, global prefix
├── app.module.ts
├── public-checkin.controller.ts
├── common/ # Auth guards, check-in helpers, realtime
└── modules/ # Feature modules (auth, intake-ai, …)
- Node.js 20+ (recommended)
- PostgreSQL database
- OpenAI API key (for AI intake)
- Optional: Telegram bot token, Cloudinary credentials
npm installpostinstall runs prisma generate.
Create a .env in the project root (not committed). There is no .env.example in the repo; use this template:
# Server
PORT=4000
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/meduzaai
# JWT / cookies
ACCESS_TOKEN_KEY=change-me-access
ACCESS_TOKEN_KEY_TIME=15m
REFRESH_TOKEN_KEY=change-me-refresh
REFRESH_TOKEN_TIME=7d
REFRESH_TIME_MS=604800000
# Public check-in (frontend base for QR / redirect)
CHECKIN_PUBLIC_BASE_URL=http://localhost:3000
# FRONTEND_BASE_URL=http://localhost:3000 # fallback if CHECKIN_PUBLIC_BASE_URL unset
CHECKIN_TOKEN_SECRET=change-me-checkin # falls back to ACCESS_TOKEN_KEY
# OpenAI
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-mini
# Telegram (optional)
TELEGRAM_BOT_TOKEN=
TELEGRAM_BOT_USERNAME=meduzaal_bot
# TELEGRAM_POLLING=0 # set 0/false for webhook-only (production)
# Cloudinary (optional; for avatars & news images)
CLOUDINARY_URL=
# or:
# CLOUDINARY_CLOUD_NAME=
# CLOUDINARY_API_KEY=
# CLOUDINARY_API_SECRET=
# Analytics timezone
APP_TIMEZONE=Asia/TashkentApply the schema to Postgres. Migrations are configured in prisma.config.ts (prisma/migrations), but migration history may not be present in the repo — sync the schema as appropriate for your environment, for example:
npx prisma db push
# or, when migrations exist:
npx prisma migrate deployNote:
npm run db:deploycurrently has a typo (primsainstead ofprisma). Prefernpx prisma migrate deploy.
node scripts/add-super-admin-once.mjs [phone] [password]Defaults (if args omitted): phone +998979105060, password amin123. Requires DATABASE_URL.
# development (watch)
npm run start:dev
# or
npm run dev
# production build + start
npm run build
npm run start:prodServer listens on 0.0.0.0 at PORT (default 4000).
- API base:
http://localhost:4000/api - Swagger:
http://localhost:4000/api/docs
| Setting | Value |
|---|---|
| Global prefix | /api |
Exception (no /api) |
GET /h/:hospitalId/:departmentSlug/d/:doctorId → 302 to frontend check-in |
| Auth | Bearer access token and/or httpOnly cookies |
| CORS | https://meduzaai.uz, http://meduzaai.uz (+ requests with no Origin); credentials enabled |
| Proxy | trust proxy = 1 (nginx X-Forwarded-For for audit/IP) |
| Body limit | 10mb |
| Prefix | Purpose |
|---|---|
/api/auth |
Login, refresh, me, logout, change password |
/api/admins |
Admin users |
/api/hospitals |
Hospitals |
/api/departments |
Departments + AI system prompts |
/api/doctors |
Doctors + /me/* doctor self-service |
/api/patients (/api/potients) |
Patients |
/api/questions |
Intake question templates / doctor questions |
/api/answers |
Answers |
/api/checkin |
Public patient check-in |
/api/ai-intake |
AI intake conversation |
/api/patient-response |
Staff-facing intake sessions |
/api/analytics |
Dashboard analytics |
/api/hospital-admin/analytics |
Peak-hours analytics |
/api/notifications |
Notifications |
/api/audit-logs |
Audit trail |
/api/news |
Medical news articles |
/api/department-chat |
Department staff chat |
/api/telegram |
Telegram webhook |
/api/categories, /api/priorities |
Notification metadata |
/api/users |
User accounts |
Interactive docs: /api/docs.
| API role | Who | Typical access |
|---|---|---|
SUPER_ADMIN |
Platform admin (is_super) |
All hospitals, departments, news write, global chat rooms, unscoped analytics |
HOSPITAL_ADMIN |
Hospital-scoped admin | Own hospital data, doctors, analytics, audit, chat rooms |
DOCTOR |
Clinician | Own patients, intake dashboard, AI prompt, avatar, doctor questions, dept chat me/* |
Guards: JwtAuthGuard + RolesGuard with @Roles(...). Public surfaces include login, check-in, AI intake (tokenized), and the Telegram webhook.
sequenceDiagram
participant P as Patient
participant FE as Frontend
participant API as MeduzaAI API
participant AI as OpenAI
participant D as Doctor (Socket.IO)
participant TG as Telegram
P->>FE: Open /h/{hospital}/{dept}/d/{doctor}
FE->>API: GET /api/checkin/doctor/:doctorId
API-->>FE: profile + checkin_token
FE->>API: POST /api/checkin/session/start
FE->>API: POST /api/ai-intake/start
loop Conversation
FE->>API: POST /api/ai-intake/message
API->>AI: chat + structured JSON
API-->>FE: assistant message + facts
end
FE->>API: POST /api/checkin/submissions
API->>D: doctor:patients_changed
API->>TG: queue notify (if linked)
- Link / QR — Built as
{CHECKIN_PUBLIC_BASE_URL}/h/{hospitalId}/{departmentSlug}/d/{doctorId}. Hitting the same path on the API redirects to the frontend. - Doctor profile —
GET /api/checkin/doctor/:doctorIdreturns a short-lived HMACcheckin_token(~30 minutes). - Session —
POST /api/checkin/session/startupsertsPatients+PatientResponse. - AI chat —
POST /api/ai-intake/startand/messagedrive the interview; prompts combine global rules, departmentai_system_prompt, and optional doctorai_intake_prompt. - Submit / queue —
POST /api/checkin/submissionsfinalizes answers, assigns queue workflow, emits realtime updates, and can notify Telegram. - Doctor view —
GET /api/ai-intake/:sessionId/dashboardand Socket.IO namespace/doctor-updates.
Session statuses (IntakeSessionStatus): collecting, enough_data, urgent, completed, cancelled. Risk levels: low | medium | high | urgent.
- Subscribe with handshake
doctorId/doctor_idor eventdoctor:subscribe. - Events:
doctor:patients_changed(patient_created,patient_status_updated,patient_updated,queue_updated).
- Auth via JWT (
auth.tokenorquery.token, secretACCESS_TOKEN_KEY). - Join room with
join{ departmentId }→ roomdepartment:{id}. - Events:
department_chat:message.
- Bot token:
TELEGRAM_BOT_TOKEN. - Username default:
meduzaal_bot. - Development: polling unless
TELEGRAM_POLLINGis0/false. - Production:
POST /api/telegram/webhook. - Patients link via
POST /api/checkin/telegram-link→t.me/{bot}?start={token}, then share contact to subscribe to queue updates.
| Script | Description |
|---|---|
npm run start:dev / dev |
Nest watch mode |
npm run build |
prisma generate + Nest build |
npm start / start:prod |
Run compiled dist/src/main.js |
npm run lint |
ESLint |
npm run format |
Prettier |
npm test / test:e2e / test:cov |
Jest |
npm run db:clear-checkin-sessions |
Delete answers + patient responses |
node scripts/add-super-admin-once.mjs |
Create/upgrade super admin |
Scripts db:wipe-intake-full and seed:cardiology-intake are referenced in package.json but their Prisma script files are not currently in the repository.
- Login:
POST /api/auth/loginwith phone + password. - Issues JWT access + refresh; sets httpOnly cookies
access_tokenandrefresh_token(sameSite: 'lax'). - Also returns
accessTokenin the JSON body for Bearer clients. GET /api/auth/me,POST /api/auth/refresh,POST /api/auth/logout,POST /api/auth/change-password.
- Bind address is already
0.0.0.0— suitable behind nginx. - Enable
trust proxyis already set for correct client IPs. - Point CORS / frontend base URLs at your real domains.
- Prefer Telegram webhook mode in production (
TELEGRAM_POLLING=0) and configure the webhook to/api/telegram/webhook. - Keep
DATABASE_URL, JWT secrets,CHECKIN_TOKEN_SECRET, andOPENAI_API_KEYout of version control.
Private / UNLICENSED (see package.json).