generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ─── Multi-Tenancy ──────────────────────────────────────────── model Tenant { id String @id @default(uuid()) @db.Uuid name String slug String @unique createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") hostnames TenantHostname[] users User[] boards Board[] tags Tag[] categories Category[] boardTemplates BoardTemplate[] @@map("tenants") } model TenantHostname { id String @id @default(uuid()) @db.Uuid hostname String @unique tenantId String @map("tenant_id") @db.Uuid isPrimary Boolean @default(false) @map("is_primary") createdAt DateTime @default(now()) @map("created_at") tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) @@map("tenant_hostnames") } // ─── Users & Auth ───────────────────────────────────────────── enum GlobalRole { SITE_ADMIN USER } enum TenantRole { ADMIN MEMBER } model User { id String @id @default(uuid()) @db.Uuid tenantId String @map("tenant_id") @db.Uuid email String name String passwordHash String @map("password_hash") globalRole GlobalRole @default(USER) @map("global_role") tenantRole TenantRole @default(MEMBER) @map("tenant_role") avatarUrl String? @map("avatar_url") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) sessions Session[] boardMembers BoardMember[] cardAssignees CardAssignee[] comments Comment[] activities Activity[] notifications Notification[] @@unique([tenantId, email]) @@map("users") } model Session { id String @id @default(uuid()) @db.Uuid userId String @map("user_id") @db.Uuid expiresAt DateTime @map("expires_at") createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("sessions") } // ─── Boards ─────────────────────────────────────────────────── enum BoardVisibility { PUBLIC PRIVATE } enum BoardMemberRole { OWNER EDITOR VIEWER } model Board { id String @id @default(uuid()) @db.Uuid tenantId String @map("tenant_id") @db.Uuid title String description String? visibility BoardVisibility @default(PRIVATE) background String? categoryId String? @map("category_id") @db.Uuid archived Boolean @default(false) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) columns Column[] members BoardMember[] activities Activity[] @@map("boards") } model BoardMember { id String @id @default(uuid()) @db.Uuid boardId String @map("board_id") @db.Uuid userId String @map("user_id") @db.Uuid role BoardMemberRole @default(EDITOR) board Board @relation(fields: [boardId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([boardId, userId]) @@map("board_members") } // ─── Columns ────────────────────────────────────────────────── model Column { id String @id @default(uuid()) @db.Uuid boardId String @map("board_id") @db.Uuid title String position String // Fractional index (lexicographic) board Board @relation(fields: [boardId], references: [id], onDelete: Cascade) cards Card[] @@map("columns") } // ─── Cards ──────────────────────────────────────────────────── model Card { id String @id @default(uuid()) @db.Uuid columnId String @map("column_id") @db.Uuid title String description String? position String // Fractional index (lexicographic) dueDate DateTime? @map("due_date") archived Boolean @default(false) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") column Column @relation(fields: [columnId], references: [id], onDelete: Cascade) assignees CardAssignee[] labels CardLabel[] checklists Checklist[] comments Comment[] attachments Attachment[] activities Activity[] @@map("cards") } model CardAssignee { id String @id @default(uuid()) @db.Uuid cardId String @map("card_id") @db.Uuid userId String @map("user_id") @db.Uuid card Card @relation(fields: [cardId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([cardId, userId]) @@map("card_assignees") } // ─── Tags & Labels ──────────────────────────────────────────── model Tag { id String @id @default(uuid()) @db.Uuid tenantId String @map("tenant_id") @db.Uuid name String color String // hex color tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) cards CardLabel[] @@unique([tenantId, name]) @@map("tags") } model CardLabel { id String @id @default(uuid()) @db.Uuid cardId String @map("card_id") @db.Uuid tagId String @map("tag_id") @db.Uuid card Card @relation(fields: [cardId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) @@unique([cardId, tagId]) @@map("card_labels") } // ─── Categories ─────────────────────────────────────────────── model Category { id String @id @default(uuid()) @db.Uuid tenantId String @map("tenant_id") @db.Uuid name String tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) boards Board[] @@unique([tenantId, name]) @@map("categories") } // ─── Checklists ─────────────────────────────────────────────── model Checklist { id String @id @default(uuid()) @db.Uuid cardId String @map("card_id") @db.Uuid title String position String card Card @relation(fields: [cardId], references: [id], onDelete: Cascade) items ChecklistItem[] @@map("checklists") } model ChecklistItem { id String @id @default(uuid()) @db.Uuid checklistId String @map("checklist_id") @db.Uuid title String completed Boolean @default(false) position String checklist Checklist @relation(fields: [checklistId], references: [id], onDelete: Cascade) @@map("checklist_items") } // ─── Comments ───────────────────────────────────────────────── model Comment { id String @id @default(uuid()) @db.Uuid cardId String @map("card_id") @db.Uuid userId String @map("user_id") @db.Uuid content String createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") card Card @relation(fields: [cardId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("comments") } // ─── Attachments ────────────────────────────────────────────── model Attachment { id String @id @default(uuid()) @db.Uuid cardId String @map("card_id") @db.Uuid filename String filepath String mimetype String size Int createdAt DateTime @default(now()) @map("created_at") card Card @relation(fields: [cardId], references: [id], onDelete: Cascade) @@map("attachments") } // ─── Activity Log ───────────────────────────────────────────── model Activity { id String @id @default(uuid()) @db.Uuid boardId String @map("board_id") @db.Uuid cardId String? @map("card_id") @db.Uuid userId String @map("user_id") @db.Uuid action String // e.g. "card.created", "card.moved", "comment.added" metadata Json? // additional context createdAt DateTime @default(now()) @map("created_at") board Board @relation(fields: [boardId], references: [id], onDelete: Cascade) card Card? @relation(fields: [cardId], references: [id], onDelete: SetNull) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("activities") } // ─── Notifications ──────────────────────────────────────────── model Notification { id String @id @default(uuid()) @db.Uuid userId String @map("user_id") @db.Uuid type String // e.g. "assigned", "mentioned", "due_soon" title String body String? link String? read Boolean @default(false) createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("notifications") } // ─── Board Templates ────────────────────────────────────────── model BoardTemplate { id String @id @default(uuid()) @db.Uuid tenantId String? @map("tenant_id") @db.Uuid // null = system template name String description String? template Json // JSON structure with columns/cards tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade) @@map("board_templates") }