Files
temetro/backend/drizzle/0000_small_sir_ram.sql
Claude 9dabe2f5d2 Add TypeScript + Express + Postgres backend with Better Auth
Implements the first temetro backend: an Express 5 API on Postgres via
Drizzle ORM, with authentication and multi-tenant clinics powered by
Better Auth.

- Auth: email/password with required email verification, password reset,
  rate limiting, CSRF/trusted-origins, secure cookies, session audit hook.
- Organizations (clinics) with RBAC (owner/admin/member/viewer) and an
  extended `patient` permission set; member invitations by email.
- Org-scoped patient records mirroring the frontend Patient shape, with
  CRUD endpoints gated by permission (read/write/delete).
- Email helper logs links to the console when SMTP is unset (zero-setup
  local dev); Dockerfile + docker-compose (db + backend + frontend) with
  migrations applied on startup and a configurable Postgres host port.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 21:27:32 +03:00

183 lines
8.5 KiB
SQL

CREATE TABLE "account" (
"id" text PRIMARY KEY NOT NULL,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" text NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp,
"refresh_token_expires_at" timestamp,
"scope" text,
"password" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp NOT NULL
);
--> statement-breakpoint
CREATE TABLE "invitation" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"email" text NOT NULL,
"role" text,
"status" text DEFAULT 'pending' NOT NULL,
"expires_at" timestamp NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"inviter_id" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "member" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"user_id" text NOT NULL,
"role" text DEFAULT 'member' NOT NULL,
"created_at" timestamp NOT NULL
);
--> statement-breakpoint
CREATE TABLE "organization" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"slug" text NOT NULL,
"logo" text,
"created_at" timestamp NOT NULL,
"metadata" text,
CONSTRAINT "organization_slug_unique" UNIQUE("slug")
);
--> statement-breakpoint
CREATE TABLE "rate_limit" (
"id" text PRIMARY KEY NOT NULL,
"key" text NOT NULL,
"count" integer NOT NULL,
"last_request" bigint NOT NULL,
CONSTRAINT "rate_limit_key_unique" UNIQUE("key")
);
--> statement-breakpoint
CREATE TABLE "session" (
"id" text PRIMARY KEY NOT NULL,
"expires_at" timestamp NOT NULL,
"token" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp NOT NULL,
"ip_address" text,
"user_agent" text,
"user_id" text NOT NULL,
"active_organization_id" text,
CONSTRAINT "session_token_unique" UNIQUE("token")
);
--> statement-breakpoint
CREATE TABLE "user" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"email" text NOT NULL,
"email_verified" boolean DEFAULT false NOT NULL,
"image" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "user_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE "verification" (
"id" text PRIMARY KEY NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "patient_allergies" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"patient_id" uuid NOT NULL,
"position" integer DEFAULT 0 NOT NULL,
"substance" text NOT NULL,
"reaction" text NOT NULL,
"severity" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "patient_encounters" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"patient_id" uuid NOT NULL,
"position" integer DEFAULT 0 NOT NULL,
"date" text NOT NULL,
"type" text NOT NULL,
"provider" text NOT NULL,
"summary" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "patient_labs" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"patient_id" uuid NOT NULL,
"position" integer DEFAULT 0 NOT NULL,
"name" text NOT NULL,
"value" text NOT NULL,
"flag" text NOT NULL,
"taken_at" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "patient_medications" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"patient_id" uuid NOT NULL,
"position" integer DEFAULT 0 NOT NULL,
"name" text NOT NULL,
"dose" text NOT NULL,
"frequency" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "patients" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"organization_id" text NOT NULL,
"file_number" text NOT NULL,
"name" text NOT NULL,
"age" integer NOT NULL,
"sex" text NOT NULL,
"pcp" text NOT NULL,
"status" text NOT NULL,
"initials" text NOT NULL,
"alerts" jsonb NOT NULL,
"vitals_bp" text NOT NULL,
"vitals_hr" text NOT NULL,
"vitals_temp" text NOT NULL,
"vitals_spo2" text NOT NULL,
"vitals_taken_at" text NOT NULL,
"vitals_trend" jsonb NOT NULL,
"lab_trend" jsonb NOT NULL,
"created_by" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "patient_problems" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"patient_id" uuid NOT NULL,
"position" integer DEFAULT 0 NOT NULL,
"label" text NOT NULL,
"since" text NOT NULL
);
--> statement-breakpoint
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "member" ADD CONSTRAINT "member_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "member" ADD CONSTRAINT "member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "patient_allergies" ADD CONSTRAINT "patient_allergies_patient_id_patients_id_fk" FOREIGN KEY ("patient_id") REFERENCES "public"."patients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "patient_encounters" ADD CONSTRAINT "patient_encounters_patient_id_patients_id_fk" FOREIGN KEY ("patient_id") REFERENCES "public"."patients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "patient_labs" ADD CONSTRAINT "patient_labs_patient_id_patients_id_fk" FOREIGN KEY ("patient_id") REFERENCES "public"."patients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "patient_medications" ADD CONSTRAINT "patient_medications_patient_id_patients_id_fk" FOREIGN KEY ("patient_id") REFERENCES "public"."patients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "patients" ADD CONSTRAINT "patients_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "patients" ADD CONSTRAINT "patients_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "patient_problems" ADD CONSTRAINT "patient_problems_patient_id_patients_id_fk" FOREIGN KEY ("patient_id") REFERENCES "public"."patients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "invitation_organizationId_idx" ON "invitation" USING btree ("organization_id");--> statement-breakpoint
CREATE INDEX "invitation_email_idx" ON "invitation" USING btree ("email");--> statement-breakpoint
CREATE INDEX "member_organizationId_idx" ON "member" USING btree ("organization_id");--> statement-breakpoint
CREATE INDEX "member_userId_idx" ON "member" USING btree ("user_id");--> statement-breakpoint
CREATE UNIQUE INDEX "organization_slug_uidx" ON "organization" USING btree ("slug");--> statement-breakpoint
CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");--> statement-breakpoint
CREATE INDEX "allergies_patient_idx" ON "patient_allergies" USING btree ("patient_id");--> statement-breakpoint
CREATE INDEX "encounters_patient_idx" ON "patient_encounters" USING btree ("patient_id");--> statement-breakpoint
CREATE INDEX "labs_patient_idx" ON "patient_labs" USING btree ("patient_id");--> statement-breakpoint
CREATE INDEX "medications_patient_idx" ON "patient_medications" USING btree ("patient_id");--> statement-breakpoint
CREATE UNIQUE INDEX "patients_org_file_uidx" ON "patients" USING btree ("organization_id","file_number");--> statement-breakpoint
CREATE INDEX "patients_org_idx" ON "patients" USING btree ("organization_id");--> statement-breakpoint
CREATE INDEX "problems_patient_idx" ON "patient_problems" USING btree ("patient_id");