Database cleanup pre rollout

This commit is contained in:
Leivur Djurhuus 2026-04-06 14:35:56 -05:00
parent d5c250277c
commit dfa067e95f
7 changed files with 6129 additions and 1 deletions

5370
backup_pre_clean_slate.sql Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,78 @@
---
date: 2026-04-06
topic: clean-slate-toolkit
---
# Clean Slate Toolkit
## Problem Frame
The HP CG Production Tracker is ready for a gradual rollout to a small group of real producers and artists. The database currently contains sample project data (imported from XLSX via `prisma/seed-tracker-data.ts`) that must be removed before real users begin tracking real deliverables. The team roster, skills, pipeline templates, and RBAC permissions need to survive the transition — but since `prisma/seed.ts` already recreates all of this idempotently, the simplest approach is to purge everything and re-seed.
## Requirements
**Data Purge**
- R1. Purge all data from every table in the database. This includes: projects, deliverables, deliverable stages, stage assignments, revisions, comments, annotations, feedback items, color probes, review sessions, review session items, notifications, chat messages, search logs, automation rules, automation executions, invitations, custom field definitions, notification rules, accounts, sessions, verification tokens, users, user skills, skills, stage skill requirements, RBAC permissions, pipeline templates (both global and org-scoped), pipeline stage definitions, and pipeline stage dependencies (both versions).
- R2. Use a full database truncation or ordered deletion strategy that respects FK constraints. Since all data is being purged (not selectively preserved), cascade-aware ordering or `TRUNCATE ... CASCADE` is acceptable.
**Re-seed Structural Data**
- R3. After purging, run the existing `prisma/seed.ts` to recreate: the dev organization, dev user, 26 team members with skills, pipeline stage templates (global), stage dependencies, stage skill requirements, and RBAC permissions.
- R4. The "HP CG Standard" dynamic pipeline template (org-scoped, 11 stages) must also survive. If `seed.ts` does not create it, the clean-slate script must recreate it or the seed must be extended to include it.
**Organization Transition**
- R5. After re-seeding, update the dev organization (`dev-org-001`, domain `dev.localhost`) in-place to the real Oliver Agency organization name and domain.
**Producer Users**
- R6. Add real producer users with `@oliver.agency` emails to `prisma/seed.ts` so they are created during re-seed. At minimum, the producers participating in the initial rollout need PRODUCER role, appropriate department, and capacity settings.
**User Preservation**
- R7. Keep the dev user (`dev-user-001`) in place for continued local development with `DEV_BYPASS_AUTH`. The dev user belongs to the (now-renamed) real org.
**Safety**
- R8. Require explicit `--confirm` flag for execution. Without `--confirm`, default to dry-run mode that logs what would happen without making changes.
- R9. In dry-run mode, print a "would purge" count projection per table. In execution mode, print "purged" and "re-seeded" counts per table after completion.
**Invocation**
- R10. Runnable as `npm run db:clean-slate` (or similar npm script). Standalone TypeScript script in `scripts/` using the same Prisma client setup as the existing seed scripts.
## Success Criteria
- After running the toolkit, the database contains zero projects, deliverables, or any downstream content data.
- All team members (artists + newly added producers), their skills, pipeline templates, and RBAC permissions are present and functional.
- The organization record reflects the real Oliver Agency name and domain.
- The "HP CG Standard" pipeline template is usable for creating new projects.
- A producer can log in and immediately create a new real project.
## Scope Boundaries
- **Not in scope:** SSO configuration, auth changes, deployment setup, or migration baseline work. Those are separate rollout ideas (see `docs/ideation/2026-04-06-production-rollout-ideation.md`).
- **Not in scope:** Data export or archival of sample data before purge. The sample data has no preservation value.
- **Not in scope:** Interactive prompts or TUI. This is a simple CLI script with flags.
- **Not in scope:** Idempotency guarantees for re-runs. This is a one-time operation (though running it twice on an already-clean database should not error).
## Key Decisions
- **Purge-and-reseed vs. selective preservation:** Purge-and-reseed is dramatically simpler. The existing `seed.ts` already upserts all structural data idempotently, so there's no need for complex selective-delete logic. Purge everything, re-seed, then update the org.
- **Keep dev user:** Retained so `DEV_BYPASS_AUTH` continues to work for local development alongside the real SSO rollout. Dev user belongs to the real org after transition.
- **Preserve dynamic pipeline template:** The "HP CG Standard" template (org-scoped, 11 stages) is production-ready. If seed.ts doesn't create it, the clean-slate script or an extended seed handles it.
- **Add producers to seed:** The current seed has zero PRODUCER role users. Real producers must be added to `seed.ts` before the clean-slate run so they exist after re-seeding.
## Dependencies / Assumptions
- The database is accessible and the Prisma client can connect (same `.env` setup as `seed.ts`).
- `seed.ts` is idempotent and covers all structural data needed post-purge (verified: it upserts users, skills, user skills, stage templates, dependencies, skill requirements, and RBAC permissions).
- The "HP CG Standard" dynamic pipeline template either exists in `seed.ts` or will be added before the clean-slate run.
- Auth/Session/Account tables contain no real user data yet — safe to purge entirely.
- The real org name and domain will be provided as CLI arguments to the script.
- Producer user details (names, emails, departments) will be provided before the clean-slate run to add to `seed.ts`.
## Outstanding Questions
### Deferred to Planning
- [Affects R2][Technical] Determine whether to use `TRUNCATE ... CASCADE` (fastest, requires superuser or table owner) or ordered `deleteMany` calls. Verify which approach works with the Prisma pg adapter.
- [Affects R4][Needs research] Confirm whether `seed.ts` creates the "HP CG Standard" dynamic pipeline template, or if it needs to be extended.
- [Affects R5][Technical] Determine whether the org name/domain should be passed as CLI arguments or read from env vars.
## Next Steps
-> `/ce:plan` for structured implementation planning

