POST /voice/extract behind JwtAuthGuard + ClinicOrgGuard, plus GET /voice/availability so the frontend can decide whether to render the microphone — it cannot learn that from NEXT_PUBLIC_*, which are baked in at build time. Audio is held in memory for the request only: never written to disk, never a Prisma row. The transcript goes back to the client and is not persisted. What is logged is structured and patient-free — clip length, which fields resolved, unresolved count, vendor cost, outcome — with log lines as the interim sink until this repo has metrics infrastructure. On extraction failure the transcript still travels back in the error details, so the words the clinician already paid for can be salvaged into a note. v1 ships ungated beyond a configured locale profile; the Plan.features design is deferred, not dropped. From review of this commit, four of which were load-bearing: - Express's 100 kb default body limit rejected any recording past ~20 seconds, making the endpoint unusable at its own 2-minute cap. Body parsers are now registered explicitly with a 10 MB limit scoped to the voice route only. Verified empirically: 600 KB reaches /api/voice/extract, while /api/auth/login still 413s. - ThrottlerGuard keys on req.ip, so behind nginx the whole deployment would share one bucket and an abuser rotating IPs would bypass it. VoiceThrottlerGuard keys on the user id instead — with no plan gate, this is the only control on metered vendor spend. - ThrottlerException had no 429 fallback and surfaced as INTERNAL_ERROR; the guard now throws VOICE_RATE_LIMITED directly. - durationMs was optional, so omitting it bypassed VOICE_MAX_RECORDING_MS entirely. It is required. - VOICE_UNSUPPORTED_FORMAT was dead code — the DTO's @IsIn already rejects unknown containers — so it is gone rather than left unreachable. ThrottlerModule is deliberately not bound as a global APP_GUARD: a global ThrottlerGuard rate-limits every route against every named throttler, which would have capped the whole API at the voice limit. All seven remaining VOICE_* codes have errors.* keys in en, fa and nl. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dyolink — Backend (NestJS)
Prerequisites
- Node.js 20+ and npm
- PostgreSQL reachable from your machine — either installed locally or run via Docker (see below)
First-time setup
-
Clone the monorepo and go to the backend app:
git clone <repository-url> dyolink cd dyolink/backend -
Install dependencies
npm install -
Environment
Copy
.env.exampleto.envand set at least:DATABASE_URL— PostgreSQL connection string for your dev databaseJWT_SECRET— strong secret for signing tokens
Do not commit.env.
-
Database for local dev
Option A — Postgres in Docker (no local install, e.g. Mac)
Frombackend/, with.envpresent (copy from.env.examplefirst):- Ensure
DATABASE_URLuseslocalhostas the host (notpostgres). Match user, password, and DB name toPOSTGRES_USER,POSTGRES_PASSWORD, andPOSTGRES_DBin the same file.
docker compose -f docker-compose.postgres.yml up -dWait until Postgres is healthy (
docker compose -f docker-compose.postgres.yml ps). The container creates the database on first start.To stop Postgres (data is kept in the named volume):
docker compose -f docker-compose.postgres.yml downOption B — Postgres installed on the machine
Create an empty database, then pointDATABASE_URLat it. - Ensure
-
Generate Prisma Client
npm run prisma:generate -
Apply migrations (creates/updates tables to match
prisma/schema.prisma)npm run prisma:migrateThis runs
prisma migrate dev. Use it during development when the schema changes. -
Seed (optional — reference data only)
npm run prisma:seedThis does not wipe your database. It only upserts lookup data: organization types (
CLINIC,LAB), subscription plans, and tab permissions. Existing users, organizations, memberships, patients, appointments, and links are left unchanged.To start from an empty database with fresh tables and reference data, see Reset database (clean slate) below.
Reset database (clean slate)
Use this when you want to delete all application data (users, organizations, patients, sessions, etc.) and rebuild the schema from migrations, then run the seed.
From backend/:
npx prisma migrate reset
Prisma will prompt for confirmation, drop the database, re-apply all migrations, and run prisma/seed.ts automatically.
What gets removed: everything in the database, including organizations and all related rows.
What the seed adds back: only reference data (types, plans, permissions) — not demo users or organizations. Register again or use your own test data after a reset.
Docker Postgres dev: if you also want to wipe the Docker volume (not only tables), stop the container and remove the volume:
docker compose -f docker-compose.postgres.yml down -v
docker compose -f docker-compose.postgres.yml up -d
npm run prisma:migrate
npm run prisma:seed
Do not run migrate reset against production or shared staging databases.
Run (development)
npm run start:dev
API listens on http://localhost:3000 by default (PORT in .env).
If the frontend runs on another origin (e.g. http://localhost:3001), set FRONTEND_URL in .env to that URL (CORS and invite links use it).
After pulling latest main
git pull
npm install
npm run prisma:generate
npm run prisma:migrate
If teammates added migrations, the migrate step above applies them. Resolve migration conflicts locally before pushing.
Useful commands
| Command | Purpose |
|---|---|
npm run prisma:generate |
Regenerate client after schema.prisma changes |
npm run prisma:migrate |
Dev migrations (migrate dev) |
npm run prisma:deploy |
Production-style apply (migrate deploy) — e.g. CI/containers |
npm run prisma:seed |
Upsert reference data only (does not clear existing rows) |
npx prisma migrate reset |
Drop DB, re-migrate, run seed — dev clean slate |
npm run build |
Compile Nest app |
npm run start:prod |
Run compiled app (node dist/main) |
Docker
| File | Purpose |
|---|---|
Dockerfile |
Production API image |
docker-compose.postgres.yml |
Local dev Postgres only (port mapped to host) |
For full-stack deployment and CI, see the repository root README.md.