Files
vfxreview/prisma/schema.prisma
T
twotalesanimation 9a49cdc6a3
Deploy / deploy (push) Failing after 1m52s
RenderPipeline2
2026-08-01 15:44:26 +02:00

893 lines
24 KiB
Plaintext

// 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
INTERNAL_REVIEW
READY_FOR_CLIENT
CLIENT_REVIEW
REVISIONS
COMPLETE
}
enum ShotApprovalStatus {
PENDING
INTERNALLY_APPROVED
CLIENT_APPROVED
}
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
}
enum TakeQuality {
HERO
GOOD
PRINT
NO_GOOD
FALSE_START
}
enum AttachmentFileType {
IMAGE
VIDEO
HDRI
LIDAR
LENS_GRID
PDF
OTHER
}
enum AttachmentCategory {
SLATE
CAMERA_POSITION
WIDE_REFERENCE
LENS
LIGHTING
HDRI
TRACKING
TEXTURE
WITNESS
MISCELLANEOUS
}
enum ExportStatus {
QUEUED
RENDERING
RENDER_FAILED
VALIDATING
VALIDATION_FAILED
GENERATING_PREVIEW
PREVIEW_FAILED
READY_FOR_QC
QC_FAILED
READY_FOR_DELIVERY
PACKAGED
DELIVERED
SUPERSEDED
ARCHIVED
CANCELLED
}
enum RenderJobStatus {
QUEUED
CLAIMED
RUNNING
COMPLETED
FAILED
CANCELLED
EXPIRED
}
enum RenderJobType {
AE_RENDER
PREVIEW_ONLY
DELIVERY_BUILD
}
// ─────────────────────────────────────────────
// 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")
createdShootDays ShootDay[] @relation("ShootDayCreator")
createdTakes Take[] @relation("TakeCreator")
uploadedTakeAttachments TakeAttachment[] @relation("TakeAttachmentUploader")
@@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)
deliveryConfig Json?
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[]
shotGroups ShotGroup[]
reviewSessions ReviewSession[]
episodeDueDates EpisodeDueDate[]
shootDays ShootDay[]
@@map("projects")
}
model EpisodeDueDate {
id String @id @default(cuid())
projectId String
episode String
dueDate DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@unique([projectId, episode])
@@map("episode_due_dates")
}
model Shot {
id String @id @default(cuid())
shotCode String
scene String @default("")
episode String?
shotNumber Int @default(0)
sequence String?
description String? @db.Text
notes 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?
shotGroupId String?
shotApprovalStatus ShotApprovalStatus @default(PENDING)
sharedWithClient Boolean @default(false)
// EDL / pull CSV fields
sourceClip String?
timecodeStart String?
timecodeEnd String?
clipDuration String?
exrOutput String?
// Sequence / picture lock timecodes
seqTimecodeStart String?
seqTimecodeEnd String?
// High-res deliverable (stored in Hetzner Object Storage)
highResKey String?
highResFilename String?
// Shot-level version tracking (e.g. v001, v002)
shotVersion String @default("v001")
// Key shot flag — highlighted for prioritisation
isKeyShot Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
artist User? @relation("ArtistShots", fields: [artistId], references: [id])
shotGroup ShotGroup? @relation(fields: [shotGroupId], references: [id])
versions Version[]
tasks Task[]
exports Export[]
footagePlates FootagePlate[]
references ShotReference[]
loggedTakes Take[] @relation("TakeToShot")
@@unique([projectId, shotCode])
@@map("shots")
}
model FootagePlate {
id String @id @default(cuid())
shotId String
label String @default("")
fileUrl String
fileKey String @default("")
fileName String @default("")
fileSize BigInt?
sortOrder Int @default(0)
createdAt DateTime @default(now())
shot Shot @relation(fields: [shotId], references: [id], onDelete: Cascade)
@@map("footage_plates")
}
model ShotReference {
id String @id @default(cuid())
shotId String
label String?
fileUrl String
fileKey String @default("")
fileName String @default("")
fileSize BigInt?
sortOrder Int @default(0)
createdAt DateTime @default(now())
shot Shot @relation(fields: [shotId], references: [id], onDelete: Cascade)
@@map("shot_references")
}
model ShotGroup {
id String @id @default(cuid())
name String
projectId String
sortOrder Int @default(0)
createdAt DateTime @default(now())
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
shots Shot[]
@@unique([projectId, name])
@@map("shot_groups")
}
model Export {
id String @id @default(cuid())
shotId String
shot Shot @relation(fields: [shotId], references: [id], onDelete: Cascade)
projectId String
taskId String?
versionId String?
versionNumber Int
versionString String
status ExportStatus @default(QUEUED)
statusChangedAt DateTime @default(now())
aepPath String
compName String
rendererType String
outputDir String
outputPattern String
frameStart Int
frameEnd Int
fps Float
width Int
height Int
colorspace String?
deliveryMovPath String?
previewMovKey String?
thumbnailKey String?
metadataKey String?
exrFileCount Int?
exrTotalBytes BigInt?
checksum String?
submittedById String?
submittedByName String?
supersededById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
renderJobs RenderJob[]
events ExportEvent[]
@@unique([shotId, versionNumber])
@@index([status])
@@index([projectId, status])
@@map("exports")
}
model RenderJob {
id String @id @default(cuid())
type RenderJobType @default(AE_RENDER)
exportId String?
export Export? @relation(fields: [exportId], references: [id], onDelete: Cascade)
deliveryId String?
attempt Int @default(1)
maxAttempts Int @default(3)
status RenderJobStatus @default(QUEUED)
priority Int @default(50)
manifest Json
machineId String?
claimedAt DateTime?
leaseExpiresAt DateTime?
startedAt DateTime?
finishedAt DateTime?
progress Float @default(0)
currentFrame Int?
totalFrames Int?
etaSeconds Int?
exitCode Int?
errorMessage String?
logTail String?
logFileKey String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status, priority, createdAt])
@@map("render_jobs")
}
model ExportEvent {
id String @id @default(cuid())
exportId String
export Export @relation(fields: [exportId], references: [id], onDelete: Cascade)
fromStatus String?
toStatus String
actorType String
actorId String?
note String?
createdAt DateTime @default(now())
@@index([exportId, createdAt])
@@map("export_events")
}
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?
passwordHash String?
expiresAt DateTime
isActive Boolean @default(true)
accessCount Int @default(0)
allowedEpisodes String[] @default([])
createdAt DateTime @default(now())
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@map("review_sessions")
}
/// Key-value store for system-wide configuration (e.g. storage credentials)
model SystemConfig {
key String @id
value String @db.Text
updatedAt DateTime @updatedAt
@@map("system_config")
}
/// Physical display event log — polled by ESP32 dot-matrix display
model DisplayEvent {
id Int @id @default(autoincrement())
type String // APPROVED | CHANGES | COMMENT | REJECTED
shotCode String
by String // actor name (reviewer / commenter)
createdAt DateTime @default(now())
@@index([createdAt])
@@map("display_events")
}
// ─────────────────────────────────────────────
// SHOOT LOG MODELS
// ─────────────────────────────────────────────
model ShootDay {
id String @id @default(cuid())
projectId String
date DateTime
unit String @default("A")
label String?
notes String? @db.Text
createdById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdBy User? @relation("ShootDayCreator", fields: [createdById], references: [id], onDelete: SetNull)
setups Setup[]
@@map("shoot_days")
}
model Setup {
id String @id @default(cuid())
shootDayId String
name String
description String? @db.Text
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
shootDay ShootDay @relation(fields: [shootDayId], references: [id], onDelete: Cascade)
takes Take[]
@@map("setups")
}
model Take {
id String @id @default(cuid())
setupId String
takeNumber Int
// Optional link back to a pipeline shot
pipelineShotId String?
// General
scene String?
shotLabel String?
unitLabel String?
// Camera
cameraLetter String?
clipName String?
roll String?
cameraModel String?
resolution String?
codec String?
fps Float?
shutter String?
iso Int?
whiteBalance Int?
colourSpace String?
// Lens
lensSet String?
lens String?
tStop String?
filters String?
isAnamorphic Boolean @default(false)
// Tracking
hasHdri Boolean @default(false)
hasChromeBall Boolean @default(false)
hasGreyBall Boolean @default(false)
hasMacbeth Boolean @default(false)
hasCleanPlate Boolean @default(false)
hasSurvey Boolean @default(false)
hasLidar Boolean @default(false)
hasWitnessCamera Boolean @default(false)
hasLensGrid Boolean @default(false)
hasTexturePhotos Boolean @default(false)
hasPhotogrammetry Boolean @default(false)
// Environment
weather String?
sunDirection String?
artificialLights String? @db.Text
// Supervisor
supervisorNotes String? @db.Text
continuityNotes String? @db.Text
vfxRequirements String? @db.Text
quality TakeQuality @default(PRINT)
createdById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
setup Setup @relation(fields: [setupId], references: [id], onDelete: Cascade)
pipelineShot Shot? @relation("TakeToShot", fields: [pipelineShotId], references: [id], onDelete: SetNull)
createdBy User? @relation("TakeCreator", fields: [createdById], references: [id], onDelete: SetNull)
attachments TakeAttachment[]
@@unique([setupId, takeNumber])
@@map("takes")
}
model TakeAttachment {
id String @id @default(cuid())
takeId String
fileUrl String
fileKey String @default("")
fileName String
fileSize BigInt?
fileType AttachmentFileType @default(IMAGE)
category AttachmentCategory @default(MISCELLANEOUS)
caption String?
sortOrder Int @default(0)
uploadedById String?
createdAt DateTime @default(now())
take Take @relation(fields: [takeId], references: [id], onDelete: Cascade)
uploadedBy User? @relation("TakeAttachmentUploader", fields: [uploadedById], references: [id], onDelete: SetNull)
@@map("take_attachments")
}
model SketchTemplate {
id String @id @default(cuid())
name String
fileUrl String
fileKey String @default("")
fileName String @default("")
fileSize BigInt?
sortOrder Int @default(0)
createdAt DateTime @default(now())
@@map("sketch_templates")
}