Files
vfxreview/prisma/seed.ts
T
twotalesanimation 0fbe856dce Initial commit
2026-05-19 22:20:29 +02:00

374 lines
14 KiB
TypeScript

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();
});