Initial commit
This commit is contained in:
@@ -0,0 +1,347 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Role" AS ENUM ('ADMIN', 'PRODUCER', 'SUPERVISOR', 'ARTIST', 'CLIENT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ProjectStatus" AS ENUM ('ACTIVE', 'ON_HOLD', 'COMPLETED', 'ARCHIVED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ShotStatus" AS ENUM ('WAITING', 'IN_PROGRESS', 'INTERNAL_REVIEW', 'CLIENT_REVIEW', 'REVISIONS', 'APPROVED', 'FINAL');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ShotPriority" AS ENUM ('LOW', 'NORMAL', 'HIGH', 'URGENT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ApprovalStatus" AS ENUM ('PENDING_REVIEW', 'APPROVED', 'REJECTED', 'NEEDS_CHANGES');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ReviewStatus" AS ENUM ('PENDING', 'INTERNAL_APPROVED', 'CLIENT_APPROVED', 'NEEDS_CHANGES', 'FINAL_APPROVED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "NotificationType" AS ENUM ('VERSION_UPLOADED', 'FEEDBACK_ADDED', 'SHOT_APPROVED', 'SHOT_REJECTED', 'COMMENT_REPLY', 'MENTION', 'REVISION_REQUESTED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT NOT NULL,
|
||||
"emailVerified" TIMESTAMP(3),
|
||||
"image" TEXT,
|
||||
"passwordHash" TEXT,
|
||||
"role" "Role" NOT NULL DEFAULT 'ARTIST',
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "accounts" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"providerAccountId" TEXT NOT NULL,
|
||||
"refresh_token" TEXT,
|
||||
"access_token" TEXT,
|
||||
"expires_at" INTEGER,
|
||||
"token_type" TEXT,
|
||||
"scope" TEXT,
|
||||
"id_token" TEXT,
|
||||
"session_state" TEXT,
|
||||
|
||||
CONSTRAINT "accounts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sessions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionToken" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"expires" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "sessions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "verification_tokens" (
|
||||
"identifier" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expires" TIMESTAMP(3) NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "clients" (
|
||||
"id" TEXT NOT NULL,
|
||||
"company" TEXT NOT NULL,
|
||||
"contactPerson" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"phone" TEXT,
|
||||
"notes" TEXT,
|
||||
"logoUrl" TEXT,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "clients_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "client_access" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"clientId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "client_access_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "projects" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"status" "ProjectStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"dueDate" TIMESTAMP(3),
|
||||
"startDate" TIMESTAMP(3),
|
||||
"clientId" TEXT NOT NULL,
|
||||
"producerId" TEXT,
|
||||
"supervisorId" TEXT,
|
||||
"slackWebhook" TEXT,
|
||||
"slackChannel" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "projects_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "shots" (
|
||||
"id" TEXT NOT NULL,
|
||||
"shotCode" TEXT NOT NULL,
|
||||
"sequence" TEXT,
|
||||
"description" TEXT,
|
||||
"status" "ShotStatus" NOT NULL DEFAULT 'WAITING',
|
||||
"priority" "ShotPriority" NOT NULL DEFAULT 'NORMAL',
|
||||
"artistId" TEXT,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"frameStart" INTEGER,
|
||||
"frameEnd" INTEGER,
|
||||
"fps" DOUBLE PRECISION NOT NULL DEFAULT 24,
|
||||
"dueDate" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "shots_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "versions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"versionNumber" INTEGER NOT NULL,
|
||||
"shotId" TEXT NOT NULL,
|
||||
"artistId" TEXT,
|
||||
"fileUrl" TEXT NOT NULL,
|
||||
"fileName" TEXT NOT NULL,
|
||||
"fileSize" BIGINT,
|
||||
"mimeType" TEXT,
|
||||
"thumbnailUrl" TEXT,
|
||||
"posterUrl" TEXT,
|
||||
"proxyUrl" TEXT,
|
||||
"fps" DOUBLE PRECISION NOT NULL DEFAULT 24,
|
||||
"duration" DOUBLE PRECISION,
|
||||
"frameCount" INTEGER,
|
||||
"width" INTEGER,
|
||||
"height" INTEGER,
|
||||
"notes" TEXT,
|
||||
"approvalStatus" "ApprovalStatus" NOT NULL DEFAULT 'PENDING_REVIEW',
|
||||
"reviewStatus" "ReviewStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"isLatest" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "versions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "comments" (
|
||||
"id" TEXT NOT NULL,
|
||||
"versionId" TEXT NOT NULL,
|
||||
"authorId" TEXT NOT NULL,
|
||||
"frameNumber" INTEGER NOT NULL,
|
||||
"timestamp" DOUBLE PRECISION NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
"isResolved" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "comments_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "comment_replies" (
|
||||
"id" TEXT NOT NULL,
|
||||
"commentId" TEXT NOT NULL,
|
||||
"authorId" TEXT NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "comment_replies_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "annotations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"versionId" TEXT NOT NULL,
|
||||
"commentId" TEXT,
|
||||
"authorId" TEXT NOT NULL,
|
||||
"frameNumber" INTEGER NOT NULL,
|
||||
"drawingData" JSONB NOT NULL,
|
||||
"color" TEXT NOT NULL DEFAULT '#ef4444',
|
||||
"isVisible" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "annotations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "approvals" (
|
||||
"id" TEXT NOT NULL,
|
||||
"versionId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"status" "ApprovalStatus" NOT NULL,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "approvals_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "notifications" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"type" "NotificationType" NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"message" TEXT NOT NULL,
|
||||
"data" JSONB,
|
||||
"isRead" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "notifications_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "review_sessions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"label" TEXT,
|
||||
"email" TEXT,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"accessCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "review_sessions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "accounts_provider_providerAccountId_key" ON "accounts"("provider", "providerAccountId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sessions_sessionToken_key" ON "sessions"("sessionToken");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "verification_tokens_token_key" ON "verification_tokens"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "verification_tokens_identifier_token_key" ON "verification_tokens"("identifier", "token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "clients_email_key" ON "clients"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "client_access_userId_clientId_key" ON "client_access"("userId", "clientId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "projects_code_key" ON "projects"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "shots_projectId_shotCode_key" ON "shots"("projectId", "shotCode");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "versions_shotId_versionNumber_key" ON "versions"("shotId", "versionNumber");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "review_sessions_token_key" ON "review_sessions"("token");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "client_access" ADD CONSTRAINT "client_access_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "client_access" ADD CONSTRAINT "client_access_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "clients"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "clients"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_producerId_fkey" FOREIGN KEY ("producerId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_supervisorId_fkey" FOREIGN KEY ("supervisorId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "shots" ADD CONSTRAINT "shots_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "shots" ADD CONSTRAINT "shots_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "versions" ADD CONSTRAINT "versions_shotId_fkey" FOREIGN KEY ("shotId") REFERENCES "shots"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "versions" ADD CONSTRAINT "versions_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comments" ADD CONSTRAINT "comments_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "versions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comments" ADD CONSTRAINT "comments_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comment_replies" ADD CONSTRAINT "comment_replies_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "comments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comment_replies" ADD CONSTRAINT "comment_replies_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotations" ADD CONSTRAINT "annotations_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "versions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotations" ADD CONSTRAINT "annotations_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "comments"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotations" ADD CONSTRAINT "annotations_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "approvals" ADD CONSTRAINT "approvals_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "versions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "approvals" ADD CONSTRAINT "approvals_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "notifications" ADD CONSTRAINT "notifications_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "review_sessions" ADD CONSTRAINT "review_sessions_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,107 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TaskStatus" AS ENUM ('TODO', 'IN_PROGRESS', 'INTERNAL_REVIEW', 'CLIENT_REVIEW', 'CHANGES', 'DONE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TaskType" AS ENUM ('TRACK', 'ROTO', 'KEY', 'COMP', 'FX', 'LIGHTING', 'RENDER', 'ANIMATION', 'MODEL', 'TEXTURE', 'RIG', 'LOOKDEV', 'GENERAL');
|
||||
|
||||
-- AlterEnum
|
||||
-- This migration adds more than one value to an enum.
|
||||
-- With PostgreSQL versions 11 and earlier, this is not possible
|
||||
-- in a single migration. This can be worked around by creating
|
||||
-- multiple migrations, each migration adding only one value to
|
||||
-- the enum.
|
||||
|
||||
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'TASK_ASSIGNED';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'TASK_OVERDUE';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'TASK_APPROVED';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'TASK_CHANGES_REQUESTED';
|
||||
ALTER TYPE "NotificationType" ADD VALUE 'TASK_READY_FOR_REVIEW';
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "projects" DROP CONSTRAINT "projects_clientId_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "versions_shotId_versionNumber_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "projects" ALTER COLUMN "clientId" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "versions" ADD COLUMN "isClientVisible" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "sharedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "sharedById" TEXT,
|
||||
ADD COLUMN "taskId" TEXT,
|
||||
ALTER COLUMN "shotId" DROP NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "assets" (
|
||||
"id" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"assetCode" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"status" "ShotStatus" NOT NULL DEFAULT 'WAITING',
|
||||
"priority" "ShotPriority" NOT NULL DEFAULT 'NORMAL',
|
||||
"leadId" TEXT,
|
||||
"dueDate" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "assets_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "tasks" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"type" "TaskType" NOT NULL DEFAULT 'GENERAL',
|
||||
"status" "TaskStatus" NOT NULL DEFAULT 'TODO',
|
||||
"priority" "ShotPriority" NOT NULL DEFAULT 'NORMAL',
|
||||
"dueDate" TIMESTAMP(3),
|
||||
"estimatedHours" DOUBLE PRECISION,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"shotId" TEXT,
|
||||
"assetId" TEXT,
|
||||
"assignedArtistId" TEXT,
|
||||
"createdById" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "tasks_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "assets_projectId_assetCode_key" ON "assets"("projectId", "assetCode");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "clients"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "versions" ADD CONSTRAINT "versions_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "tasks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "versions" ADD CONSTRAINT "versions_sharedById_fkey" FOREIGN KEY ("sharedById") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "assets" ADD CONSTRAINT "assets_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "assets" ADD CONSTRAINT "assets_leadId_fkey" FOREIGN KEY ("leadId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_shotId_fkey" FOREIGN KEY ("shotId") REFERENCES "shots"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_assignedArtistId_fkey" FOREIGN KEY ("assignedArtistId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,29 @@
|
||||
-- Migration: Task-First Workflow Refactor
|
||||
-- 1. Delete old shot-based versions (versions linked to shots, not tasks)
|
||||
DELETE FROM "versions" WHERE "shotId" IS NOT NULL AND "taskId" IS NULL;
|
||||
|
||||
-- 2. Drop defaults before type changes (they hold a reference to the enum)
|
||||
ALTER TABLE "shots" ALTER COLUMN "status" DROP DEFAULT;
|
||||
ALTER TABLE "assets" ALTER COLUMN "status" DROP DEFAULT;
|
||||
|
||||
-- 3. Cast status columns to text so we can rename values
|
||||
ALTER TABLE "shots" ALTER COLUMN "status" TYPE TEXT;
|
||||
ALTER TABLE "assets" ALTER COLUMN "status" TYPE TEXT;
|
||||
|
||||
-- 4. Rename old enum values in the data
|
||||
UPDATE "shots" SET "status" = 'IN_REVIEW' WHERE "status" IN ('INTERNAL_REVIEW', 'CLIENT_REVIEW');
|
||||
UPDATE "shots" SET "status" = 'COMPLETE' WHERE "status" IN ('APPROVED', 'FINAL');
|
||||
UPDATE "assets" SET "status" = 'IN_REVIEW' WHERE "status" IN ('INTERNAL_REVIEW', 'CLIENT_REVIEW');
|
||||
UPDATE "assets" SET "status" = 'COMPLETE' WHERE "status" IN ('APPROVED', 'FINAL');
|
||||
|
||||
-- 5. Drop old enum type (no dependents remain)
|
||||
DROP TYPE "ShotStatus";
|
||||
|
||||
-- 6. Create new enum type with 5 values
|
||||
CREATE TYPE "ShotStatus" AS ENUM ('WAITING', 'IN_PROGRESS', 'IN_REVIEW', 'REVISIONS', 'COMPLETE');
|
||||
|
||||
-- 7. Convert columns back to the new enum and restore defaults
|
||||
ALTER TABLE "shots" ALTER COLUMN "status" TYPE "ShotStatus" USING "status"::"ShotStatus";
|
||||
ALTER TABLE "shots" ALTER COLUMN "status" SET DEFAULT 'WAITING';
|
||||
ALTER TABLE "assets" ALTER COLUMN "status" TYPE "ShotStatus" USING "status"::"ShotStatus";
|
||||
ALTER TABLE "assets" ALTER COLUMN "status" SET DEFAULT 'WAITING';
|
||||
@@ -0,0 +1,11 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ProjectType" AS ENUM ('STANDARD', 'EPISODIC');
|
||||
|
||||
-- AlterTable projects: add showId and projectType
|
||||
ALTER TABLE "projects" ADD COLUMN "showId" TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE "projects" ADD COLUMN "projectType" "ProjectType" NOT NULL DEFAULT 'STANDARD';
|
||||
|
||||
-- AlterTable shots: add scene, episode, shotNumber
|
||||
ALTER TABLE "shots" ADD COLUMN "scene" TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE "shots" ADD COLUMN "episode" TEXT;
|
||||
ALTER TABLE "shots" ADD COLUMN "shotNumber" INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "shots" ADD COLUMN "thumbnailUrl" TEXT;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "mustChangePassword" BOOLEAN NOT NULL DEFAULT false;
|
||||
@@ -0,0 +1,44 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "annotations" DROP CONSTRAINT "annotations_authorId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "approvals" DROP CONSTRAINT "approvals_userId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "comment_replies" DROP CONSTRAINT "comment_replies_authorId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "comments" DROP CONSTRAINT "comments_authorId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "tasks" DROP CONSTRAINT "tasks_createdById_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "annotations" ALTER COLUMN "authorId" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "approvals" ALTER COLUMN "userId" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "comment_replies" ALTER COLUMN "authorId" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "comments" ALTER COLUMN "authorId" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "tasks" ALTER COLUMN "createdById" DROP NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comments" ADD CONSTRAINT "comments_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "comment_replies" ADD CONSTRAINT "comment_replies_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "annotations" ADD CONSTRAINT "annotations_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "approvals" ADD CONSTRAINT "approvals_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,10 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "tasks" ADD COLUMN "scheduleNotes" TEXT,
|
||||
ADD COLUMN "scheduledEndDate" TIMESTAMP(3),
|
||||
ADD COLUMN "scheduledStartDate" TIMESTAMP(3);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "tasks_scheduledStartDate_idx" ON "tasks"("scheduledStartDate");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "tasks_scheduledEndDate_idx" ON "tasks"("scheduledEndDate");
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
@@ -0,0 +1,472 @@
|
||||
// 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?
|
||||
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")
|
||||
}
|
||||
+373
@@ -0,0 +1,373 @@
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user