View file

@ -0,0 +1,105 @@
---
date: 2026-04-06
topic: production-rollout
focus: Gradual rollout to producers and artists, clean slate from sample data
---
# Ideation: Production Rollout Preparation
## Codebase Context
- **Stack:** Next.js 16 (Turbopack), React 19, Prisma 7, PostgreSQL 17 (pgvector), Zustand, next-auth v5
- **Current state:** Dev-only with sample data via seed scripts. Dev bypass auth enabled, real SSO not configured.
- **Schema:** 40+ models evolved via `db push` — only 2 migration files exist, both outdated.
- **Deployment:** Docker Compose has db + ollama services; app service defined but commented out. Runs on local machine via WSL2 Docker.
- **Auth:** Google + Microsoft Entra ID providers defined but env vars are placeholders. Hard-coded `dev-user-001` bypass.
- **Seed data:** 26 team members with real `@oliver.agency` emails, 15-stage HP pipeline template, sample projects from XLSX import.
- **Past learnings:** No documented solutions exist for rollout/migration topics.
## Ranked Ideas
### 1. Clean Slate Toolkit
**Description:** A single `npm run setup:production` CLI that purges all sample/seed data (projects, deliverables, stages, assignments, annotations, comments, chat messages, notifications, search logs) while preserving structural config (RBAC permissions, skills), creates the real Oliver Agency organization with correct domain, and optionally bulk-imports the real team roster. Includes `--dry-run` and `--confirm` safety flags.
**Rationale:** Most direct answer to the stated goal. Current seed scripts don't handle full purge and org creation is hardcoded to `dev-org-001`.
**Downsides:** One-time tooling that may not be reused. Could be done manually with SQL.
**Confidence:** 95%
**Complexity:** Low-Medium
**Status:** Explored (brainstorm 2026-04-06)
### 2. Versioned Migration Baseline
**Description:** Create a fresh baseline migration capturing the full current schema, resolve drift between existing migration files and actual DB state, enforce `prisma migrate deploy` going forward. Update Dockerfile to run migrations before app start.
**Rationale:** `db push` on a database with real user data is a data-loss risk with no rollback. Cheapest time to switch is before real data enters.
**Downsides:** Requires careful drift resolution. Existing migration files may need replacement.
**Confidence:** 90%
**Complexity:** Medium
**Status:** Unexplored
### 3. SSO Bridge: Domain Auto-Join + Seed User Linking
**Description:** Configure Microsoft Entra ID SSO (tenant-locked), add auth callback for org auto-assignment by email domain, handle seed-to-real transition by linking SSO accounts to existing seeded user records.
**Rationale:** The gap between hardcoded dev-user and real SSO is the single biggest login blocker. Seed data uses real emails, so linking should be straightforward.
**Downsides:** Requires Azure AD app registration. PrismaAdapter email-matching behavior needs explicit testing.
**Confidence:** 85%
**Complexity:** Medium
**Status:** Unexplored
### 4. Unprovisioned User Safety Net
**Description:** Branded limbo landing page for SSO-authenticated users without org membership (instead of JSON 403), plus hard guard against `DEV_BYPASS_AUTH` in production mode.
**Rationale:** First impressions during pilot are critical. A broken error kills trust; a leaked dev bypass is a security hole.
**Downsides:** Minimal — both are small, targeted changes.
**Confidence:** 95%
**Complexity:** Low
**Status:** Unexplored
### 5. One-Command Deployment (Docker Compose + Cloudflare Tunnel)
**Description:** Activate the existing Docker Compose app service, validate the Dockerfile build, stabilize networking via Docker internal DNS, add cloudflared tunnel service for HTTPS access on a subdomain.
**Rationale:** Infrastructure is 80% written but untested. Converts local machine to team-accessible server in hours. WSL IP instability disappears.
**Downsides:** Runs on local machine (uptime = machine uptime). Not a long-term solution if team grows.
**Confidence:** 80%
**Complexity:** Medium
**Status:** Unexplored
### 6. Production Startup Checks + Health Endpoint
**Description:** `/api/health` endpoint verifying DB, pgvector, migrations, org existence, dev bypass status. Startup validation that fails fast on missing/placeholder env vars in production mode.
**Rationale:** Bare `node server.js` with no pre-flight checks means misconfigured deploys silently serve errors.
**Downsides:** Adds startup latency (a few hundred ms).
**Confidence:** 85%
**Complexity:** Low-Medium
**Status:** Unexplored
### 7. Pre-seeded HP Pipeline Template on Org Bootstrap
**Description:** Auto-create the standard 15-stage HP CG pipeline template when real org is bootstrapped. Producers can immediately create projects with zero setup.
**Rationale:** Without templates, producers see empty Settings > Pipelines with no ability to create projects. Template is already defined in seed.ts.
**Downsides:** Tightly couples bootstrap to HP's specific pipeline. Fine for single-tenant pilot.
**Confidence:** 90%
**Complexity:** Low
**Status:** Unexplored
## Rejection Summary
| # | Idea | Reason Rejected |
|---|------|-----------------|
| 1 | Multi-org isolation audit | Too expensive -- single org, future concern |
| 2 | Automated DB backup on schema change | Simple pg_dump suffices at this scale |
| 3 | Seed data fingerprinting | Overly clever -- purge script is simpler |
| 4 | XLSX import idempotency | High complexity, v2 concern |
| 5 | Role pre-assignment via invitation | Manual assignment fine for ~5 users |
| 6 | Dual-mode auth toggle | Adds complexity -- use separate browser profiles |
| 7 | First-login guided tour | Too expensive for handful of users |
| 8 | Teams notification for invites | Adoption enhancement, not prerequisite |
| 9 | Multi-environment .env files | Subsumed by deployment + startup validation |
| 10 | Error tracking (Sentry) | Console logs fine for small pilot |
| 11 | Cron scheduling for deadlines | Narrow -- single curl cron job handles it |
| 12 | Actionable empty states | Users will import real data; transient |
| 13 | Spreadsheet migration wizard | Existing BulkImportDialog handles initial import |
| 14 | "My Day" artist dashboard | Enhancement, not rollout blocker |
| 15 | Live pipeline health bar | Enhancement, not rollout requirement |
| 16 | Inline status toasts with undo | Polish, not rollout-critical |
| 17 | Activity feed / audit trail | High complexity for initial rollout |
| 18 | Keyboard command palette | Power user feature, not rollout-critical |
| 19 | "Prove It" pilot mode | Walk-throughs done manually via Teams |
| 20 | Teams notification connector | Defer until tool has proven value |
| 21 | Env-aware database URL management | Solved by Docker internal networking |
| 22 | Schema drift detection | Merged into versioned migration baseline |
## Session Log
- 2026-04-06: Initial ideation -- 40 candidates generated across 4 frames, 7 survived. Clean Slate Toolkit selected for brainstorm.

