@@ -12,7 +12,7 @@ export default function ProjectsPage() {
|
|||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [showNew, setShowNew] = useState(false);
|
const [showNew, setShowNew] = useState(false);
|
||||||
|
|
||||||
const { data, isLoading } = useQuery({
|
const { data, isLoading, isError } = useQuery({
|
||||||
queryKey: ["projects", search],
|
queryKey: ["projects", search],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`/api/projects?q=${encodeURIComponent(search)}`);
|
const res = await fetch(`/api/projects?q=${encodeURIComponent(search)}`);
|
||||||
@@ -80,6 +80,11 @@ export default function ProjectsPage() {
|
|||||||
<div className="flex justify-center py-12">
|
<div className="flex justify-center py-12">
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
|
) : isError ? (
|
||||||
|
<div className="text-center py-12 text-muted-foreground">
|
||||||
|
<p>Could not load projects.</p>
|
||||||
|
<p className="text-xs mt-2">Check server logs and database connectivity, then refresh.</p>
|
||||||
|
</div>
|
||||||
) : projects.length === 0 ? (
|
) : projects.length === 0 ? (
|
||||||
<div className="text-center py-12 text-muted-foreground">
|
<div className="text-center py-12 text-muted-foreground">
|
||||||
<p>No projects found.</p>
|
<p>No projects found.</p>
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ import { z } from "zod";
|
|||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
function isPrismaTableOrColumnMissingError(error: unknown): boolean {
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
return error.code === "P2021" || error.code === "P2022";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const exportManifestSchema = z.object({
|
const exportManifestSchema = z.object({
|
||||||
shotCode: z.string().min(1),
|
shotCode: z.string().min(1),
|
||||||
projectCode: z.string().min(1),
|
projectCode: z.string().min(1),
|
||||||
@@ -152,23 +159,40 @@ export async function createRenderExport(input: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function listExportsForQueue() {
|
export async function listExportsForQueue() {
|
||||||
return db.export.findMany({
|
try {
|
||||||
where: { status: { notIn: ["SUPERSEDED", "ARCHIVED", "CANCELLED", "DELIVERED"] } },
|
return await db.export.findMany({
|
||||||
orderBy: [{ createdAt: "desc" }],
|
where: { status: { notIn: ["SUPERSEDED", "ARCHIVED", "CANCELLED", "DELIVERED"] } },
|
||||||
include: {
|
orderBy: [{ createdAt: "desc" }],
|
||||||
shot: { select: { shotCode: true, shotVersion: true, project: { select: { code: true } } } },
|
include: {
|
||||||
renderJobs: { orderBy: [{ createdAt: "desc" }], take: 1 },
|
shot: { select: { shotCode: true, shotVersion: true, project: { select: { code: true } } } },
|
||||||
},
|
renderJobs: { orderBy: [{ createdAt: "desc" }], take: 1 },
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Render pipeline tables may not exist yet if Phase 1 migrations have not been applied.
|
||||||
|
if (isPrismaTableOrColumnMissingError(error)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLatestExportForShot(shotId: string) {
|
export async function getLatestExportForShot(shotId: string) {
|
||||||
return db.export.findFirst({
|
try {
|
||||||
where: { shotId },
|
return await db.export.findFirst({
|
||||||
orderBy: [{ createdAt: "desc" }],
|
where: { shotId },
|
||||||
include: {
|
orderBy: [{ createdAt: "desc" }],
|
||||||
shot: { select: { shotCode: true, project: { select: { code: true } } } },
|
include: {
|
||||||
renderJobs: { orderBy: [{ createdAt: "desc" }], take: 1 },
|
shot: { select: { shotCode: true, project: { select: { code: true } } } },
|
||||||
},
|
renderJobs: { orderBy: [{ createdAt: "desc" }], take: 1 },
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (isPrismaTableOrColumnMissingError(error)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE "ExportStatus" AS ENUM (
|
||||||
|
'QUEUED',
|
||||||
|
'RENDERING',
|
||||||
|
'RENDER_FAILED',
|
||||||
|
'VALIDATING',
|
||||||
|
'VALIDATION_FAILED',
|
||||||
|
'GENERATING_PREVIEW',
|
||||||
|
'PREVIEW_FAILED',
|
||||||
|
'READY_FOR_QC',
|
||||||
|
'QC_FAILED',
|
||||||
|
'READY_FOR_DELIVERY',
|
||||||
|
'PACKAGED',
|
||||||
|
'DELIVERED',
|
||||||
|
'SUPERSEDED',
|
||||||
|
'ARCHIVED',
|
||||||
|
'CANCELLED'
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE "RenderJobStatus" AS ENUM (
|
||||||
|
'QUEUED',
|
||||||
|
'CLAIMED',
|
||||||
|
'RUNNING',
|
||||||
|
'COMPLETED',
|
||||||
|
'FAILED',
|
||||||
|
'CANCELLED',
|
||||||
|
'EXPIRED'
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE "RenderJobType" AS ENUM (
|
||||||
|
'AE_RENDER',
|
||||||
|
'PREVIEW_ONLY',
|
||||||
|
'DELIVERY_BUILD'
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE IF NOT EXISTS "exports" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"shotId" TEXT NOT NULL,
|
||||||
|
"projectId" TEXT NOT NULL,
|
||||||
|
"taskId" TEXT,
|
||||||
|
"versionId" TEXT,
|
||||||
|
"versionNumber" INTEGER NOT NULL,
|
||||||
|
"versionString" TEXT NOT NULL,
|
||||||
|
"status" "ExportStatus" NOT NULL DEFAULT 'QUEUED',
|
||||||
|
"statusChangedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"aepPath" TEXT NOT NULL,
|
||||||
|
"compName" TEXT NOT NULL,
|
||||||
|
"rendererType" TEXT NOT NULL,
|
||||||
|
"outputDir" TEXT NOT NULL,
|
||||||
|
"outputPattern" TEXT NOT NULL,
|
||||||
|
"frameStart" INTEGER NOT NULL,
|
||||||
|
"frameEnd" INTEGER NOT NULL,
|
||||||
|
"fps" DOUBLE PRECISION NOT NULL,
|
||||||
|
"width" INTEGER NOT NULL,
|
||||||
|
"height" INTEGER NOT NULL,
|
||||||
|
"colorspace" TEXT,
|
||||||
|
"deliveryMovPath" TEXT,
|
||||||
|
"previewMovKey" TEXT,
|
||||||
|
"thumbnailKey" TEXT,
|
||||||
|
"metadataKey" TEXT,
|
||||||
|
"exrFileCount" INTEGER,
|
||||||
|
"exrTotalBytes" BIGINT,
|
||||||
|
"checksum" TEXT,
|
||||||
|
"submittedById" TEXT,
|
||||||
|
"submittedByName" TEXT,
|
||||||
|
"supersededById" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "exports_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE IF NOT EXISTS "render_jobs" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"type" "RenderJobType" NOT NULL DEFAULT 'AE_RENDER',
|
||||||
|
"exportId" TEXT,
|
||||||
|
"deliveryId" TEXT,
|
||||||
|
"attempt" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"maxAttempts" INTEGER NOT NULL DEFAULT 3,
|
||||||
|
"status" "RenderJobStatus" NOT NULL DEFAULT 'QUEUED',
|
||||||
|
"priority" INTEGER NOT NULL DEFAULT 50,
|
||||||
|
"manifest" JSONB NOT NULL,
|
||||||
|
"machineId" TEXT,
|
||||||
|
"claimedAt" TIMESTAMP(3),
|
||||||
|
"leaseExpiresAt" TIMESTAMP(3),
|
||||||
|
"startedAt" TIMESTAMP(3),
|
||||||
|
"finishedAt" TIMESTAMP(3),
|
||||||
|
"progress" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"currentFrame" INTEGER,
|
||||||
|
"totalFrames" INTEGER,
|
||||||
|
"etaSeconds" INTEGER,
|
||||||
|
"exitCode" INTEGER,
|
||||||
|
"errorMessage" TEXT,
|
||||||
|
"logTail" TEXT,
|
||||||
|
"logFileKey" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "render_jobs_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE IF NOT EXISTS "export_events" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"exportId" TEXT NOT NULL,
|
||||||
|
"fromStatus" TEXT,
|
||||||
|
"toStatus" TEXT NOT NULL,
|
||||||
|
"actorType" TEXT NOT NULL,
|
||||||
|
"actorId" TEXT,
|
||||||
|
"note" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "export_events_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "exports_shotId_versionNumber_key" ON "exports"("shotId", "versionNumber");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX IF NOT EXISTS "exports_status_idx" ON "exports"("status");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX IF NOT EXISTS "exports_projectId_status_idx" ON "exports"("projectId", "status");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX IF NOT EXISTS "render_jobs_status_priority_createdAt_idx" ON "render_jobs"("status", "priority", "createdAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX IF NOT EXISTS "export_events_exportId_createdAt_idx" ON "export_events"("exportId", "createdAt");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "exports"
|
||||||
|
ADD CONSTRAINT "exports_shotId_fkey"
|
||||||
|
FOREIGN KEY ("shotId") REFERENCES "shots"("id")
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "render_jobs"
|
||||||
|
ADD CONSTRAINT "render_jobs_exportId_fkey"
|
||||||
|
FOREIGN KEY ("exportId") REFERENCES "exports"("id")
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "export_events"
|
||||||
|
ADD CONSTRAINT "export_events_exportId_fkey"
|
||||||
|
FOREIGN KEY ("exportId") REFERENCES "exports"("id")
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
Reference in New Issue
Block a user