feat: patient & lab file attachments (real backend storage)

Add a real file-storage layer to the backend and wire upload UI into the
frontend.

backend:
- new `attachments` table (org-scoped, links to a patient file number and
  optionally a lab result) + Drizzle migration
- `/api/attachments` route: upload (multer → disk under UPLOAD_DIR),
  list, stream/download, delete; gated by patient:write OR lab:write via
  a new requireAnyPermission helper so lab staff can attach analyses
- UPLOAD_DIR env (default ./uploads) + a persistent docker volume

frontend:
- lib/attachments.ts client (multipart upload, list, delete, preview URL)
- staged file picker in the patient Add/Edit dialog (uploaded after save)
  and the lab Add-result dialog (linked to the result)
- a Files section in the patient sheet that lists attachments and opens
  them in a preview dialog (images inline, others via download)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-06-18 20:04:03 +03:00
parent 67cafdac3d
commit b1abb29108
21 changed files with 4574 additions and 14 deletions
+16
View File
@@ -0,0 +1,16 @@
CREATE TABLE "attachments" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"organization_id" text NOT NULL,
"file_number" text,
"lab_key" text,
"filename" text NOT NULL,
"mime_type" text NOT NULL,
"size_bytes" integer NOT NULL,
"storage_path" text NOT NULL,
"uploaded_by_user_id" text,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "attachments" ADD CONSTRAINT "attachments_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "attachments" ADD CONSTRAINT "attachments_uploaded_by_user_id_user_id_fk" FOREIGN KEY ("uploaded_by_user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "attachments_org_file_idx" ON "attachments" USING btree ("organization_id","file_number");
File diff suppressed because it is too large Load Diff
+7
View File
@@ -148,6 +148,13 @@
"when": 1781629630102,
"tag": "0020_freezing_tomas",
"breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1781801972208,
"tag": "0021_milky_blur",
"breakpoints": true
}
]
}