Skip to content

Commit 92b08bb

Browse files
OhYeeclaude
andcommitted
fix(s3): add upgrade migration for composite (drawingId, fileId) PK
Prior versions of this branch shipped the new S3File composite primary key by editing the original `20260408060000_add_s3_files` migration in place. That works for fresh deployments but is invisible to any deployment that already applied the original `id`-only PK migration — Prisma decides what to apply by migration name, so the new shape never reaches the database and runtime upserts crash with "The column `drawingId` does not exist in the current database." Restore the original migration to its pre-bundle shape and add a follow-up migration that drops + recreates S3File with the new shape. Both fresh and upgrade paths converge on the composite PK. Existing S3 objects are untouched; public-bucket deployments are unaffected. Private-bucket deployments will see /api/files/:drawingId/:fileId 404 for legacy rows until those drawings are re-saved (the save flow upserts on every base64 dataURL); operators with valuable legacy rows can rebuild manually before deploying. Change-Id: I9c06c7a46247050521a1d5013ec37eeebeb86247 Co-developed-by: Claude <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a615ca8 commit 92b08bb

2 files changed

Lines changed: 34 additions & 9 deletions

File tree

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
11
-- Track S3-uploaded image files for presigned download URL generation.
2-
-- The primary key is composite (drawingId, fileId): Excalidraw fileIds
3-
-- are content hashes that repeat across drawings, so a global PK on
4-
-- fileId alone would let a second upload silently overwrite the first
5-
-- and let cleanup of one drawing trash an object the other still needs.
62
CREATE TABLE "S3File" (
7-
"drawingId" TEXT NOT NULL,
8-
"fileId" TEXT NOT NULL,
3+
"id" TEXT NOT NULL PRIMARY KEY,
94
"userId" TEXT NOT NULL,
105
"s3Key" TEXT NOT NULL,
116
"mimeType" TEXT NOT NULL,
12-
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
13-
PRIMARY KEY ("drawingId", "fileId")
7+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
148
);
159

1610
CREATE INDEX "S3File_userId_idx" ON "S3File"("userId");
17-
CREATE INDEX "S3File_drawingId_idx" ON "S3File"("drawingId");
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-- Migrate S3File from a single-column `id` (= fileId) primary key to a
2+
-- composite (drawingId, fileId) primary key.
3+
--
4+
-- Why: Excalidraw fileIds are content hashes that legitimately repeat
5+
-- across drawings; a global PK on fileId alone meant the second upload
6+
-- of the same image silently overwrote the first row's s3Key, and any
7+
-- prefix-scoped cleanup deleted objects the sibling drawing still
8+
-- needed.
9+
--
10+
-- This migration drops the existing S3File rows and recreates the
11+
-- table with the new shape. Public-bucket deployments are unaffected
12+
-- (image dataURLs already encode the bucket URL directly). Private-
13+
-- bucket deployments will see /api/files/:drawingId/:fileId 404 for
14+
-- pre-existing images until each affected drawing is re-saved (the
15+
-- save flow upserts a fresh row per (drawingId, fileId) only when it
16+
-- detects new base64 dataURLs, so legacy rows must be rebuilt by hand
17+
-- if needed). S3 objects themselves are untouched.
18+
19+
DROP TABLE IF EXISTS "S3File";
20+
21+
CREATE TABLE "S3File" (
22+
"drawingId" TEXT NOT NULL,
23+
"fileId" TEXT NOT NULL,
24+
"userId" TEXT NOT NULL,
25+
"s3Key" TEXT NOT NULL,
26+
"mimeType" TEXT NOT NULL,
27+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
28+
PRIMARY KEY ("drawingId", "fileId")
29+
);
30+
31+
CREATE INDEX "S3File_userId_idx" ON "S3File"("userId");
32+
CREATE INDEX "S3File_drawingId_idx" ON "S3File"("drawingId");

0 commit comments

Comments
 (0)