301 lines
11 KiB
Markdown
301 lines
11 KiB
Markdown
# FeedBack — App Overview & Status
|
||
|
||
> Last updated: May 2026
|
||
|
||
---
|
||
|
||
## What It Is
|
||
|
||
FeedBack is a self-hosted VFX review and approval platform for boutique studios. Internal teams (admins, producers, supervisors, artists) manage projects and shots, upload versioned video files, draw frame-accurate annotations, and leave timestamped comments. External clients receive token-gated review links and can approve, reject, or request changes — no login required.
|
||
|
||
---
|
||
|
||
## Current Status
|
||
|
||
All core features are built and functional:
|
||
|
||
| Area | Status |
|
||
|------|--------|
|
||
| Auth (login, roles, sessions) | ✅ Complete |
|
||
| Projects & shots (CRUD) | ✅ Complete |
|
||
| Version upload (local storage) | ✅ Complete |
|
||
| Review player + frame timeline | ✅ Complete |
|
||
| Annotation drawing (canvas) | ✅ Complete — persists across frame navigation |
|
||
| Frame-anchored comments + replies | ✅ Complete |
|
||
| Approval workflow | ✅ Complete |
|
||
| Client management (internal) | ✅ Complete |
|
||
| Client portal (external, token-gated) | ✅ Complete |
|
||
| Tokenized review links | ✅ Complete |
|
||
| Slack notifications | ✅ Complete (optional webhook) |
|
||
| In-app notifications | ✅ Complete |
|
||
| Email (nodemailer) | ⚙️ Wired, requires SMTP env vars |
|
||
| S3 / R2 / MinIO / B2 storage | ⚙️ Wired, requires env vars |
|
||
| Docker Compose deployment | ✅ Complete |
|
||
|
||
---
|
||
|
||
## Route Map
|
||
|
||
### Public / Unauthenticated
|
||
|
||
| Route | Description |
|
||
|-------|-------------|
|
||
| `/login` | Credentials sign-in page |
|
||
| `/client/[token]` | External client project overview — lists shots visible to client |
|
||
| `/client/[token]/review/[versionId]` | External client review page — video player, comments, approve/reject |
|
||
|
||
### Dashboard (requires login)
|
||
|
||
| Route | Description |
|
||
|-------|-------------|
|
||
| `/dashboard` | Home — stats cards, shot queue, recent activity |
|
||
| `/projects` | Project list |
|
||
| `/projects/[id]` | Project detail — shot grid, stats |
|
||
| `/projects/[id]/shots/[shotId]` | Shot detail — version list, approval history |
|
||
| `/review/[versionId]` | Full-screen review player (internal) |
|
||
| `/clients` | Client list — ADMIN/PRODUCER only |
|
||
| `/clients/[clientId]` | Client detail — linked projects, active review sessions |
|
||
| `/settings` | User profile settings |
|
||
|
||
---
|
||
|
||
## API Routes
|
||
|
||
### Internal (authenticated)
|
||
|
||
| Method | Route | Purpose |
|
||
|--------|-------|---------|
|
||
| GET/POST | `/api/projects` | List / create projects |
|
||
| GET/POST/DELETE | `/api/shots` | Shot CRUD |
|
||
| GET | `/api/versions/[versionId]/comments` | Fetch comments for a version |
|
||
| GET | `/api/versions/[versionId]/annotations` | Fetch annotations for a version |
|
||
| GET/POST | `/api/clients` | List / create clients |
|
||
| GET/POST/DELETE | `/api/review-sessions` | Manage tokenized review links |
|
||
| GET/POST | `/api/notifications` | In-app notification CRUD |
|
||
| GET | `/api/files/[...key]` | Serve locally-stored files (no auth) |
|
||
| POST | `/api/upload/local` | Local file upload handler |
|
||
|
||
### Client portal (no auth — token-gated)
|
||
|
||
| Method | Route | Purpose |
|
||
|--------|-------|---------|
|
||
| GET | `/api/client/[token]/project` | Project + shots (CLIENT_REVIEW / REVISIONS / APPROVED / FINAL only) |
|
||
| GET | `/api/client/[token]/versions/[versionId]` | Version detail + comments |
|
||
| POST | `/api/client/[token]/approve` | Submit approval decision |
|
||
| POST | `/api/client/[token]/comment` | Post a comment |
|
||
|
||
---
|
||
|
||
## Server Actions (`/actions`)
|
||
|
||
| File | Exports |
|
||
|------|---------|
|
||
| `projects.ts` | `createProject`, `updateProject` |
|
||
| `shots.ts` | `createShot`, `updateShotStatus`, `getShotById` |
|
||
| `versions.ts` | `createVersion`, `setLatestVersion` |
|
||
| `comments.ts` | `addComment`, `addReply`, `resolveComment` |
|
||
| `annotations.ts` | `saveAnnotation`, `getAnnotationsForVersion`, `getAnnotationsForFrame` |
|
||
| `approvals.ts` | `submitApproval` |
|
||
|
||
---
|
||
|
||
## Database Schema
|
||
|
||
### Enums
|
||
|
||
| Enum | Values |
|
||
|------|--------|
|
||
| `Role` | `ADMIN` · `PRODUCER` · `SUPERVISOR` · `ARTIST` · `CLIENT` |
|
||
| `ProjectStatus` | `ACTIVE` · `ON_HOLD` · `COMPLETED` · `ARCHIVED` |
|
||
| `ShotStatus` | `WAITING` · `IN_PROGRESS` · `INTERNAL_REVIEW` · `CLIENT_REVIEW` · `REVISIONS` · `APPROVED` · `FINAL` |
|
||
| `ShotPriority` | `LOW` · `NORMAL` · `HIGH` · `URGENT` |
|
||
| `ApprovalStatus` | `PENDING_REVIEW` · `APPROVED` · `REJECTED` · `NEEDS_CHANGES` |
|
||
| `NotificationType` | `VERSION_UPLOADED` · `FEEDBACK_ADDED` · `SHOT_APPROVED` · `SHOT_REJECTED` · `COMMENT_REPLY` · `MENTION` · `REVISION_REQUESTED` |
|
||
|
||
### Models
|
||
|
||
#### `User`
|
||
Core identity. Has a `role` (see enum). `passwordHash` used for credentials login. NextAuth `Account` and `Session` models hang off this.
|
||
|
||
| Key field | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `id` | cuid | PK |
|
||
| `email` | String | unique |
|
||
| `role` | Role | default `ARTIST` |
|
||
| `passwordHash` | String? | bcrypt hash |
|
||
| `isActive` | Boolean | soft-disable |
|
||
|
||
#### `Client`
|
||
Represents an external studio or client company.
|
||
|
||
| Key field | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `company` | String | |
|
||
| `contactPerson` | String | |
|
||
| `email` | String | unique |
|
||
| `phone`, `notes`, `logoUrl` | optional | |
|
||
|
||
#### `ClientAccess`
|
||
Junction between a `User` (with `CLIENT` role) and a `Client` record. Allows a portal user account to be linked to a specific client company.
|
||
|
||
#### `Project`
|
||
Top-level container. Linked to an optional `Client`, `producer` (User), and `supervisor` (User).
|
||
|
||
| Key field | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `code` | String | unique, used as short label |
|
||
| `status` | ProjectStatus | default `ACTIVE` |
|
||
| `clientId` | String? | nullable |
|
||
| `slackWebhook` | String? | per-project Slack alerts |
|
||
|
||
#### `Shot`
|
||
Belongs to a `Project`. Tracks pipeline status and priority.
|
||
|
||
| Key field | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `shotCode` | String | unique per project |
|
||
| `sequence` | String? | grouping label |
|
||
| `status` | ShotStatus | default `WAITING` |
|
||
| `priority` | ShotPriority | default `NORMAL` |
|
||
| `fps` | Float | default 24 |
|
||
| `frameStart`, `frameEnd` | Int? | optional frame range |
|
||
|
||
> **Client visibility rule**: only shots with status `CLIENT_REVIEW`, `REVISIONS`, `APPROVED`, or `FINAL` are returned by the client portal API.
|
||
|
||
#### `Version`
|
||
A specific upload for a shot. Multiple versions per shot; only one has `isLatest = true`.
|
||
|
||
| Key field | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `versionNumber` | Int | unique per shot |
|
||
| `fileUrl` | String | path/URL to video |
|
||
| `fileSize` | BigInt? | serialized to string in API responses |
|
||
| `fps` | Float | |
|
||
| `approvalStatus` | ApprovalStatus | default `PENDING_REVIEW` |
|
||
| `isLatest` | Boolean | |
|
||
|
||
#### `Comment`
|
||
Frame-anchored comment on a version. Supports replies via `CommentReply`.
|
||
|
||
| Key field | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `frameNumber` | Int | exact frame |
|
||
| `timestamp` | Float | seconds |
|
||
| `text` | Text | |
|
||
| `isResolved` | Boolean | |
|
||
|
||
#### `CommentReply`
|
||
Flat child of `Comment`. No threading beyond one level.
|
||
|
||
#### `Annotation`
|
||
Canvas drawing saved against a version + frame. `drawingData` is JSON containing an array of `AnnotationShape` objects with normalized (0–1) coordinates.
|
||
|
||
| Key field | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `frameNumber` | Int | |
|
||
| `drawingData` | Json | `{ shapes, canvasWidth, canvasHeight, version }` |
|
||
| `color` | String | hex, default `#ef4444` |
|
||
| `isVisible` | Boolean | soft-hide |
|
||
| `commentId` | String? | optional companion comment link |
|
||
|
||
#### `Approval`
|
||
Immutable approval record created each time a user submits a decision. The version's `approvalStatus` is updated separately.
|
||
|
||
| Key field | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `status` | ApprovalStatus | |
|
||
| `notes` | Text? | |
|
||
|
||
#### `Notification`
|
||
In-app notification for a user.
|
||
|
||
| Key field | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `type` | NotificationType | |
|
||
| `data` | Json? | extra context (versionId, shotCode, etc.) |
|
||
| `isRead` | Boolean | |
|
||
|
||
#### `ReviewSession`
|
||
A tokenized, time-limited review link for external clients. Tied to a `Project`.
|
||
|
||
| Key field | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `token` | String | unique cuid, used in client portal URLs |
|
||
| `label` | String? | friendly name shown to client |
|
||
| `email` | String? | recipient email |
|
||
| `expiresAt` | DateTime | |
|
||
| `isActive` | Boolean | can be deactivated manually |
|
||
| `accessCount` | Int | incremented on each portal visit |
|
||
|
||
---
|
||
|
||
## Key Component Map
|
||
|
||
```
|
||
components/
|
||
player/
|
||
ReviewPlayer.tsx — Forwardref player, keyboard shortcuts, fullscreen
|
||
FrameTimeline.tsx — Canvas ruler with comment/annotation markers
|
||
PlaybackControls.tsx — Transport bar, speed selector, annotation toggle
|
||
annotations/
|
||
AnnotationCanvas.tsx — Canvas overlay; per-frame shape map persists navigation
|
||
AnnotationTools.tsx — Tool/colour/stroke picker (shown when annotating)
|
||
comments/
|
||
CommentPanel.tsx — Comment list, filter, reply, resolve; seekToFrame on click
|
||
versions/
|
||
VersionUpload.tsx — Drag-and-drop uploader
|
||
VersionList.tsx — Version history with approval badges
|
||
shots/
|
||
ShotCard.tsx — Shot grid card with status dropdown
|
||
NewShotDialog.tsx — Create shot form
|
||
projects/
|
||
ProjectCard.tsx — Project list card
|
||
NewProjectDialog.tsx — Create project form
|
||
clients/
|
||
NewClientDialog.tsx — Create client form
|
||
ShareReviewDialog.tsx — Generate tokenized review link
|
||
ReviewSessionList.tsx — Active sessions with copy/deactivate actions
|
||
dashboard/
|
||
StatsCards.tsx — Top-level counts
|
||
ShotQueue.tsx — Shots needing attention
|
||
RecentActivity.tsx — Latest version/comment events
|
||
layout/
|
||
Sidebar.tsx — Nav + role-based link visibility
|
||
Header.tsx — Breadcrumb + notification bell
|
||
NotificationBell.tsx — Dropdown of unread notifications
|
||
```
|
||
|
||
---
|
||
|
||
## Auth & Roles
|
||
|
||
Authentication uses **NextAuth v5** with a Credentials provider (email + bcrypt password). Sessions are JWT-based.
|
||
|
||
| Role | Access |
|
||
|------|--------|
|
||
| `ADMIN` | Full access including user management, client management |
|
||
| `PRODUCER` | Full project/shot/version access, client management |
|
||
| `SUPERVISOR` | Full project/shot/version access, no client management |
|
||
| `ARTIST` | Own shots and versions; can comment |
|
||
| `CLIENT` | Dashboard login only (legacy); primary access via portal token links |
|
||
|
||
The **client portal** routes (`/client/[token]/*` and `/api/client/[token]/*`) bypass auth entirely — access is controlled by token validity, `isActive`, and `expiresAt`.
|
||
|
||
---
|
||
|
||
## Storage
|
||
|
||
Controlled by `STORAGE_PROVIDER` env var. Currently defaults to `local` (files saved in `/public/uploads/`, served via `/api/files/[...key]`). Switching to S3/R2/B2/MinIO/UploadThing requires only env var changes.
|
||
|
||
---
|
||
|
||
## Environment Variables (minimum for local dev)
|
||
|
||
```env
|
||
DATABASE_URL="postgresql://user:pass@localhost:5432/feedback"
|
||
AUTH_SECRET="<32-char random string>"
|
||
NEXTAUTH_URL="http://localhost:3000"
|
||
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
||
STORAGE_PROVIDER="local"
|
||
```
|