generator client { provider = "prisma-client-js" } generator erd { provider = "prisma-erd-generator" output = "../docs/erd.svg" } datasource db { provider = "postgresql" } enum Role { PRODUCT_OWNER SCRUM_MASTER DEVELOPER } enum StoryStatus { OPEN IN_SPRINT DONE } enum PbiStatus { READY BLOCKED DONE } enum ClaudeJobStatus { QUEUED CLAIMED RUNNING DONE FAILED CANCELLED } enum VerifyResult { ALIGNED PARTIAL EMPTY DIVERGENT } enum TaskStatus { TO_DO IN_PROGRESS REVIEW DONE } enum LogType { IMPLEMENTATION_PLAN TEST_RESULT COMMIT } enum TestStatus { PASSED FAILED } enum SprintStatus { ACTIVE COMPLETED } model User { id String @id @default(cuid()) username String @unique email String? @unique password_hash String is_demo Boolean @default(false) bio String? @db.VarChar(160) bio_detail String? @db.VarChar(2000) avatar_data Bytes? active_product_id String? active_product Product? @relation("UserActiveProduct", fields: [active_product_id], references: [id], onDelete: SetNull) created_at DateTime @default(now()) updated_at DateTime @updatedAt roles UserRole[] api_tokens ApiToken[] products Product[] todos Todo[] product_members ProductMember[] assigned_stories Story[] @relation("StoryAssignee") login_pairings LoginPairing[] asked_questions ClaudeQuestion[] @relation("ClaudeQuestionAsker") answered_questions ClaudeQuestion[] @relation("ClaudeQuestionAnswerer") claude_jobs ClaudeJob[] claude_workers ClaudeWorker[] @@index([active_product_id]) @@map("users") } model UserRole { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String role Role @@unique([user_id, role]) @@map("user_roles") } model ApiToken { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String token_hash String @unique label String? created_at DateTime @default(now()) revoked_at DateTime? claimed_jobs ClaudeJob[] claude_worker ClaudeWorker? @@index([token_hash]) @@map("api_tokens") } model Product { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String name String code String? @db.VarChar(30) description String? repo_url String? definition_of_done String auto_pr Boolean @default(false) archived Boolean @default(false) created_at DateTime @default(now()) updated_at DateTime @updatedAt pbis Pbi[] sprints Sprint[] stories Story[] todos Todo[] members ProductMember[] active_for_users User[] @relation("UserActiveProduct") claude_questions ClaudeQuestion[] claude_jobs ClaudeJob[] @@unique([user_id, name]) @@unique([user_id, code]) @@index([user_id, archived]) @@map("products") } model Pbi { id String @id @default(cuid()) product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String code String? @db.VarChar(30) title String description String? priority Int sort_order Float status PbiStatus @default(READY) created_at DateTime @default(now()) updated_at DateTime @updatedAt stories Story[] @@unique([product_id, code]) @@index([product_id, priority, sort_order]) @@index([product_id, status]) @@map("pbis") } model Story { id String @id @default(cuid()) pbi Pbi @relation(fields: [pbi_id], references: [id], onDelete: Cascade) pbi_id String product Product @relation(fields: [product_id], references: [id]) product_id String sprint Sprint? @relation(fields: [sprint_id], references: [id]) sprint_id String? assignee User? @relation("StoryAssignee", fields: [assignee_id], references: [id], onDelete: SetNull) assignee_id String? code String? @db.VarChar(30) title String description String? acceptance_criteria String? priority Int sort_order Float status StoryStatus @default(OPEN) created_at DateTime @default(now()) updated_at DateTime @updatedAt logs StoryLog[] tasks Task[] claude_questions ClaudeQuestion[] @@unique([product_id, code]) @@index([pbi_id, priority, sort_order]) @@index([sprint_id, sort_order]) @@index([product_id, status]) @@index([sprint_id, assignee_id]) @@map("stories") } model StoryLog { id String @id @default(cuid()) story Story @relation(fields: [story_id], references: [id], onDelete: Cascade) story_id String type LogType content String status TestStatus? commit_hash String? commit_message String? metadata Json? created_at DateTime @default(now()) @@index([story_id, created_at]) @@map("story_logs") } model Sprint { id String @id @default(cuid()) product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String sprint_goal String status SprintStatus @default(ACTIVE) start_date DateTime? @db.Date end_date DateTime? @db.Date created_at DateTime @default(now()) completed_at DateTime? stories Story[] tasks Task[] @@index([product_id, status]) @@map("sprints") } model Task { id String @id @default(cuid()) story Story @relation(fields: [story_id], references: [id], onDelete: Cascade) story_id String sprint Sprint? @relation(fields: [sprint_id], references: [id]) sprint_id String? title String description String? implementation_plan String? priority Int sort_order Float status TaskStatus @default(TO_DO) verify_only Boolean @default(false) created_at DateTime @default(now()) updated_at DateTime @updatedAt claude_questions ClaudeQuestion[] claude_jobs ClaudeJob[] @@index([story_id, priority, sort_order]) @@index([sprint_id, status]) @@map("tasks") } model ClaudeJob { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String task Task @relation(fields: [task_id], references: [id], onDelete: Cascade) task_id String status ClaudeJobStatus @default(QUEUED) claimed_by_token ApiToken? @relation(fields: [claimed_by_token_id], references: [id], onDelete: SetNull) claimed_by_token_id String? claimed_at DateTime? started_at DateTime? finished_at DateTime? pushed_at DateTime? verify_result VerifyResult? plan_snapshot String? branch String? pr_url String? summary String? error String? retry_count Int @default(0) created_at DateTime @default(now()) updated_at DateTime @updatedAt @@index([user_id, status]) @@index([task_id, status]) @@index([status, claimed_at]) @@map("claude_jobs") } model ClaudeWorker { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String token ApiToken @relation(fields: [token_id], references: [id], onDelete: Cascade) token_id String product_id String? started_at DateTime @default(now()) last_seen_at DateTime @default(now()) @@unique([token_id]) @@index([user_id, last_seen_at]) @@map("claude_workers") } model ProductMember { id String @id @default(cuid()) product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String created_at DateTime @default(now()) @@unique([product_id, user_id]) @@index([user_id]) @@map("product_members") } model Todo { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String product Product? @relation(fields: [product_id], references: [id], onDelete: SetNull) product_id String? title String description String? @db.VarChar(2000) done Boolean @default(false) archived Boolean @default(false) created_at DateTime @default(now()) updated_at DateTime @updatedAt @@index([user_id, done, archived]) @@index([user_id, product_id]) @@map("todos") } model LoginPairing { id String @id @default(cuid()) secret_hash String desktop_token_hash String status String user_id String? user User? @relation(fields: [user_id], references: [id], onDelete: SetNull) desktop_ua String? @db.VarChar(255) desktop_ip String? @db.VarChar(45) created_at DateTime @default(now()) expires_at DateTime approved_at DateTime? consumed_at DateTime? @@index([expires_at]) @@index([status, expires_at]) @@map("login_pairings") } model ClaudeQuestion { id String @id @default(cuid()) story Story @relation(fields: [story_id], references: [id], onDelete: Cascade) story_id String task Task? @relation(fields: [task_id], references: [id], onDelete: SetNull) task_id String? product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String // gedenormaliseerd uit story.product_id voor SSE-filter asker User @relation("ClaudeQuestionAsker", fields: [asked_by], references: [id]) asked_by String // user_id van token-houder (= Claude-token) question String @db.Text options Json? // string[] voor multi-choice; null voor free-text status String // 'open' | 'answered' | 'cancelled' | 'expired' answer String? @db.Text answerer User? @relation("ClaudeQuestionAnswerer", fields: [answered_by], references: [id]) answered_by String? answered_at DateTime? created_at DateTime @default(now()) expires_at DateTime // ingesteld door MCP-tool, default now() + 24h @@index([story_id, status]) @@index([product_id, status]) @@index([status, expires_at]) @@map("claude_questions") }