Files
temetro/backend
Khalid Abdi 3a7378e00d backend: AI chat agent with Veil PHI safeguard (providers, /chat, import)
Real LLM chat replacing the mock, backend-centric per the plan:

- Multi-provider API-key mode (OpenAI / Anthropic / Gemini via the AI SDK) plus
  local Ollama (OpenAI-compatible endpoint). Provider is derived from the
  picked model id; the matching stored key is used. New user_ai_settings table
  holds per-user config with provider API keys encrypted at rest (AES-256-GCM,
  src/lib/crypto.ts, keyed by AI_CREDENTIALS_KEY).
- POST /api/ai/config (get/put, secrets never returned), POST /api/ai/test
  (Ollama ping / key presence), POST /api/ai/import (approved migration commit,
  re-validated server-side, reuses the audited patient service).
- POST /api/chat: streamText agent with tools (getPatient, getPatientLabs,
  searchPatients, previewImport). Real record data streams to the clinician as
  custom data parts (cards) while the model sees only Veil-redacted results.
- Veil (src/services/ai/veil.ts): de-identifies patient identifiers to tokens
  before external calls, resolves tokens on tool args, and rehydrates the final
  answer. Bypassed for local Ollama. External mode runs non-streamed so the
  rehydrated text is correct. Every call is audited (provider + Veil level).
- Shared role-scoping helpers extracted to src/lib/role-scope.ts (reused by the
  patient routes and chat tools so visibility rules match).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 18:35:16 +03:00
..

temetro backend

TypeScript + Express + Postgres API for temetro. Authentication is powered by Better Auth (email/password with verification, password reset, and multi-tenant organizations with role-based access control). Patient records are scoped to an organization (a clinic) and exposed over a small REST API that mirrors the frontend's Patient shape exactly.

Quick start (Docker)

cp .env.example .env
# set a strong secret:
#   openssl rand -base64 32   ->  paste into BETTER_AUTH_SECRET
docker compose up            # db + backend + frontend

Run only the API + database: docker compose up db backend. Browse the DB with Adminer: docker compose --profile tools up adminerhttp://localhost:8080.

Migrations are applied automatically on container start (node dist/migrate.js).

Local development (without Docker)

npm install
cp .env.example .env          # point DATABASE_URL at a local Postgres (localhost:5432)
npm run db:migrate            # apply migrations (drizzle-kit)
npm run dev                   # tsx watch on http://localhost:4000

Auth & schema workflow

The Better Auth tables are generated into src/db/schema/auth.ts from src/auth.ts. Re-run after changing auth config/plugins, then regenerate the SQL migration:

npm run auth:generate   # better-auth CLI -> src/db/schema/auth.ts
npm run db:generate     # drizzle-kit -> ./drizzle/*.sql
npm run db:migrate      # apply

API

All patient routes require a signed-in user (Better Auth session cookie) and an active organization; access is gated by the caller's clinic role.

Method Path Permission Notes
GET /api/patients patient:read list patients in the active clinic
GET /api/patients/:fileNumber patient:read one patient (404 if absent)
POST /api/patients patient:write create (409 if file number exists)
PUT /api/patients/:fileNumber patient:write replace the full record
DELETE /api/patients/:fileNumber patient:delete remove a patient

Other org-scoped resources follow the same pattern (CRUD, role-gated):

Resource Base path Permission Notes
Appointments /api/appointments appointment:* list / create / update / delete
Prescriptions /api/prescriptions prescription:* prescriber defaults to the signed-in user
Tasks /api/tasks task:* PATCH /:id for partial updates / the done toggle
Notes /api/notes — (author-scoped) private to the signed-in author
Activity GET /api/activity — (any member) audit feed of record changes
Analytics GET /api/analytics — (any member) computed clinic aggregates
Conversations /api/conversations — (participant-scoped) staff messaging; real-time over Socket.io
Notifications /api/notifications — (per-recipient) auto-generated; read-all + per-id read

Real-time messaging and live notifications are delivered over Socket.io, attached to the same HTTP server; the handshake is authenticated with the Better Auth session cookie.

Auth endpoints (sign up / in / out, verify email, reset password, organizations & invitations) are served by Better Auth under /api/auth/*.

Roles

Role Patient access Prescriptions Lab results Clinic management
owner read / write / delete read / write / delete read / write full
admin read / write / delete read / write / delete read / write members, invitations, settings
doctor / member (clinician) read / write read / write / delete read / write
reception read / write (demographics)
pharmacy read read / write
lab read read / write

Environment

See .env.example. If SMTP_HOST is unset, verification / reset / invitation emails are printed to the server console instead of being sent — no setup required for local development.