mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
56b09c269b
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.4 KiB
6.4 KiB
CLAUDE.md
Guidance for Claude Code when working in backend/. See the root ../CLAUDE.md for the project
vision and README.md here for run/setup instructions.
What this is
The temetro API: TypeScript + Express 5 + Postgres (Drizzle ORM), with authentication and
multi-tenant clinics via Better Auth. Serves the ../frontend app. ESM ("type": "module"),
Node ≥ 20. This is its own git repo — commit changes here with the
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> trailer.
Commands
npm run dev # tsx watch on http://localhost:4000
npm run build # tsc -> dist/
npm run typecheck # tsc --noEmit
npm run auth:generate # Better Auth CLI -> src/db/schema/auth.ts (re-run after auth config changes)
npm run db:generate # drizzle-kit: schema -> ./drizzle/*.sql migration
npm run db:migrate # drizzle-kit: apply migrations (local dev)
npm run db:apply # node dist/migrate.js — runtime migrator (used by Docker)
No test runner is configured. Verify by running the stack (docker compose up) and curling, or
npm run dev against a local Postgres. See README.md.
Architecture
src/auth.ts— the Better Auth config (the CLI auto-discovers it). Email/password + username plugin (staff sign in by username) and the organization plugin (clinics) with custom RBAC fromsrc/lib/access.ts(owner/admin/doctor/reception/pharmacy/lab/memberoverpatient/appointment/prescription/task/labresources).receptionhas noprescriptionstatement — it's scoped to scheduling + registration, andsrc/services/patients.tsredacts clinical fields for it.pharmacyhasprescriptionread/write (no delete —prescription:deleteis the full-clinician marker the frontend route gating probes);labsubmits results via thelabstatement (POST /api/patients/:fileNumber/labs) withoutpatient:write. Mounted insrc/index.tsviatoNodeHandler(auth)at/api/auth/*.src/routes/staff.ts— admin-provisioned staff:POST /api/staffcreates a user (auth.api.signUpEmail) + attaches them to the clinic (auth.api.addMember);GET /api/stafflists members with usernames. Replaces the old email-invitation flow. Gated byrequirePermission({ member: ["create"] }).- Better Auth = the single source of RBAC. Manage all permissions through Better Auth, not a
custom layer. Authoritative references live as repo skills under
.claude/skills/{better-auth-best-practices,organization-best-practices, email-and-password-best-practices,better-auth-security-best-practices}— consult them before changingauth.ts,src/lib/access.ts, or the auth schema. src/db/—index.tsis the Drizzle client (no schema passed; we use the core query builder).schema/auth.tsis generated by the Better Auth CLI;schema/patients.tsis hand-written and references the generatedorganization/usertables.src/services/patients.tsmaps DB rows ⇆ the canonicalPatientshape (src/types/patient.ts, mirrors../frontend/lib/patients.ts).src/routes/patients.tsis org-scoped CRUD, gated bysrc/middleware/auth.ts(requireAuth→requireOrg→requirePermission).- Other resources follow the patients/notes pattern (schema → validation → types → service →
org-scoped route): appointments, prescriptions, tasks (RBAC-gated like patients),
plus activity (an audit log written best-effort from every resource route via
services/activity.ts), analytics (computed aggregates), messaging (conversations / participants / messages) and notifications (per-recipient, auto-generated). - Real-time lives in
src/realtime.ts— a Socket.io server attached to the same HTTP server inindex.ts; the handshake reuses Better Auth'sgetSession. Other modules push viaemitToUser/emitToConversation(no direct socket import, so no circular deps). src/lib/email.ts—sendEmaillogs links to the console when SMTP is unset.
Gotchas / conventions
- Schema generation order (circular bootstrap):
auth.ts→db/index.tsmust NOT pull in the patient schema, so the Better Auth CLI can loadauth.tsto (re)generateschema/auth.ts. After any auth/plugin change:npm run auth:generate→npm run db:generate→npm run db:migrate. - Express 5 needs a named wildcard:
app.all("/api/auth/*splat", …), and the Better Auth handler must be registered beforeexpress.json(). - Secure cookies key off
BETTER_AUTH_URLscheme (https), notNODE_ENV— otherwise login breaks overhttp://localhostin the production-mode Docker stack. - Rate limiting needs a client IP; behind no proxy we backfill
x-forwarded-forfrom the socket inindex.tsso it still applies. - Env:
src/env.ts(zod) treats empty strings as unset (compose passes${VAR:-}as"") and has dev defaults so the CLIs can load the config offline. - Email verification is wired but NOT enforced at sign-in
(
emailAndPassword.requireEmailVerification: falseinauth.ts) — re-enable by flipping it totrue(and route signup back to/verify-emailin the frontend). - Docker: migrations run on container start (
node dist/migrate.js);docker-compose.ymlexposes a configurablePOSTGRES_PORThost port.
AI chat
src/routes/chat.ts—POST /api/chat, a tool-using agent (AI SDK v6streamText) gated bypatient:read. Providers resolve from the picked model id viasrc/services/ai/provider.ts(Anthropic / OpenAI / Gemini for API-key mode, or a local Ollama via the OpenAI-compatible endpoint). Tools (src/services/ai/tools.ts) reuse the patient service under the same role scoping and stream real record data to the client as custom data parts while the model sees only Veil-redacted results.- Veil (
src/services/ai/veil.ts) de-identifies PHI to tokens before external calls, resolves tokens on tool args, and rehydrates the final text (external mode runs non-streamed so the rehydrated text is correct). Local mode bypasses it. - AI config lives in
src/routes/ai.ts(/api/ai/config,/test,/import) over theuser_ai_settingstable; provider keys are encrypted withsrc/lib/crypto.ts(AI_CREDENTIALS_KEY). The migration import is gated behind a client approval step and re-validated server-side.
Not built yet
The signing / patient-owned-storage / approval flow.