mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
6213da9477
Replace the email-invitation flow with admin-provisioned staff accounts and
add role-based access that changes what each member sees.
Backend:
- Enable Better Auth `username` plugin (staff sign in by username); regenerate
auth schema (+ username/displayUsername on user) and migration 0007.
- Add `doctor` and `reception` roles to the access-control RBAC. `reception` is
scoped to scheduling + registration (no `prescription` statement).
- New `/api/staff` route: POST creates a user (auth.api.signUpEmail) and adds
them to the active clinic (auth.api.addMember); GET lists members + usernames.
Gated by requirePermission({ member: ["create"] }).
- Redact clinical PHI for the reception role in the patients service (read,
create and update) so demographics-only is enforced server-side.
Frontend:
- usernameClient + Email|Username tabs on the login form.
- lib/roles.ts: useActiveRole + Better-Auth-permission-driven nav visibility,
default landing, and a route guard (reception -> /appointments, blocked from
clinical routes). Applied to the sidebar, command palette and auth guard.
- Care team page now provisions staff via a two-step Add-team-member dialog
(details -> username/password) hitting /api/staff; removes the email-invite
and pending-invitation UI. New members are contactable from Messages
automatically (they become org members).
- Hide clinical sections of the patient form and the admin-only settings tabs
for non-clinical/non-admin roles.
All permission management stays in Better Auth (per the better-auth skills now
referenced in backend/CLAUDE.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.2 KiB
5.2 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/member/vieweroverpatient/appointment/prescription/taskresources).receptionhas noprescriptionstatement — it's scoped to scheduling + registration, andsrc/services/patients.tsredacts clinical fields for it. 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.
Not built yet
The AI /chat endpoint (LLM proxy) and the signing / patient-owned-storage / approval flow.