// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ───────────────────────────────────────────── // ENUMS // ───────────────────────────────────────────── enum Role { ADMIN PRODUCER SUPERVISOR ARTIST CLIENT } enum ProjectStatus { ACTIVE ON_HOLD COMPLETED ARCHIVED } enum ShotStatus { WAITING IN_PROGRESS IN_REVIEW REVISIONS COMPLETE } enum ShotPriority { LOW NORMAL HIGH URGENT } enum ApprovalStatus { PENDING_REVIEW APPROVED REJECTED NEEDS_CHANGES } enum ReviewStatus { PENDING INTERNAL_APPROVED CLIENT_APPROVED NEEDS_CHANGES FINAL_APPROVED } enum NotificationType { VERSION_UPLOADED FEEDBACK_ADDED SHOT_APPROVED SHOT_REJECTED COMMENT_REPLY MENTION REVISION_REQUESTED TASK_ASSIGNED TASK_OVERDUE TASK_APPROVED TASK_CHANGES_REQUESTED TASK_READY_FOR_REVIEW } enum TaskStatus { TODO IN_PROGRESS INTERNAL_REVIEW CLIENT_REVIEW CHANGES DONE } enum TaskType { TRACK ROTO KEY COMP FX LIGHTING RENDER ANIMATION MODEL TEXTURE RIG LOOKDEV GENERAL } enum ProjectType { STANDARD EPISODIC } // ───────────────────────────────────────────── // AUTH MODELS (NextAuth v5 compatible) // ───────────────────────────────────────────── model User { id String @id @default(cuid()) name String? email String @unique emailVerified DateTime? image String? passwordHash String? role Role @default(ARTIST) isActive Boolean @default(true) mustChangePassword Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // NextAuth relations accounts Account[] sessions Session[] // App relations producedProjects Project[] @relation("ProducerProjects") supervisedProjects Project[] @relation("SupervisorProjects") assignedShots Shot[] @relation("ArtistShots") uploadedVersions Version[] comments Comment[] commentReplies CommentReply[] annotations Annotation[] approvals Approval[] notifications Notification[] clientAccess ClientAccess[] assignedTasks Task[] @relation("TaskArtist") createdTasks Task[] @relation("TaskCreator") sharedVersions Version[] @relation("VersionSharedBy") ledAssets Asset[] @relation("AssetLead") @@map("users") } model Account { id String @id @default(cuid()) userId String type String provider String providerAccountId String refresh_token String? @db.Text access_token String? @db.Text expires_at Int? token_type String? scope String? id_token String? @db.Text session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) @@map("accounts") } model Session { id String @id @default(cuid()) sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("sessions") } model VerificationToken { identifier String token String @unique expires DateTime @@unique([identifier, token]) @@map("verification_tokens") } // ───────────────────────────────────────────── // CORE DOMAIN MODELS // ───────────────────────────────────────────── model Client { id String @id @default(cuid()) company String contactPerson String email String @unique phone String? notes String? @db.Text logoUrl String? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt projects Project[] clientAccess ClientAccess[] @@map("clients") } /// Links a USER with CLIENT role to the client record they represent model ClientAccess { id String @id @default(cuid()) userId String clientId String createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) client Client @relation(fields: [clientId], references: [id], onDelete: Cascade) @@unique([userId, clientId]) @@map("client_access") } model Project { id String @id @default(cuid()) name String code String @unique showId String @default("") projectType ProjectType @default(STANDARD) description String? @db.Text status ProjectStatus @default(ACTIVE) dueDate DateTime? startDate DateTime? clientId String? producerId String? supervisorId String? slackWebhook String? slackChannel String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt client Client? @relation(fields: [clientId], references: [id]) producer User? @relation("ProducerProjects", fields: [producerId], references: [id]) supervisor User? @relation("SupervisorProjects", fields: [supervisorId], references: [id]) shots Shot[] assets Asset[] tasks Task[] reviewSessions ReviewSession[] @@map("projects") } model Shot { id String @id @default(cuid()) shotCode String scene String @default("") episode String? shotNumber Int @default(0) sequence String? description String? @db.Text status ShotStatus @default(WAITING) priority ShotPriority @default(NORMAL) artistId String? projectId String frameStart Int? frameEnd Int? fps Float @default(24) dueDate DateTime? thumbnailUrl String? originalFootageUrl String? originalFootageKey String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) artist User? @relation("ArtistShots", fields: [artistId], references: [id]) versions Version[] tasks Task[] @@unique([projectId, shotCode]) @@map("shots") } model Version { id String @id @default(cuid()) versionNumber Int shotId String? taskId String? artistId String? fileUrl String fileName String fileSize BigInt? mimeType String? thumbnailUrl String? posterUrl String? proxyUrl String? fps Float @default(24) duration Float? frameCount Int? width Int? height Int? notes String? @db.Text approvalStatus ApprovalStatus @default(PENDING_REVIEW) reviewStatus ReviewStatus @default(PENDING) isLatest Boolean @default(true) isClientVisible Boolean @default(false) sharedAt DateTime? sharedById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt shot Shot? @relation(fields: [shotId], references: [id], onDelete: Cascade) task Task? @relation(fields: [taskId], references: [id], onDelete: Cascade) artist User? @relation(fields: [artistId], references: [id]) sharedBy User? @relation("VersionSharedBy", fields: [sharedById], references: [id]) comments Comment[] annotations Annotation[] approvals Approval[] @@map("versions") } model Comment { id String @id @default(cuid()) versionId String authorId String? frameNumber Int timestamp Float text String @db.Text isResolved Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt version Version @relation(fields: [versionId], references: [id], onDelete: Cascade) author User? @relation(fields: [authorId], references: [id], onDelete: SetNull) replies CommentReply[] annotations Annotation[] @@map("comments") } model CommentReply { id String @id @default(cuid()) commentId String authorId String? text String @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade) author User? @relation(fields: [authorId], references: [id], onDelete: SetNull) @@map("comment_replies") } /// Stores canvas drawing data as JSON (normalized 0-1 coordinates) model Annotation { id String @id @default(cuid()) versionId String commentId String? authorId String? frameNumber Int drawingData Json // Array of AnnotationShape objects color String @default("#ef4444") isVisible Boolean @default(true) createdAt DateTime @default(now()) version Version @relation(fields: [versionId], references: [id], onDelete: Cascade) comment Comment? @relation(fields: [commentId], references: [id], onDelete: SetNull) author User? @relation(fields: [authorId], references: [id], onDelete: SetNull) @@map("annotations") } model Approval { id String @id @default(cuid()) versionId String userId String? status ApprovalStatus notes String? @db.Text createdAt DateTime @default(now()) version Version @relation(fields: [versionId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@map("approvals") } model Notification { id String @id @default(cuid()) userId String type NotificationType title String message String @db.Text data Json? // Extra context (versionId, shotCode, etc.) isRead Boolean @default(false) createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("notifications") } model Asset { id String @id @default(cuid()) projectId String assetCode String name String description String? @db.Text status ShotStatus @default(WAITING) priority ShotPriority @default(NORMAL) leadId String? dueDate DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) lead User? @relation("AssetLead", fields: [leadId], references: [id]) tasks Task[] @@unique([projectId, assetCode]) @@map("assets") } model Task { id String @id @default(cuid()) title String description String? @db.Text type TaskType @default(GENERAL) status TaskStatus @default(TODO) priority ShotPriority @default(NORMAL) dueDate DateTime? estimatedHours Float? sortOrder Int @default(0) shotId String? assetId String? assignedArtistId String? createdById String? projectId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt scheduledStartDate DateTime? scheduledEndDate DateTime? scheduleNotes String? @db.Text shot Shot? @relation(fields: [shotId], references: [id], onDelete: Cascade) asset Asset? @relation(fields: [assetId], references: [id], onDelete: Cascade) assignedArtist User? @relation("TaskArtist", fields: [assignedArtistId], references: [id]) createdBy User? @relation("TaskCreator", fields: [createdById], references: [id], onDelete: SetNull) project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) versions Version[] @@index([scheduledStartDate]) @@index([scheduledEndDate]) @@map("tasks") } /// Secure tokenized review link for clients model ReviewSession { id String @id @default(cuid()) projectId String token String @unique @default(cuid()) label String? email String? expiresAt DateTime isActive Boolean @default(true) accessCount Int @default(0) createdAt DateTime @default(now()) project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) @@map("review_sessions") }