import { PrismaClient, Role, ProjectStatus, ShotStatus, ShotPriority, ApprovalStatus, TaskStatus, TaskType, } from "@prisma/client"; import bcrypt from "bcryptjs"; const db = new PrismaClient(); async function main() { console.log("🌱 Seeding VFX Review database..."); // ── Admin user ────────────────────────────────────── const adminPassword = await bcrypt.hash("admin123", 12); const admin = await db.user.upsert({ where: { email: "admin@vfxreview.local" }, update: {}, create: { name: "Admin User", email: "admin@vfxreview.local", passwordHash: adminPassword, role: Role.ADMIN, }, }); // ── Producer ───────────────────────────────────────── const producerPassword = await bcrypt.hash("producer123", 12); const producer = await db.user.upsert({ where: { email: "producer@vfxreview.local" }, update: {}, create: { name: "Sarah Chen", email: "producer@vfxreview.local", passwordHash: producerPassword, role: Role.PRODUCER, }, }); // ── Supervisor ─────────────────────────────────────── const supervisorPassword = await bcrypt.hash("supervisor123", 12); const supervisor = await db.user.upsert({ where: { email: "supervisor@vfxreview.local" }, update: {}, create: { name: "James Park", email: "supervisor@vfxreview.local", passwordHash: supervisorPassword, role: Role.SUPERVISOR, }, }); // ── Artist ────────────────────────────────────────── const artistPassword = await bcrypt.hash("artist123", 12); const artist = await db.user.upsert({ where: { email: "artist@vfxreview.local" }, update: {}, create: { name: "Maya Torres", email: "artist@vfxreview.local", passwordHash: artistPassword, role: Role.ARTIST, }, }); // ── Client user ────────────────────────────────────── const clientPassword = await bcrypt.hash("client123", 12); const clientUser = await db.user.upsert({ where: { email: "client@studio.com" }, update: {}, create: { name: "Alex Morgan", email: "client@studio.com", passwordHash: clientPassword, role: Role.CLIENT, }, }); // ── Client record ──────────────────────────────────── const client = await db.client.upsert({ where: { email: "client@studio.com" }, update: {}, create: { company: "Stellar Productions", contactPerson: "Alex Morgan", email: "client@studio.com", phone: "+1 (555) 000-0001", notes: "Demo client for seed data.", }, }); // Link client user to client record await db.clientAccess.upsert({ where: { userId_clientId: { userId: clientUser.id, clientId: client.id } }, update: {}, create: { userId: clientUser.id, clientId: client.id }, }); // ── Project ────────────────────────────────────────── const project = await db.project.upsert({ where: { code: "NOVA-2025" }, update: {}, create: { name: "Project Nova", code: "NOVA-2025", description: "Feature film visual effects - Act 2", status: ProjectStatus.ACTIVE, clientId: client.id, producerId: producer.id, supervisorId: supervisor.id, dueDate: new Date("2025-09-01"), startDate: new Date("2025-01-15"), }, }); // ── Shots ──────────────────────────────────────────── const shotData = [ { shotCode: "SH010", sequence: "010", description: "Wide establishing shot of the space station exterior.", status: ShotStatus.IN_REVIEW, priority: ShotPriority.HIGH, }, { shotCode: "SH020", sequence: "010", description: "Close-up of astronaut helmet reflection.", status: ShotStatus.IN_REVIEW, priority: ShotPriority.HIGH, }, { shotCode: "SH035", sequence: "020", description: "Explosion with debris field.", status: ShotStatus.REVISIONS, priority: ShotPriority.URGENT, }, { shotCode: "SH050", sequence: "020", description: "Hero flying through asteroid belt.", status: ShotStatus.IN_PROGRESS, priority: ShotPriority.NORMAL, }, { shotCode: "SH060", sequence: "030", description: "Earth from orbit, cloud layer simulation.", status: ShotStatus.COMPLETE, priority: ShotPriority.NORMAL, }, { shotCode: "SH070", sequence: "030", description: "Warp jump effect - tunnel of light.", status: ShotStatus.WAITING, priority: ShotPriority.LOW, }, ]; for (const shot of shotData) { await db.shot.upsert({ where: { projectId_shotCode: { projectId: project.id, shotCode: shot.shotCode } }, update: {}, create: { ...shot, projectId: project.id, artistId: artist.id, fps: 24, frameStart: 1001, frameEnd: 1080, }, }); } // ── Tasks ───────────────────────────────────────────── // Helper: find-or-create task by title + projectId async function upsertTask(data: { title: string; type: TaskType; status: TaskStatus; priority: ShotPriority; sortOrder: number; projectId: string; shotId?: string; assetId?: string; assignedArtistId?: string; dueDate?: Date; estimatedHours?: number; }) { const existing = await db.task.findFirst({ where: { projectId: data.projectId, title: data.title, shotId: data.shotId ?? null, assetId: data.assetId ?? null }, }); if (existing) return existing; return db.task.create({ data: { ...data, createdById: producer.id }, }); } const shots = await db.shot.findMany({ where: { projectId: project.id } }); const firstShot = shots.find((s) => s.shotCode === "SH010")!; const sh020 = shots.find((s) => s.shotCode === "SH020")!; const sh035 = shots.find((s) => s.shotCode === "SH035")!; // ── Tasks must be created first (versions belong to tasks) ── // Create tasks early so we can attach versions to them const sh010CompTask = await upsertTask({ title: "SH010 Comp", type: TaskType.COMP, status: TaskStatus.CLIENT_REVIEW, priority: ShotPriority.HIGH, sortOrder: 2, projectId: project.id, shotId: firstShot.id, assignedArtistId: artist.id, dueDate: new Date("2025-06-15"), estimatedHours: 24 }); // ── Versions now belong to tasks, not shots ── const existingV1 = await db.version.findFirst({ where: { taskId: sh010CompTask.id, versionNumber: 1 }, }); if (!existingV1) { await db.version.create({ data: { versionNumber: 1, taskId: sh010CompTask.id, artistId: artist.id, fileUrl: "/placeholder/sh010_comp_v001.mp4", fileName: "sh010_comp_v001.mp4", fps: 24, duration: 3.375, frameCount: 81, width: 1920, height: 1080, notes: "Initial blocking pass. Camera move rough.", approvalStatus: ApprovalStatus.NEEDS_CHANGES, isLatest: false, }, }); } let latestVersion = await db.version.findFirst({ where: { taskId: sh010CompTask.id, versionNumber: 2 }, }); if (!latestVersion) { latestVersion = await db.version.create({ data: { versionNumber: 2, taskId: sh010CompTask.id, artistId: artist.id, fileUrl: "/placeholder/sh010_comp_v002.mp4", fileName: "sh010_comp_v002.mp4", fps: 24, duration: 3.375, frameCount: 81, width: 1920, height: 1080, notes: "Revised camera move. Fixed comp integration. Added lens flares.", approvalStatus: ApprovalStatus.PENDING_REVIEW, isClientVisible: true, isLatest: true, }, }); } // ── Sample Comments ────────────────────────────────── const c1 = await db.comment.create({ data: { versionId: latestVersion.id, authorId: supervisor.id, frameNumber: 22, timestamp: 22 / 24, text: "The tracking slips on frame 22 — the station feels like it's drifting slightly to the left. Can you lock this down?", }, }); await db.commentReply.create({ data: { commentId: c1.id, authorId: artist.id, text: "Got it — I'll re-track from scratch using the new markers. Should have a fix by EOD.", }, }); await db.comment.create({ data: { versionId: latestVersion.id, authorId: clientUser.id, frameNumber: 55, timestamp: 55 / 24, text: "Love the lens flare here! Can we make it 20% brighter and hold it two frames longer?", }, }); await db.comment.create({ data: { versionId: latestVersion.id, authorId: supervisor.id, frameNumber: 70, timestamp: 70 / 24, text: "Edge matte at the bottom — there's a clean line around the station hull. Needs feathering.", isResolved: true, }, }); // ── Assets ──────────────────────────────────────────── const assetSpaceship = await db.asset.upsert({ where: { projectId_assetCode: { projectId: project.id, assetCode: "SHIP-01" } }, update: {}, create: { projectId: project.id, assetCode: "SHIP-01", name: "Hero Spaceship", description: "Main hero vehicle — used in SH010, SH050.", status: ShotStatus.IN_PROGRESS, priority: ShotPriority.HIGH, leadId: supervisor.id, dueDate: new Date("2025-07-01"), }, }); const assetStation = await db.asset.upsert({ where: { projectId_assetCode: { projectId: project.id, assetCode: "STATION-01" } }, update: {}, create: { projectId: project.id, assetCode: "STATION-01", name: "Space Station", description: "BG environment asset for SH010.", status: ShotStatus.COMPLETE, priority: ShotPriority.NORMAL, leadId: supervisor.id, dueDate: new Date("2025-06-01"), }, }); // ── Shot & Asset Tasks ──────────────────────────────── // SH010 tasks (SH010 Comp was already created above for version attachment) await upsertTask({ title: "SH010 Track", type: TaskType.TRACK, status: TaskStatus.DONE, priority: ShotPriority.HIGH, sortOrder: 0, projectId: project.id, shotId: firstShot.id, assignedArtistId: artist.id, dueDate: new Date("2025-04-01"), estimatedHours: 8 }); await upsertTask({ title: "SH010 Roto", type: TaskType.ROTO, status: TaskStatus.IN_PROGRESS, priority: ShotPriority.HIGH, sortOrder: 1, projectId: project.id, shotId: firstShot.id, assignedArtistId: artist.id, dueDate: new Date("2025-05-20"), estimatedHours: 16 }); // SH010 Comp is sh010CompTask (created earlier) // SH020 tasks await upsertTask({ title: "SH020 Track", type: TaskType.TRACK, status: TaskStatus.DONE, priority: ShotPriority.HIGH, sortOrder: 0, projectId: project.id, shotId: sh020.id, assignedArtistId: artist.id, dueDate: new Date("2025-04-15"), estimatedHours: 4 }); await upsertTask({ title: "SH020 Comp", type: TaskType.COMP, status: TaskStatus.INTERNAL_REVIEW, priority: ShotPriority.HIGH, sortOrder: 1, projectId: project.id, shotId: sh020.id, assignedArtistId: artist.id, dueDate: new Date("2025-05-25"), estimatedHours: 20 }); // SH035 tasks await upsertTask({ title: "SH035 FX Explosion", type: TaskType.FX, status: TaskStatus.CHANGES, priority: ShotPriority.URGENT, sortOrder: 0, projectId: project.id, shotId: sh035.id, assignedArtistId: artist.id, dueDate: new Date("2025-05-10"), estimatedHours: 32 }); await upsertTask({ title: "SH035 Comp", type: TaskType.COMP, status: TaskStatus.TODO, priority: ShotPriority.URGENT, sortOrder: 1, projectId: project.id, shotId: sh035.id, dueDate: new Date("2025-06-01"), estimatedHours: 16 }); // SHIP-01 asset tasks await upsertTask({ title: "SHIP-01 Model", type: TaskType.MODEL, status: TaskStatus.DONE, priority: ShotPriority.HIGH, sortOrder: 0, projectId: project.id, assetId: assetSpaceship.id, assignedArtistId: artist.id, dueDate: new Date("2025-03-01"), estimatedHours: 40 }); await upsertTask({ title: "SHIP-01 Texture", type: TaskType.TEXTURE, status: TaskStatus.IN_PROGRESS, priority: ShotPriority.HIGH, sortOrder: 1, projectId: project.id, assetId: assetSpaceship.id, assignedArtistId: artist.id, dueDate: new Date("2025-06-01"), estimatedHours: 24 }); await upsertTask({ title: "SHIP-01 Rig", type: TaskType.RIG, status: TaskStatus.TODO, priority: ShotPriority.NORMAL, sortOrder: 2, projectId: project.id, assetId: assetSpaceship.id, dueDate: new Date("2025-07-01"), estimatedHours: 20 }); await upsertTask({ title: "SHIP-01 Lookdev", type: TaskType.LOOKDEV, status: TaskStatus.TODO, priority: ShotPriority.NORMAL, sortOrder: 3, projectId: project.id, assetId: assetSpaceship.id, dueDate: new Date("2025-07-15"), estimatedHours: 16 }); // STATION-01 asset tasks await upsertTask({ title: "STATION-01 Model", type: TaskType.MODEL, status: TaskStatus.DONE, priority: ShotPriority.NORMAL, sortOrder: 0, projectId: project.id, assetId: assetStation.id, assignedArtistId: artist.id, dueDate: new Date("2025-02-15"), estimatedHours: 60 }); await upsertTask({ title: "STATION-01 Lookdev", type: TaskType.LOOKDEV, status: TaskStatus.DONE, priority: ShotPriority.NORMAL, sortOrder: 1, projectId: project.id, assetId: assetStation.id, assignedArtistId: artist.id, dueDate: new Date("2025-03-15"), estimatedHours: 20 }); console.log("āœ… Seed complete!"); console.log("\nšŸ“‹ Demo credentials:"); console.log(" Admin: admin@vfxreview.local / admin123"); console.log(" Producer: producer@vfxreview.local / producer123"); console.log(" Supervisor: supervisor@vfxreview.local / supervisor123"); console.log(" Artist: artist@vfxreview.local / artist123"); console.log(" Client: client@studio.com / client123"); } main() .catch((e) => { console.error(e); process.exit(1); }) .finally(async () => { await db.$disconnect(); });