View file

@ -0,0 +1,203 @@
---
title: "feat: Clean slate toolkit for production rollout"
type: feat
status: completed
date: 2026-04-06
origin: docs/brainstorms/2026-04-06-clean-slate-toolkit-requirements.md
---
# feat: Clean slate toolkit for production rollout
## Overview
Create a CLI script (`npm run db:clean-slate`) that purges all data from the database, re-seeds structural data (team, skills, templates, RBAC), adds producer users, creates the HP CG Standard dynamic pipeline template, and renames the dev organization to the real Oliver Agency org. This prepares the tracker for gradual rollout to real producers and artists.
## Problem Frame
The database contains sample project data from development. Real users need a clean database with the correct org identity, team roster (including producers), and pipeline templates ready to use. The purge-and-reseed approach was chosen over selective preservation because `seed.ts` already recreates all structural data idempotently. (see origin: `docs/brainstorms/2026-04-06-clean-slate-toolkit-requirements.md`)
## Requirements Trace
- R1. Purge all data from every table in the database
- R2. Use ordered deletion or TRUNCATE CASCADE respecting FK constraints
- R3. Re-seed structural data via existing `seed.ts`
- R4. Ensure HP CG Standard dynamic pipeline template exists after re-seed
- R5. Update dev org to real Oliver Agency name and domain
- R6. Add producer users to `seed.ts`
- R7. Keep dev user for local development
- R8. Require `--confirm` flag, default to dry-run
- R9. Print count projections (dry-run) or results (execution)
- R10. Runnable as `npm run db:clean-slate`
## Scope Boundaries
- Not in scope: SSO configuration, deployment, migration baseline
- Not in scope: Data archival before purge
- Not in scope: Interactive prompts or TUI
- Script is a one-time operation but should not error on re-runs
## Context & Research
### Relevant Code and Patterns
- `prisma/seed.ts` — idempotent seeder using `upsert` for org, users, skills; `deleteMany` + recreate for dependencies, skill requirements, assignments, RBAC. Prisma client setup: `PrismaPg` adapter with `DATABASE_URL` from dotenv.
- `prisma/seed-tracker-data.ts` — uses `deleteMany({ where: { organizationId } })` for scoped purge, `$executeRawUnsafe()` for raw SQL.
- `scripts/backfill-embeddings.ts` — uses `$executeRawUnsafe()` for raw SQL, same Prisma client pattern.
- No existing script uses CLI arg parsing — all use hardcoded values.
- No existing script uses `$transaction()`.
### Institutional Learnings
None documented for database operations or script patterns.
## Key Technical Decisions
- **Deletion strategy: ordered `deleteMany()` calls, not TRUNCATE CASCADE.** TRUNCATE CASCADE requires raw SQL and may conflict with Prisma's connection pooling or adapter. The existing codebase consistently uses `deleteMany()`. For a one-time script the performance difference is negligible. Order: leaf tables first (annotations, color probes, review session items, feedback items) → mid-level (revisions, comments, stage assignments, deliverable stages) → parents (deliverables, projects) → standalone org-linked tables (chat messages, notifications, search logs, automation executions, automation rules, invitations, custom field definitions, notification rules, review sessions) → auth tables (accounts, sessions, verification tokens) → user-linked (user skills, stage skill requirements, stage assignments) → pipeline (pipeline stage dependencies v2, pipeline stage definitions, pipeline templates, pipeline stage dependencies, pipeline stage templates) → RBAC (org role permissions) → users → org. Since re-seeding recreates everything, we can delete aggressively.
- **HP CG Standard template: add to `seed.ts`.** The dynamic pipeline template was created through the UI and doesn't exist in seed.ts. Adding it there ensures it's always available in dev and after clean-slate runs. Query the current database to capture the exact stage definitions before purging.
- **CLI args via `process.argv`.** No CLI framework needed for a one-time script with two arguments (org name, domain) and one flag (`--confirm`).
- **Producer users: add to TEAM_MEMBERS array in `seed.ts`.** User will provide names/emails before running. The empty `// Producers` comment block is the natural insertion point.
- **Self-referential FKs: null before deleting.** Comments have a self-referential `parentId` FK with no cascade rule — must `updateMany({ data: { parentId: null } })` before `deleteMany()`. FeedbackItem has `carriedFromId` (onDelete: SetNull at DB level) — null it for safety before deletion.
- **Deletion order is load-bearing, not optional.** Several FK relationships lack `onDelete: Cascade` (DeliverableStage.templateId, StageAssignment.userId, Comment.authorId, Annotation.createdById, ReviewSessionItem.deliverableStageId). The specified order must be followed exactly.
## Open Questions
### Resolved During Planning
- **TRUNCATE vs deleteMany?** → deleteMany, consistent with codebase patterns.
- **Does seed.ts create HP CG Standard?** → No. Must be added to seed.ts.
- **Org name/domain delivery?** → CLI arguments: `--org-name "Oliver Agency" --domain oliver.agency`.
### Deferred to Implementation
- **Exact HP CG Standard template stages:** Must query the current database to capture ALL fields of the 11 stage definitions (name, slug, order, isCriticalGate, isOptional, description, estimatedDays, color, customStatuses) and their PipelineStageDependencyV2 entries before implementing the seed extension. Write a capture query that dumps the complete template structure to verify before hardcoding into seed.ts.
- **Producer user details:** Names, emails, departments, and capacity values will be provided by the user before implementation.
## Implementation Units
- [x] **Unit 1: Add producer users to seed.ts**
**Goal:** Add real producer users to the TEAM_MEMBERS array so they're created during seeding.
**Requirements:** R6
**Dependencies:** None. User must provide producer details (names, emails) before this unit.
**Files:**
- Modify: `prisma/seed.ts`
**Approach:**
- Add producer entries under the existing `// Producers` comment (line 185) in the TEAM_MEMBERS array
- Each producer needs: `id`, `name`, `email` (@oliver.agency), `role: "PRODUCER"`, `department`, `maxCapacity`
- Use deterministic IDs following existing pattern: `user-producer-001`, `user-producer-002`, etc.
**Patterns to follow:**
- Existing TEAM_MEMBERS entries in `prisma/seed.ts` (lines 184-215)
**Test expectation:** none — data-only change to seed script, verified by re-running seed.
**Verification:**
- Running `npm run db:seed-team` creates producer users with PRODUCER role in the database.
---
- [x] **Unit 2: Add HP CG Standard dynamic pipeline template to seed.ts**
**Goal:** Seed the org-scoped "HP CG Standard" pipeline template so it survives purge-and-reseed.
**Requirements:** R4
**Dependencies:** None (independent of Unit 1, but both modify seed.ts).
**Files:**
- Modify: `prisma/seed.ts`
**Approach:**
- Query the current database first to capture the exact 11 stage definitions from the existing HP CG Standard template (PipelineTemplate → PipelineStageDefinition rows, PipelineStageDependencyV2 rows)
- Add a new section to seed.ts after the global template seeding that upserts a PipelineTemplate for `dev-org-001` with `isDefault: true`
- Use deterministic IDs for the template and all stage definitions (e.g., `pipeline-hp-cg-standard`, `stage-def-brief-intake`) following the same pattern as users and org — this prevents broken references across reseed operations
- Upsert each PipelineStageDefinition with ALL captured fields (name, slug, order, isCriticalGate, isOptional, description, estimatedDays, color, customStatuses)
- Recreate PipelineStageDependencyV2 entries
- Use the same `upsert` pattern as the existing global template seeding
**Patterns to follow:**
- Global template seeding in `prisma/seed.ts` (lines 120-148) — upsert + deleteMany/recreate for dependencies
**Test expectation:** none — data-only seed extension, verified by re-running seed.
**Verification:**
- Running `npm run db:seed-team` creates a "HP CG Standard" PipelineTemplate with 11 PipelineStageDefinition rows and their dependencies.
---
- [x] **Unit 3: Create the clean-slate script**
**Goal:** Create the main purge-reseed-rename script.
**Requirements:** R1, R2, R3, R5, R7, R8, R9, R10
**Dependencies:** Units 1 and 2 (seed.ts must include producers and HP CG Standard template before this script is useful).
**Files:**
- Create: `scripts/clean-slate.ts`
- Modify: `package.json` (add `db:clean-slate` npm script)
**Approach:**
- Parse CLI args with `process.argv`: `--confirm` flag, `--org-name <name>`, `--domain <domain>`
- Without `--confirm`, run in dry-run mode: count records per table and print "would purge" projection, then exit
- With `--confirm`:
1. **Count phase:** Query record counts per table for the pre-purge summary
2. **Purge phase:** Delete all data in FK-safe order using `deleteMany()`:
- Null out `Comment.parentId` (`updateMany`)
- Delete leaf tables: annotations, color probes, review session items, feedback items
- Delete mid tables: revisions, comments, stage assignments, deliverable stages
- Delete parent tables: deliverables, projects
- Delete org-linked standalone: chat messages, notifications, search logs, automation executions, automation rules, invitations, custom field definitions, notification rules, review sessions
- Delete auth tables: accounts, sessions, verification tokens
- Null out `FeedbackItem.carriedFromId` (`updateMany`) — self-referential FK
- Delete user-linked: user skills, stage skill requirements, skills
- Delete pipeline v2: pipeline stage dependencies v2, pipeline stage definitions, pipeline templates
- Delete pipeline v1: pipeline stage dependencies, pipeline stage templates
- Delete RBAC: org role permissions
- Delete users (all including dev user — seed recreates them)
- Delete organization
3. **Reseed phase:** Shell out to `npx tsx prisma/seed.ts` via `child_process.execSync()`. Do not import seed.ts programmatically — `main()` is not exported and the file auto-executes with `process.exit(1)` on error, which would kill the clean-slate process. Check the exit code to detect seed failure.
4. **Rename phase:** Update the org record with the provided name and domain
5. **Summary phase:** Query counts again and print "purged N / re-seeded M" per category
- Wrap the purge phase in a try/catch. On error, log the failure point and suggest manual inspection.
**Patterns to follow:**
- Prisma client setup from `prisma/seed.ts` (lines 1-6)
- `deleteMany()` patterns from `prisma/seed.ts` and `prisma/seed-tracker-data.ts`
- Error handling with `process.exit(1)` and `$disconnect()` in `.finally()`
**Test scenarios:**
- Happy path: Running with `--confirm --org-name "Oliver Agency" --domain oliver.agency` purges all data, re-seeds team/templates/RBAC, renames org. Post-run, zero projects exist, 26+ users exist, HP CG Standard template exists, org has new name/domain.
- Happy path: Running without `--confirm` prints record counts per table and exits without modifying data.
- Edge case: Running on an already-clean database (no projects, no deliverables) completes without error — deleteMany on empty tables returns 0.
- Error path: Running with `--confirm` but missing `--org-name` or `--domain` prints usage message and exits with code 1.
- Integration: After clean-slate + reseed, creating a new project via the API using the HP CG Standard pipeline template succeeds (template ID is valid, stage definitions exist).
**Verification:**
- `npm run db:clean-slate -- --confirm --org-name "Oliver Agency" --domain oliver.agency` completes without errors
- Database has zero projects/deliverables, all team members present with correct roles, HP CG Standard template with 11 stages, org renamed
## System-Wide Impact
- **Interaction graph:** The script only touches the database directly. No application code, middleware, or API routes are modified.
- **State lifecycle risks:** If the script fails mid-purge, the database will be in an inconsistent state. The reseed will not run. Mitigation: the user can re-run the script (deleteMany on partially-purged tables is safe) or manually run `npm run db:seed-team`.
- **Unchanged invariants:** All application code, API routes, and UI remain unchanged. The script only modifies database contents.
## Risks & Dependencies
| Risk | Mitigation |
|------|------------|
| FK constraint violation during purge | Explicit deletion order handles all known FK chains. Comment self-ref handled by nulling parentId first. |
| HP CG Standard template data lost before capture | Query and document the template's stage definitions before implementing Unit 2. |
| Producer user details not available | Defer Unit 1 until user provides names/emails. Script is functional without producers (just missing producer-role users). |
| Mid-purge failure leaves inconsistent state | Script can be re-run safely. Seed is idempotent. |
## Sources & References
- **Origin document:** [docs/brainstorms/2026-04-06-clean-slate-toolkit-requirements.md](docs/brainstorms/2026-04-06-clean-slate-toolkit-requirements.md)
- Related code: `prisma/seed.ts`, `prisma/seed-tracker-data.ts`, `scripts/backfill-embeddings.ts`
- Schema: `prisma/schema.prisma`

