From 36ee62dacc7e1dca06e8f62c81cd845994b71e45 Mon Sep 17 00:00:00 2001 From: twotalesanimation <80506065+twotalesanimation@users.noreply.github.com> Date: Fri, 29 May 2026 10:40:28 +0200 Subject: [PATCH] test --- app/api/ext/shots/route.ts | 78 ++++++++++++++++----- import-shots.ps1 | 135 +++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 17 deletions(-) create mode 100644 import-shots.ps1 diff --git a/app/api/ext/shots/route.ts b/app/api/ext/shots/route.ts index 4de6572..0ae0340 100644 --- a/app/api/ext/shots/route.ts +++ b/app/api/ext/shots/route.ts @@ -38,6 +38,8 @@ const createShotSchema = z.object({ dueDate: z.string().optional(), thumbnailUrl: z.string().url().optional(), shotGroupName: z.string().max(100).optional(), + // Optional explicit shot code — bypasses auto-generation + shotCode: z.string().max(100).optional(), }); // ── POST /api/ext/shots ─────────────────────────────────────────────────────── @@ -109,26 +111,46 @@ export async function POST(req: NextRequest) { ); } - // Auto-increment shot number within scene (+ episode for episodic) - const scopeWhere = { - projectId: parsed.projectId, - scene, - ...(project.projectType === "EPISODIC" ? { episode } : {}), - }; + // Use explicit shotCode if provided, otherwise auto-increment + let shotCode: string; + let shotNumber: number; - const maxShot = await db.shot.findFirst({ - where: scopeWhere, - orderBy: { shotNumber: "desc" }, - select: { shotNumber: true }, - }); + if (parsed.shotCode) { + // Check uniqueness manually since we bypass auto-generation + const existing = await db.shot.findUnique({ + where: { projectId_shotCode: { projectId: parsed.projectId, shotCode: parsed.shotCode } }, + select: { id: true }, + }); + if (existing) { + return NextResponse.json( + { error: "A shot with that code already exists in this project", shotCode: parsed.shotCode }, + { status: 409 } + ); + } + shotCode = parsed.shotCode; + shotNumber = 0; + } else { + // Auto-increment shot number within scene (+ episode for episodic) + const scopeWhere = { + projectId: parsed.projectId, + scene, + ...(project.projectType === "EPISODIC" ? { episode } : {}), + }; - const shotNumber = (maxShot?.shotNumber ?? 0) + 10; - const paddedNumber = shotNumber.toString().padStart(4, "0"); + const maxShot = await db.shot.findFirst({ + where: scopeWhere, + orderBy: { shotNumber: "desc" }, + select: { shotNumber: true }, + }); - const shotCode = - project.projectType === "EPISODIC" && episode - ? `${project.showId}_${episode}_${scene}_${paddedNumber}` - : `${project.showId}_${scene}_${paddedNumber}`; + shotNumber = (maxShot?.shotNumber ?? 0) + 10; + const paddedNumber = shotNumber.toString().padStart(4, "0"); + + shotCode = + project.projectType === "EPISODIC" && episode + ? `${project.showId}_${episode}_${scene}_${paddedNumber}` + : `${project.showId}_${scene}_${paddedNumber}`; + } // Upload thumbnail if provided as a file let thumbnailUrl = parsed.thumbnailUrl; @@ -214,3 +236,25 @@ export async function POST(req: NextRequest) { return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } } + +// ── GET /api/ext/shots?projectId=... ───────────────────────────────────────── + +export async function GET(req: NextRequest) { + if (!isAuthorized(req)) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { searchParams } = new URL(req.url); + const projectId = searchParams.get("projectId"); + if (!projectId) { + return NextResponse.json({ error: "projectId is required" }, { status: 400 }); + } + + const shots = await db.shot.findMany({ + where: { projectId }, + orderBy: { createdAt: "asc" }, + select: { id: true, shotCode: true, scene: true, episode: true, status: true }, + }); + + return NextResponse.json({ shots }); +} diff --git a/import-shots.ps1 b/import-shots.ps1 new file mode 100644 index 0000000..93febc3 --- /dev/null +++ b/import-shots.ps1 @@ -0,0 +1,135 @@ +# ============================================================== +# import-shots.ps1 +# +# Place this script inside a folder whose SIBLINGS are the +# render directories you want to scan, e.g.: +# +# /renders/ +# import-shots.ps1 <- this file +# UNG_103_010_010_BG01_TT_v001/ +# UNG_103_010_010_FG01_TT_v001/ +# UNG_103_020_010_BG01_TT_v001/ +# +# Folder naming convention expected: +# {showId}_{episode}_{scene}_{shot}_{...anything...} +# +# The first four underscore-delimited segments form the shot +# code, e.g. UNG_103_010_010. +# ============================================================== + +# -- CONFIG ---------------------------------------------------- +$BaseUrl = "https://review.twotalesvfx.com" +$ApiKey = "am3O0PWUtqMJkAqsZ+bO7lho4cxQItxgukF6FHteAx4=" +$ProjectId = "cmp6l5mzq0001ua0gz07bk72f" +# -------------------------------------------------------------- + +$ErrorActionPreference = "Stop" + +$headers = @{ + "Authorization" = "Bearer $ApiKey" + "Content-Type" = "application/json" +} + +# -- 1. Fetch existing shots for the project ------------------- +Write-Host "" +Write-Host "Fetching existing shots for project $ProjectId ..." -ForegroundColor Cyan + +$existingUrl = "$BaseUrl/api/ext/shots?projectId=$ProjectId" +try { + $response = Invoke-RestMethod -Uri $existingUrl -Method GET -Headers $headers + $existingCodes = [System.Collections.Generic.HashSet[string]]($response.shots | ForEach-Object { $_.shotCode }) + Write-Host " Found $($existingCodes.Count) existing shot(s)." -ForegroundColor DarkGray +} catch { + Write-Error "Failed to fetch existing shots: $_" + exit 1 +} + +# -- 2. Scan sibling directories for EXR files ---------------- +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$parentDir = Split-Path -Parent $scriptDir +$candidates = Get-ChildItem -Path $parentDir -Directory + +Write-Host "" +Write-Host "Scanning '$parentDir' for directories containing EXR files ..." -ForegroundColor Cyan + +$shotMap = [ordered]@{} + +foreach ($dir in $candidates) { + if ($dir.FullName -eq $scriptDir) { continue } + + $hasExr = Get-ChildItem -Path $dir.FullName -Filter "*.exr" -Recurse -ErrorAction SilentlyContinue | + Select-Object -First 1 + + if (-not $hasExr) { continue } + + $parts = $dir.Name -split "_" + if ($parts.Count -lt 4) { + Write-Warning " Skipping '$($dir.Name)' - fewer than 4 underscore-delimited segments." + continue + } + + $shotCode = "$($parts[0])_$($parts[1])_$($parts[2])_$($parts[3])" + $episode = $parts[1] + $scene = $parts[2] + + if (-not $shotMap.Contains($shotCode)) { + $shotMap[$shotCode] = @{ Episode = $episode; Scene = $scene } + Write-Host " Found EXR dir: $($dir.Name) -> shot code: $shotCode" -ForegroundColor DarkGray + } +} + +if ($shotMap.Count -eq 0) { + Write-Host "" + Write-Host "No directories containing EXR files found. Nothing to import." -ForegroundColor Yellow + exit 0 +} + +Write-Host "" +Write-Host "Unique shot codes to process: $($shotMap.Count)" -ForegroundColor Cyan + +# -- 3. Create shots, skipping existing ones ------------------ +$created = 0 +$skipped = 0 +$failed = 0 + +foreach ($shotCode in $shotMap.Keys) { + $info = $shotMap[$shotCode] + + if ($existingCodes.Contains($shotCode)) { + Write-Host " [SKIP] $shotCode (already exists)" -ForegroundColor DarkGray + $skipped++ + continue + } + + $body = @{ + projectId = $ProjectId + scene = $info.Scene + episode = $info.Episode + shotCode = $shotCode + } | ConvertTo-Json + + try { + $result = Invoke-RestMethod -Uri "$BaseUrl/api/ext/shots" -Method POST -Headers $headers -Body $body + Write-Host " [CREATED] $shotCode (id: $($result.shot.id))" -ForegroundColor Green + $created++ + } catch { + $statusCode = $null + if ($_.Exception.Response) { $statusCode = [int]$_.Exception.Response.StatusCode } + if ($statusCode -eq 409) { + Write-Host " [SKIP] $shotCode (conflict - already exists)" -ForegroundColor DarkGray + $skipped++ + } else { + Write-Host " [ERROR] $shotCode - $($_.Exception.Message)" -ForegroundColor Red + $failed++ + } + } +} + +# -- 4. Summary ----------------------------------------------- +Write-Host "" +Write-Host "Done." -ForegroundColor Cyan +Write-Host " Created : $created" -ForegroundColor Green +Write-Host " Skipped : $skipped" -ForegroundColor DarkGray +if ($failed -gt 0) { + Write-Host " Failed : $failed" -ForegroundColor Red +} \ No newline at end of file