22 KiB
Technical Architecture Report (Current State)
Date: 2026-07-31
Scope: Current implementation only (no redesign suggestions)
1. Database
Authoritative schema source: prisma/schema.prisma
Full Prisma Schema
The full schema is defined in prisma/schema.prisma.
Models related to requested domains
- Users/Auth
- User
- Account
- Session
- VerificationToken
- ClientAccess
- Projects/Episodes
- Project
- EpisodeDueDate
- Client
- Shots/Tasks/Versions/Reviews
- Shot
- ShotGroup
- Task
- Version
- Comment
- CommentReply
- Annotation
- Approval
- ReviewSession
- Files/Storage
- FootagePlate
- ShotReference
- SystemConfig
- Delivery/Export adjacent
- Shot fields: highResKey, highResFilename, exrOutput, shotVersion
- Version fields: fileUrl, fileName, proxyUrl, thumbnailUrl, posterUrl
Relationship summary
- Client 1:N Project
- Project 1:N Shot
- Project 1:N Task
- Project 1:N ReviewSession
- Project 1:N EpisodeDueDate
- Shot 1:N Task
- Shot 1:N Version
- Shot 1:N FootagePlate
- Shot 1:N ShotReference
- Task 1:N Version
- Version 1:N Comment
- Version 1:N Annotation
- Version 1:N Approval
- Comment 1:N CommentReply
- User has many assigned/created entities across shots, tasks, versions, comments, approvals
Existing status enums
From prisma/schema.prisma:
- ProjectStatus: ACTIVE, ON_HOLD, COMPLETED, ARCHIVED
- ShotStatus: WAITING, IN_PROGRESS, INTERNAL_REVIEW, READY_FOR_CLIENT, CLIENT_REVIEW, REVISIONS, COMPLETE
- ShotApprovalStatus: PENDING, INTERNALLY_APPROVED, CLIENT_APPROVED
- TaskStatus: TODO, IN_PROGRESS, INTERNAL_REVIEW, CLIENT_REVIEW, CHANGES, DONE
- ApprovalStatus: PENDING_REVIEW, APPROVED, REJECTED, NEEDS_CHANGES
- ReviewStatus: PENDING, INTERNAL_APPROVED, CLIENT_APPROVED, NEEDS_CHANGES, FINAL_APPROVED
Notes:
- There is no dedicated Delivery model.
- There is no dedicated Export model.
- Delivery/export state is represented by file pointers and shot/version metadata fields.
2. API
2.1 Shots
External shot APIs
-
GET /api/ext/projects
- URL: /api/ext/projects
- Method: GET
- Purpose: List projects for pipeline tools
- Request body: None
- Response: projects[] with id, name, code, showId, projectType, status, dates, _count
- Source: app/api/ext/projects/route.ts
-
GET /api/ext/projects/{projectCode}/episodes
- URL: /api/ext/projects/{projectCode}/episodes
- Method: GET
- Purpose: List distinct episodes and optionally shot payloads per episode
- Request body: None
- Response: project + episodes[]; optional shots[] includes exrOutput/timecodes
- Source: app/api/ext/projects/[projectCode]/episodes/route.ts
-
GET /api/ext/projects/{projectCode}/shots
- URL: /api/ext/projects/{projectCode}/shots
- Method: GET
- Purpose: List shots by project code with filters/pagination
- Request body: None
- Response: project + pagination + shots[]
- Source: app/api/ext/projects/[projectCode]/shots/route.ts
-
GET /api/ext/shots
- URL: /api/ext/shots
- Method: GET
- Purpose: Legacy listing by projectId
- Request body: None
- Response: shots[] + total
- Source: app/api/ext/shots/route.ts
-
POST /api/ext/shots
- URL: /api/ext/shots
- Method: POST
- Purpose: Create shot from external tool (JSON or multipart thumbnail)
- Request body:
- projectId, scene
- optional episode, description, artistId, priority, fps, frameStart, frameEnd, dueDate, thumbnailUrl, shotGroupName, shotCode
- optional thumbnail file (multipart)
- Response: created shot object
- Source: app/api/ext/shots/route.ts
-
GET /api/ext/shots/lookup
- URL: /api/ext/shots/lookup
- Method: GET
- Purpose: Canonical shot lookup by shotCode (+ optional projectCode)
- Request body: None
- Response: shot object with project, artist, tasks, latest version, exrOutput, shotVersion, source/seq timecodes
- Source: app/api/ext/shots/lookup/route.ts
-
GET /api/ext/shots/{shotId}
- URL: /api/ext/shots/{shotId}
- Method: GET
- Purpose: Shot detail by DB id, or byCode mode
- Request body: None
- Response: full shot detail with tasks/latest version/counts
- Source: app/api/ext/shots/[shotId]/route.ts
-
PATCH /api/ext/shots/{shotId}
- URL: /api/ext/shots/{shotId}
- Method: PATCH
- Purpose: Update mutable shot field(s) from pipeline tools
- Request body: shotVersion (v###) currently supported
- Response: success + updated shot id/shotVersion
- Source: app/api/ext/shots/[shotId]/route.ts
Internal shot APIs
-
GET /api/shots/{shotId}
- URL: /api/shots/{shotId}
- Method: GET
- Purpose: Internal dashboard shot detail payload
- Request body: None
- Response: shot + tasks + artists + permissions flags
- Source: app/api/shots/[shotId]/route.ts
-
GET /api/projects/{projectId}/episodes
- URL: /api/projects/{projectId}/episodes
- Method: GET
- Purpose: Internal distinct episode list for project
- Request body: None
- Response: episodes[]
- Source: app/api/projects/[projectId]/episodes/route.ts
2.2 Reviews
-
GET /api/review-sessions
- Purpose: list review sessions
-
POST /api/review-sessions
- Purpose: create review session token and portal link
-
DELETE /api/review-sessions
- Purpose: deactivate review session
-
Source: app/api/review-sessions/route.ts
-
POST /api/client/{token}/auth
- Purpose: review password check + unlock cookie
- Source: app/api/client/[token]/auth/route.ts
-
GET /api/client/{token}/project
- Purpose: client portal project payload (shared shots/versions)
- Source: app/api/client/[token]/project/route.ts
-
GET /api/client/{token}/versions/{versionId}
- Purpose: client review detail payload
- Source: app/api/client/[token]/versions/[versionId]/route.ts
-
POST /api/client/{token}/comment
- Purpose: client frame comment
- Request body: versionId, frameNumber, timestamp, text
- Response: created comment
- Source: app/api/client/[token]/comment/route.ts
-
POST /api/client/{token}/annotation
- Purpose: client annotation write
- Request body: versionId, frameNumber, drawingData, optional color
- Response: annotation
- Source: app/api/client/[token]/annotation/route.ts
-
POST /api/client/{token}/approve
- Purpose: shot-level approve/changes and legacy version-level approval
- Request body:
- shot mode: shotId + action
- version mode: versionId + status + notes
- Response: success
- Source: app/api/client/[token]/approve/route.ts
-
GET /api/versions/{versionId}/comments
- Purpose: internal comments read
- Source: app/api/versions/[versionId]/comments/route.ts
-
GET /api/versions/{versionId}/annotations
- Purpose: internal annotations read
- Source: app/api/versions/[versionId]/annotations/route.ts
-
GET /api/playlist
- Purpose: latest version per shot playlist
- Source: app/api/playlist/route.ts
2.3 File uploads
-
POST /api/upload
- Purpose: authenticated upload to Hetzner via app server
- Body: multipart file (+ type)
- Response: url, key
- Source: app/api/upload/route.ts
-
POST /api/upload/local
- Purpose: authenticated video upload path
- Body: multipart file
- Response: url, key
- Source: app/api/upload/local/route.ts
-
POST /api/upload/presign
- Purpose: direct browser->object-storage upload URL
- Body: fileName, contentType, optional folder
- Response: presignedUrl, key, url
- Source: app/api/upload/presign/route.ts
-
GET/POST /api/uploadthing
- Purpose: UploadThing route handler passthrough (if configured)
- Source: app/api/uploadthing/route.ts
-
POST /api/batch-upload/presign
- Purpose: presign high-res upload key
- Body: fileName
- Response: presignedUrl, key
- Source: app/api/batch-upload/presign/route.ts
-
POST /api/batch-upload/preview
- Purpose: classify upload actions before upload
- Body: projectId, fileNames[]
- Response: items[] with statuses (new-version, rename-and-upload, create-task, update-highres, etc)
- Source: app/api/batch-upload/preview/route.ts
-
POST /api/batch-upload/upload
- Purpose: commit highres key or create version records
- Body:
- update-highres: action, shotId, projectId, key, fileName
- version upload: action, shotId, projectId, file, task routing fields
- Response: success + action result
- Source: app/api/batch-upload/upload/route.ts
2.4 EXRs / Rendering / Delivery / Storage / Metadata
-
GET /api/files/{...key}
- Purpose: file serving, range requests, local/Hetzner routing
- Source: app/api/files/[...key]/route.ts
-
GET/POST /api/admin/migration
- Purpose: local uploads migration status and per-key migration to Hetzner
- Source: app/api/admin/migration/route.ts
-
POST/DELETE /api/storage-test
- Purpose: upload test object / delete test object
- Source: app/api/storage-test/route.ts
-
POST/DELETE /api/shots/{shotId}/highres
- Purpose: upload/remove high-res deliverable on shot
- Source: app/api/shots/[shotId]/highres/route.ts
-
GET /api/shots/{shotId}/highres/download
- Purpose: internal presigned download URL for high-res
- Source: app/api/shots/[shotId]/highres/download/route.ts
-
GET /api/client/{token}/shots/{shotId}/highres/download
- Purpose: client token-gated presigned high-res download URL
- Source: app/api/client/[token]/shots/[shotId]/highres/download/route.ts
-
GET/POST/DELETE /api/shots/{shotId}/references
- Purpose: shot reference image management
- Source: app/api/shots/[shotId]/references/route.ts
-
GET /api/storyboard/pdf
- Purpose: server-rendered printable storyboard HTML with metadata options
- Source: app/api/storyboard/pdf/route.ts
Notes:
- No dedicated REST endpoint that runs ffmpeg in this repository.
- No dedicated REST endpoint that performs EXR rendering on the server.
- No dedicated deliveries API namespace.
3. Storage
Primary storage abstraction: lib/storage.ts
Provider modes
- local
- uploadthing
- s3
- r2
- b2
- minio
Dedicated high-res object storage path
- Hetzner object storage helper methods are used for high-res and presigned direct uploads.
- Config source precedence:
- SystemConfig table keys
- env fallback
Folder/key layout in object storage (current code paths)
- videos/
- image/
- highres/
- storage-test/
Local storage
- LOCAL_UPLOAD_DIR (default ./uploads)
- Served via /api/files catch-all route
File naming conventions
- Key format: {folder}/{uuid}-{sanitized-file-name}
- Sanitization done by sanitizeFileName to avoid URL/signature problems in object keys
EXR locations
Within web app/runtime:
- EXR references are naming metadata (Shot.exrOutput) and file-serving pathing.
Within DCC tooling docs/scripts:
- AE/Nuke workflows target shared export roots and per-shot folders.
Preview locations
- Version media URLs stored in Version.fileUrl
- Accessed through app player routes and /api/files routing where applicable
Thumbnail locations
- Shot.thumbnailUrl
- Version.thumbnailUrl
- Can point to /api/files/{key} or external provider URL
Relevant sources:
4. Export Pipeline (Current)
AE panel
Documented in repo:
Implemented panel script also exists on the host AE installation (outside workspace), and behavior aligns with docs:
- Episodes/shots discovery via ext APIs
- Shot lookup API usage for metadata
- Overlay/slate essential property updates (burn-in style overlays)
- Queue EXR / Queue MP4 / Queue MOV render queue actions
- Queue EXR (review convention)
- Import exported EXR and import renders
- Pull picture lock using seq timecodes
- Increment shot version by PATCH call
- Delivery prep that copies/renames EXR files to delivery folder convention
External API endpoints used by DCC tools
- GET /api/ext/projects/{projectCode}/episodes
- GET /api/ext/projects/{projectCode}/shots
- GET /api/ext/shots/lookup
- PATCH /api/ext/shots/{shotId}
Existing render scripts and pipelines
- AE panel render queue automation (external script)
- Nuke connector script creating write-node outputs and shot scripts:
ffmpeg scripts
- None found in this repository.
Proxy generation
- Version.proxyUrl field exists in schema.
- No implemented proxy-generation worker/function found.
Thumbnail generation
- Upload/assignment flows exist.
- No server-side frame-extract thumbnail generator found.
Metadata extraction
- EDL / picture-tracker CSV metadata parsing implemented:
Burn-in generation
- Achieved via DCC overlay/slate layers and essential properties in AE/Nuke tool workflows.
- No ffmpeg burn-in path found.
5. Review System
Current review workflow
Core status derivation:
Server action orchestration:
Internal review
- Version upload:
- marks previous versions non-latest
- creates new latest version
- moves Task to INTERNAL_REVIEW
- recalculates Shot.status
Client review
- Tokenized ReviewSession links
- Optional password gate with signed cookie unlock
- Client comment/annotation/approval endpoints
- Share/unshare semantics via shot and version visibility fields
Task status flow
Observed statuses:
- TODO
- IN_PROGRESS
- INTERNAL_REVIEW
- CLIENT_REVIEW
- CHANGES
- DONE
Shot status flow
Derived in priority order:
- REVISIONS (any task CHANGES)
- COMPLETE (shotApprovalStatus CLIENT_APPROVED)
- CLIENT_REVIEW / READY_FOR_CLIENT (internally approved + share flag)
- IN_PROGRESS (task TODO/IN_PROGRESS)
- INTERNAL_REVIEW (tasks exist)
- WAITING (no tasks)
Approval process
- Version-level approvals create Approval rows and update Version.approvalStatus
- Shot-level client actions supported via client approve endpoint and shot actions
Reference doc in repo:
6. Authentication
External tools
- /api/ext/* routes authenticate using API_SECRET_KEY via:
- Authorization: Bearer
- x-api-key header
Client review links
- tokenized route with ReviewSession lookup
- optional password hash validation and signed unlock cookie
App users
- NextAuth credentials provider
- bcrypt password hash compare
- JWT session strategy
Middleware behavior
- Route classes allowed through middleware auth gate:
- /api/ext/
- /api/client/
- /api/display/
- /api/files/
- /api/uploadthing
- Per-route auth still enforced in handlers
Sources:
7. Existing Background Jobs
Cron jobs
- None found.
Queues
- None found.
Workers
- None found.
Polling services
- Display devices poll:
- /api/display/events
- /api/dashboard/stats
Docker containers
- vfxreview app container
- postgres container
- Source: docker-compose.yml
Scheduled tasks
- Startup migration commands in container entrypoint:
- AE panel Prep Delivery launches background PowerShell copy scripts on workstation (outside web app runtime)
8. After Effects Integration (Implemented Features)
Repo-level references:
Implemented capabilities observed/documented:
- API calls
- episodes/shots listing and shot lookup
- shot version PATCH
- Authentication
- bearer API token in script
- Comp discovery
- shot code extraction from comp names and dropdown selection
- Render queue integration
- EXR Sequence
- REVIEW_PREVIEW (MP4)
- 4444 Tri (MOV)
- EXR review sequence queue variant
- Output path generation
- shot/version naming conventions + export root paths
- Shot lookup
- uses /api/ext/shots/lookup for metadata and naming decisions
- Upload features
- no direct upload-to-web API flow in panel docs/script (render/output handled in DCC/filesystem)
- Burn-in generation
- overlay and slate essential properties
- Review integration
- preview comp build and version increment sync
9. Configuration
Environment variables
Primary reference: .env.example
Observed vars in code/docs include:
- DATABASE_URL
- NEXTAUTH_SECRET
- NEXTAUTH_URL
- NEXT_PUBLIC_APP_URL
- NEXT_PUBLIC_APP_NAME
- API_SECRET_KEY
- AUTH_SECRET
- STORAGE_PROVIDER
- LOCAL_UPLOAD_DIR
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_REGION
- AWS_BUCKET_NAME
- R2_ACCESS_KEY_ID
- R2_SECRET_ACCESS_KEY
- R2_ACCOUNT_ID
- R2_BUCKET_NAME
- R2_PUBLIC_URL
- B2_APPLICATION_KEY_ID
- B2_APPLICATION_KEY
- B2_BUCKET_NAME
- B2_ENDPOINT
- MINIO_ENDPOINT
- MINIO_ACCESS_KEY
- MINIO_SECRET_KEY
- MINIO_BUCKET_NAME
- HETZNER_ENDPOINT
- HETZNER_ACCESS_KEY
- HETZNER_SECRET_KEY
- HETZNER_BUCKET_NAME
- UPLOADTHING_SECRET
- UPLOADTHING_APP_ID
- EMAIL_FROM
- EMAIL_SERVER_HOST
- EMAIL_SERVER_PORT
- EMAIL_SERVER_USER
- EMAIL_SERVER_PASSWORD
- SLACK_DEFAULT_WEBHOOK
Storage configuration
- Provider abstraction in lib/storage.ts
- Hetzner overrides in actions/settings.ts and SystemConfig table
Render configuration
- No server-side renderer configuration module found.
- DCC render templates are documented in VFXReviewConnector.md.
ffmpeg configuration
- None found in repository.
Object storage configuration
- Implemented for AWS S3/R2/B2/MinIO + dedicated Hetzner helper path
10. Existing Utility Functions (Reusable)
Metadata extraction
- parseEdlCsv
- parsePictureTrackerCsv
- Source: lib/edl-utils.ts
File scanning
- Local upload tree walker in migration route
- Nuke connector plate/render directory scanners
- Sources:
EXR sequence detection
- Nuke connector helper sequence detectors and pattern conversion
- Source: VFXReviewConnector.py
MOV generation
- No server-side MOV generation utility found.
- MOV outputs are DCC render queue outputs in external scripts/docs.
Checksums
- No checksum utility found.
Frame counting
- durationToFrameCount and frame math helpers
- Source: lib/frame-utils.ts
Timecode extraction/conversion
- frameToTimecode / formatTimecode
- CSV timecode validation/parsing
- Sources:
11. High-Level Architecture Diagram
flowchart LR
A[AE Panel / Nuke Connector\nExternal DCC tools] -->|API key auth| B[Next.js App Router APIs]
B --> C[Prisma ORM]
C --> D[(PostgreSQL)]
B --> E[Storage Abstraction]
E --> F[(Hetzner Object Storage)]
E --> G[(S3/R2/B2/MinIO)]
B --> H[(Local uploads dir)]
I[Internal reviewers\nNextAuth session] --> B
J[Client reviewers\nToken review sessions] --> B
K[ESP32 display client] -->|x-display-key polling| B
A --> L[Shared filesystem render roots\nEXR/MP4/MOV outputs]
L -->|served/linked via app metadata| B
12. Files Inspected (Primary)
- prisma/schema.prisma
- lib/storage.ts
- lib/shot-status.ts
- lib/review-auth.ts
- lib/edl-utils.ts
- lib/frame-utils.ts
- lib/utils.ts
- auth.ts
- auth.config.ts
- middleware.ts
- next.config.ts
- .env.example
- docker-compose.yml
- Dockerfile
- entrypoint.sh
- EXT_API_REFERENCE.md
- VFXReviewConnector.md
- VFXReviewConnector.py
- # VFXReview Connector for Nuke.md
- API handlers under app/api
- Server actions under actions
End of current-state report.