View file

@ -16,7 +16,8 @@
"db:studio": "prisma studio",
"db:seed-tracker": "tsx prisma/seed-tracker-data.ts",
"db:seed-team": "tsx prisma/seed.ts",
"db:backfill-embeddings": "tsx scripts/backfill-embeddings.ts"
"db:backfill-embeddings": "tsx scripts/backfill-embeddings.ts",
"db:clean-slate": "tsx scripts/clean-slate.ts"
},
"dependencies": {
"@auth/prisma-adapter": "^2.11.1",

View file

@ -183,6 +183,13 @@ async function main() {
maxCapacity: number;
}[] = [
// Producers
{ id: "user-producer-001", name: "Bohdana Phillips", email: "bohdana.phillips@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
{ id: "user-producer-002", name: "Gaurav Singh", email: "gaurav.singh@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
{ id: "user-producer-003", name: "Ishan Jinsi", email: "ishan.jinsi@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
{ id: "user-producer-004", name: "Manan Aggarwal", email: "manan.aggarwal@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
{ id: "user-producer-005", name: "Mohd Iqbal", email: "mohd.iqbal@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
{ id: "user-producer-006", name: "Neha Sayana", email: "neha.sayana@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
{ id: "user-producer-007", name: "Vivek Singh", email: "vivek.singh@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
// CGI Stills Artists
{ id: "user-artist-001", name: "Aditya Varma", email: "aditya.varma@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 },
{ id: "user-artist-002", name: "Ameya Bhagwat", email: "ameya.bhagwat@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 },
@ -482,6 +489,84 @@ async function main() {
await prisma.orgRolePermission.createMany({ data: permData });
console.log(`Created ${permData.length} role permission entries for dev org`);
// ─── Phase 8: Seed HP CG Standard Dynamic Pipeline Template ──
console.log("Seeding HP CG Standard dynamic pipeline template...");
const HP_CG_TEMPLATE_ID = "pipeline-hp-cg-standard";
const hpCgTemplate = await prisma.pipelineTemplate.upsert({
where: { id: HP_CG_TEMPLATE_ID },
update: {
name: "HP CG Standard",
description: "Standard HP CG production pipeline with 10 stages",
isDefault: true,
isArchived: false,
},
create: {
id: HP_CG_TEMPLATE_ID,
name: "HP CG Standard",
description: "Standard HP CG production pipeline with 10 stages",
organizationId: devOrg.id,
isDefault: true,
isArchived: false,
},
});
const HP_CG_STAGES = [
{ id: "stage-def-brief-intake", name: "Brief Intake", slug: "brief-intake", order: 1, isCriticalGate: true, isOptional: false, description: "Receive and review the project brief from HP", estimatedDays: 1 },
{ id: "stage-def-file-delivery", name: "File Delivery", slug: "file-delivery", order: 2, isCriticalGate: false, isOptional: true, description: "Receive source files (CAD, textures, reference materials)", estimatedDays: 1 },
{ id: "stage-def-model-prep", name: "Model Prep", slug: "model-prep", order: 3, isCriticalGate: true, isOptional: false, description: "Prepare 3D models for rendering pipeline", estimatedDays: 5 },
{ id: "stage-def-early-images", name: "Early Images", slug: "early-images", order: 4, isCriticalGate: false, isOptional: true, description: "Optional early preview renders for client feedback", estimatedDays: 1 },
{ id: "stage-def-catalog-images", name: "Catalog Images", slug: "catalog-images", order: 5, isCriticalGate: true, isOptional: false, description: "Standard catalog product imagery", estimatedDays: 1 },
{ id: "stage-def-hero-images", name: "Hero Images", slug: "hero-images", order: 6, isCriticalGate: false, isOptional: false, description: "High-impact hero product shots", estimatedDays: 2 },
{ id: "stage-def-packaging-images", name: "Packaging Images", slug: "packaging-images", order: 7, isCriticalGate: false, isOptional: false, description: "Product packaging renders", estimatedDays: 2 },
{ id: "stage-def-photocomps", name: "Photocomps", slug: "photocomps", order: 8, isCriticalGate: false, isOptional: false, description: "Photo composite renders with lifestyle backgrounds", estimatedDays: 3 },
{ id: "stage-def-360-spin", name: "360 Spin Animations", slug: "360-spin-animations", order: 9, isCriticalGate: false, isOptional: false, description: "Interactive 360-degree product spin animations", estimatedDays: 3.5 },
{ id: "stage-def-dynamic-spin", name: "Dynamic Spin", slug: "dynamic-spin", order: 10, isCriticalGate: false, isOptional: false, description: "Dynamic animated product spins with effects", estimatedDays: 7 },
{ id: "stage-def-misc", name: "Misc", slug: "misc", order: 11, isCriticalGate: false, isOptional: true, description: "Screenswaps, logo updates, file renaming, etc", estimatedDays: 2 },
];
const hpCgStageMap = new Map<string, string>();
for (const stage of HP_CG_STAGES) {
const created = await prisma.pipelineStageDefinition.upsert({
where: { pipelineId_slug: { pipelineId: hpCgTemplate.id, slug: stage.slug } },
update: { name: stage.name, order: stage.order, isCriticalGate: stage.isCriticalGate, isOptional: stage.isOptional, description: stage.description, estimatedDays: stage.estimatedDays },
create: { id: stage.id, pipelineId: hpCgTemplate.id, name: stage.name, slug: stage.slug, order: stage.order, isCriticalGate: stage.isCriticalGate, isOptional: stage.isOptional, description: stage.description, estimatedDays: stage.estimatedDays },
});
hpCgStageMap.set(stage.slug, created.id);
}
console.log(`Created/updated ${HP_CG_STAGES.length} HP CG Standard pipeline stages`);
// Clear and recreate HP CG Standard dependencies
await prisma.pipelineStageDependencyV2.deleteMany({
where: { stage: { pipelineId: hpCgTemplate.id } },
});
const HP_CG_DEPS: [string, string][] = [
["file-delivery", "brief-intake"],
["model-prep", "brief-intake"],
["model-prep", "file-delivery"],
["early-images", "model-prep"],
["catalog-images", "model-prep"],
["hero-images", "catalog-images"],
["packaging-images", "catalog-images"],
["photocomps", "catalog-images"],
["360-spin-animations", "catalog-images"],
["dynamic-spin", "catalog-images"],
["misc", "catalog-images"],
];
for (const [stageSlug, prereqSlug] of HP_CG_DEPS) {
const stageId = hpCgStageMap.get(stageSlug)!;
const prerequisiteId = hpCgStageMap.get(prereqSlug)!;
await prisma.pipelineStageDependencyV2.create({
data: { stageId, prerequisiteId },
});
}
console.log(`Created ${HP_CG_DEPS.length} HP CG Standard stage dependencies`);
console.log("Seed complete!");
}

286
scripts/clean-slate.ts Normal file
View file

@ -0,0 +1,286 @@
import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../src/generated/prisma/client";
import { execSync } from "child_process";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
const prisma = new PrismaClient({ adapter });
// ─── CLI Argument Parsing ──────────────────────────────
function parseArgs() {
const args = process.argv.slice(2);
const flags: Record<string, string | boolean> = {};
for (let i = 0; i < args.length; i++) {
if (args[i] === "--confirm") {
flags.confirm = true;
} else if (args[i] === "--org-name" && args[i + 1]) {
flags.orgName = args[++i];
} else if (args[i] === "--domain" && args[i + 1]) {
flags.domain = args[++i];
}
}
return flags;
}
function printUsage() {
console.log(`
Usage: npm run db:clean-slate -- [--confirm] --org-name <name> --domain <domain>
Options:
--confirm Execute the purge (without this flag, runs in dry-run mode)
--org-name <name> The real organization name (e.g., "Oliver Agency")
--domain <domain> The real organization domain (e.g., "oliver.agency")
Examples:
npm run db:clean-slate -- --org-name "Oliver Agency" --domain oliver.agency
npm run db:clean-slate -- --confirm --org-name "Oliver Agency" --domain oliver.agency
`);
}
// ─── Record Counting ───────────────────────────────────
async function getRecordCounts() {
const counts: Record<string, number> = {};
counts["organizations"] = await prisma.organization.count();
counts["users"] = await prisma.user.count();
counts["accounts"] = await prisma.account.count();
counts["sessions"] = await prisma.session.count();
counts["projects"] = await prisma.project.count();
counts["deliverables"] = await prisma.deliverable.count();
counts["deliverable_stages"] = await prisma.deliverableStage.count();
counts["stage_assignments"] = await prisma.stageAssignment.count();
counts["revisions"] = await prisma.revision.count();
counts["comments"] = await prisma.comment.count();
counts["annotations"] = await prisma.annotation.count();
counts["feedback_items"] = await prisma.feedbackItem.count();
counts["color_probes"] = await prisma.colorProbe.count();
counts["review_sessions"] = await prisma.reviewSession.count();
counts["review_session_items"] = await prisma.reviewSessionItem.count();
counts["notifications"] = await prisma.notification.count();
counts["chat_messages"] = await prisma.chatMessage.count();
counts["search_logs"] = await prisma.searchLog.count();
counts["automation_rules"] = await prisma.automationRule.count();
counts["automation_executions"] = await prisma.automationExecution.count();
counts["invitations"] = await prisma.invitation.count();
counts["custom_field_definitions"] = await prisma.customFieldDefinition.count();
counts["notification_rules"] = await prisma.notificationRule.count();
counts["user_skills"] = await prisma.userSkill.count();
counts["skills"] = await prisma.skill.count();
counts["stage_skill_requirements"] = await prisma.stageSkillRequirement.count();
counts["pipeline_templates"] = await prisma.pipelineTemplate.count();
counts["pipeline_stage_definitions"] = await prisma.pipelineStageDefinition.count();
counts["pipeline_stage_deps_v2"] = await prisma.pipelineStageDependencyV2.count();
counts["pipeline_stage_templates"] = await prisma.pipelineStageTemplate.count();
counts["pipeline_stage_deps"] = await prisma.pipelineStageDependency.count();
counts["org_role_permissions"] = await prisma.orgRolePermission.count();
return counts;
}
function printCounts(counts: Record<string, number>, label: string) {
console.log(`\n${label}:`);
console.log("─".repeat(50));
const total = Object.values(counts).reduce((a, b) => a + b, 0);
for (const [table, count] of Object.entries(counts)) {
if (count > 0) {
console.log(` ${table.padEnd(30)} ${count}`);
}
}
const zeroCount = Object.values(counts).filter((c) => c === 0).length;
if (zeroCount > 0) {
console.log(` ... and ${zeroCount} tables with 0 records`);
}
console.log(` ${"TOTAL".padEnd(30)} ${total}`);
}
// ─── Purge ─────────────────────────────────────────────
async function purgeAllData() {
console.log("\n🗑 Purging all data...\n");
// Null out self-referential FKs first
console.log(" Nulling self-referential FKs...");
await prisma.comment.updateMany({ data: { parentId: null } });
await prisma.feedbackItem.updateMany({ data: { carriedFromId: null } });
// Leaf tables
console.log(" Deleting leaf tables (annotations, color probes, review session items, feedback items)...");
await prisma.annotation.deleteMany();
await prisma.colorProbe.deleteMany();
await prisma.reviewSessionItem.deleteMany();
await prisma.feedbackItem.deleteMany();
// Mid-level tables
console.log(" Deleting mid-level tables (revisions, comments, stage assignments, deliverable stages)...");
await prisma.revision.deleteMany();
await prisma.comment.deleteMany();
await prisma.stageAssignment.deleteMany();
await prisma.deliverableStage.deleteMany();
// Parent tables
console.log(" Deleting parent tables (deliverables, projects)...");
await prisma.deliverable.deleteMany();
await prisma.project.deleteMany();
// Org-linked standalone tables
console.log(" Deleting org-linked standalone tables...");
await prisma.chatMessage.deleteMany();
await prisma.notification.deleteMany();
await prisma.searchLog.deleteMany();
await prisma.automationExecution.deleteMany();
await prisma.automationRule.deleteMany();
await prisma.invitation.deleteMany();
await prisma.customFieldDefinition.deleteMany();
await prisma.notificationRule.deleteMany();
await prisma.reviewSession.deleteMany();
// Auth tables
console.log(" Deleting auth tables (accounts, sessions, verification tokens)...");
await prisma.account.deleteMany();
await prisma.session.deleteMany();
await prisma.$executeRawUnsafe(`DELETE FROM verification_tokens`);
// User-linked tables
console.log(" Deleting user-linked tables (user skills, stage skill requirements, skills)...");
await prisma.userSkill.deleteMany();
await prisma.stageSkillRequirement.deleteMany();
await prisma.skill.deleteMany();
// Pipeline v2 (org-scoped dynamic templates)
console.log(" Deleting pipeline v2 tables (stage deps v2, stage definitions, pipeline templates)...");
await prisma.pipelineStageDependencyV2.deleteMany();
await prisma.pipelineStageDefinition.deleteMany();
await prisma.pipelineTemplate.deleteMany();
// Pipeline v1 (global templates)
console.log(" Deleting pipeline v1 tables (stage dependencies, stage templates)...");
await prisma.pipelineStageDependency.deleteMany();
await prisma.pipelineStageTemplate.deleteMany();
// RBAC
console.log(" Deleting RBAC permissions...");
await prisma.orgRolePermission.deleteMany();
// Users and org
console.log(" Deleting users and organization...");
await prisma.user.deleteMany();
await prisma.organization.deleteMany();
console.log("\n✅ Purge complete — all tables empty.");
}
// ─── Reseed ────────────────────────────────────────────
function reseed() {
console.log("\n🌱 Re-seeding structural data via seed.ts...\n");
try {
execSync("npx tsx prisma/seed.ts", {
cwd: process.cwd(),
stdio: "inherit",
env: process.env,
});
console.log("\n✅ Reseed complete.");
} catch (error) {
console.error("\n❌ Reseed FAILED. The database may be empty.");
console.error(" To recover, manually run: npm run db:seed-team");
throw error;
}
}
// ─── Main ──────────────────────────────────────────────
async function main() {
const flags = parseArgs();
if (!flags.orgName || !flags.domain) {
console.error("Error: --org-name and --domain are required.\n");
printUsage();
process.exit(1);
}
console.log("╔══════════════════════════════════════════════╗");
console.log("║ HP CG Production Tracker ║");
console.log("║ Clean Slate Toolkit ║");
console.log("╚══════════════════════════════════════════════╝");
console.log(`\n Target org: "${flags.orgName}" (${flags.domain})`);
console.log(` Mode: ${flags.confirm ? "🔴 EXECUTE" : "🟢 DRY RUN"}`);
// Count current records
const preCounts = await getRecordCounts();
printCounts(preCounts, flags.confirm ? "Records to purge" : "Would purge");
if (!flags.confirm) {
console.log("\n─── DRY RUN ───");
console.log("No changes made. Run with --confirm to execute.\n");
return;
}
// Execute purge
console.log("\n═══════════════════════════════════════════════");
console.log(" EXECUTING CLEAN SLATE");
console.log("═══════════════════════════════════════════════");
await purgeAllData();
// Need to disconnect before reseed (reseed creates its own Prisma client)
await prisma.$disconnect();
reseed();
// Reconnect after reseed
const postAdapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
const postPrisma = new PrismaClient({ adapter: postAdapter });
try {
// Rename org
const org = await postPrisma.organization.findFirst();
if (!org) {
throw new Error("No organization found after reseed.");
}
await postPrisma.organization.update({
where: { id: org.id },
data: { name: flags.orgName as string, domain: flags.domain as string },
});
console.log(`\n🏢 Organization updated: "${flags.orgName}" (${flags.domain})`);
// Post-execution summary
const postCountsObj: Record<string, number> = {};
postCountsObj["organizations"] = await postPrisma.organization.count();
postCountsObj["users"] = await postPrisma.user.count();
postCountsObj["pipeline_stage_templates"] = await postPrisma.pipelineStageTemplate.count();
postCountsObj["pipeline_templates"] = await postPrisma.pipelineTemplate.count();
postCountsObj["pipeline_stage_definitions"] = await postPrisma.pipelineStageDefinition.count();
postCountsObj["skills"] = await postPrisma.skill.count();
postCountsObj["user_skills"] = await postPrisma.userSkill.count();
postCountsObj["org_role_permissions"] = await postPrisma.orgRolePermission.count();
postCountsObj["projects"] = await postPrisma.project.count();
postCountsObj["deliverables"] = await postPrisma.deliverable.count();
printCounts(postCountsObj, "Re-seeded records");
console.log("\n═══════════════════════════════════════════════");
console.log(" ✅ CLEAN SLATE COMPLETE");
console.log("═══════════════════════════════════════════════");
console.log(`\n Organization: "${flags.orgName}" (${flags.domain})`);
console.log(" Database is ready for production use.\n");
} finally {
await postPrisma.$disconnect();
}
}
main()
.catch((e) => {
console.error("\n❌ Clean slate failed:", e.message || e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});