feat(auth): add user authentication database schema

- Add User model with email, passwordHash, and name fields
- Add userId foreign key to Drawing and Collection models
- Create initial migration for user authentication
This commit is contained in:
Matteo
2026-01-24 17:11:40 +01:00
parent 81918b00cd
commit d9013b8f7a
2 changed files with 116 additions and 1 deletions
@@ -0,0 +1,64 @@
/*
Warnings:
- Added the required column `userId` to the `Collection` table without a default value. This is not possible if the table is not empty.
- Added the required column `userId` to the `Drawing` table without a default value. This is not possible if the table is not empty.
*/
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL PRIMARY KEY,
"email" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Collection" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Collection_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_Collection" ("createdAt", "id", "name", "updatedAt") SELECT "createdAt", "id", "name", "updatedAt" FROM "Collection";
DROP TABLE "Collection";
ALTER TABLE "new_Collection" RENAME TO "Collection";
CREATE TABLE "new_Drawing" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"elements" TEXT NOT NULL,
"appState" TEXT NOT NULL,
"files" TEXT NOT NULL DEFAULT '{}',
"preview" TEXT,
"version" INTEGER NOT NULL DEFAULT 1,
"userId" TEXT NOT NULL,
"collectionId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Drawing_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "Drawing_collectionId_fkey" FOREIGN KEY ("collectionId") REFERENCES "Collection" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_Drawing" ("appState", "collectionId", "createdAt", "elements", "files", "id", "name", "preview", "updatedAt", "version") SELECT "appState", "collectionId", "createdAt", "elements", "files", "id", "name", "preview", "updatedAt", "version" FROM "Drawing";
DROP TABLE "Drawing";
ALTER TABLE "new_Drawing" RENAME TO "Drawing";
CREATE TABLE "new_Library" (
"id" TEXT NOT NULL PRIMARY KEY,
"items" TEXT NOT NULL DEFAULT '[]',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Library" ("createdAt", "id", "items", "updatedAt") SELECT "createdAt", "id", "items", "updatedAt" FROM "Library";
DROP TABLE "Library";
ALTER TABLE "new_Library" RENAME TO "Library";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
+52 -1
View File
@@ -12,9 +12,26 @@ datasource db {
url = env("DATABASE_URL") url = env("DATABASE_URL")
} }
model User {
id String @id @default(uuid())
email String @unique
passwordHash String
name String
isActive Boolean @default(true)
drawings Drawing[]
collections Collection[]
passwordResetTokens PasswordResetToken[]
refreshTokens RefreshToken[]
auditLogs AuditLog[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Collection { model Collection {
id String @id @default(uuid()) id String @id @default(uuid())
name String name String
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
drawings Drawing[] drawings Drawing[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -28,6 +45,8 @@ model Drawing {
files String @default("{}") // Stored as JSON string files String @default("{}") // Stored as JSON string
preview String? // SVG string for thumbnail preview String? // SVG string for thumbnail
version Int @default(1) version Int @default(1)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
collectionId String? collectionId String?
collection Collection? @relation(fields: [collectionId], references: [id]) collection Collection? @relation(fields: [collectionId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -35,8 +54,40 @@ model Drawing {
} }
model Library { model Library {
id String @id @default("default") // Singleton pattern - use "default" ID id String @id // User-specific library ID (e.g., "user_<userId>")
items String @default("[]") // Stored as JSON string array of library items items String @default("[]") // Stored as JSON string array of library items
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
model PasswordResetToken {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
expiresAt DateTime
used Boolean @default(false)
createdAt DateTime @default(now())
}
model RefreshToken {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
expiresAt DateTime
revoked Boolean @default(false)
createdAt DateTime @default(now())
}
model AuditLog {
id String @id @default(uuid())
userId String?
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
action String // e.g., "login", "login_failed", "password_reset", "password_changed", "drawing_deleted"
resource String? // e.g., "drawing:123", "collection:456"
ipAddress String?
userAgent String?
details String? // JSON string for additional details
createdAt DateTime @default(now())
}