Private
Public Access
1
0

Add 17 features: My Work view, card copy/numbering, calendar, bulk ops, custom fields, automations, webhooks, invites, import/export, and more

Features implemented across 6 batches:
- Batch A: Fix ColorPicker gradient sync, tenant custom logo, unarchive cards, board backgrounds
- Batch B: My Work view, card copy/duplicate, card numbering (BOARD-123)
- Batch C: Due date reminders (cron), card watching with notifications
- Batch D: Bulk card operations, column sorting, calendar view
- Batch E: Invite by link, board import/export (Pounce + Trello JSON)
- Batch F: Webhooks with HMAC signing, custom fields, automation rules engine

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-27 08:50:15 -05:00
parent 3a11ed01e9
commit 8c41b33313
54 changed files with 3656 additions and 82 deletions
+148 -21
View File
@@ -13,6 +13,8 @@ model Tenant {
id String @id @default(uuid()) @db.Uuid
name String
slug String @unique
logoUrl String? @map("logo_url")
logoText String? @map("logo_text")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@ -23,6 +25,8 @@ model Tenant {
categories Category[]
boardTemplates BoardTemplate[]
theme TenantTheme?
invites TenantInvite[]
webhooks Webhook[]
@@map("tenants")
}
@@ -70,6 +74,7 @@ model User {
comments Comment[]
activities Activity[]
notifications Notification[]
cardWatchers CardWatcher[]
@@unique([tenantId, email])
@@map("users")
@@ -108,14 +113,18 @@ model Board {
background String?
categoryId String? @map("category_id") @db.Uuid
archived Boolean @default(false)
cardPrefix String? @map("card_prefix")
nextCardNum Int @default(1) @map("next_card_num")
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[]
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
columns Column[]
members BoardMember[]
activities Activity[]
customFieldDefs CustomFieldDef[]
automationRules AutomationRule[]
@@map("boards")
}
@@ -150,23 +159,27 @@ model Column {
// ─── 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")
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)
dueReminderSent Boolean @default(false) @map("due_reminder_sent")
cardNumber Int? @map("card_number")
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[]
column Column @relation(fields: [columnId], references: [id], onDelete: Cascade)
assignees CardAssignee[]
labels CardLabel[]
checklists Checklist[]
comments Comment[]
attachments Attachment[]
activities Activity[]
watchers CardWatcher[]
customFieldValues CustomFieldValue[]
@@map("cards")
}
@@ -331,6 +344,120 @@ model BoardTemplate {
@@map("board_templates")
}
// ─── Card Watchers ───────────────────────────────────────────
model CardWatcher {
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_watchers")
}
// ─── Tenant Invites ──────────────────────────────────────────
model TenantInvite {
id String @id @default(uuid()) @db.Uuid
tenantId String @map("tenant_id") @db.Uuid
code String @unique
role TenantRole @default(MEMBER)
expiresAt DateTime @map("expires_at")
createdBy String @map("created_by") @db.Uuid
usedCount Int @default(0) @map("used_count")
maxUses Int? @map("max_uses")
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@map("tenant_invites")
}
// ─── Webhooks ────────────────────────────────────────────────
model Webhook {
id String @id @default(uuid()) @db.Uuid
tenantId String @map("tenant_id") @db.Uuid
url String
events String[]
secret String
active Boolean @default(true)
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@map("webhooks")
}
// ─── Custom Fields ───────────────────────────────────────────
enum CustomFieldType {
TEXT
NUMBER
DATE
SELECT
MULTI_SELECT
}
model CustomFieldDef {
id String @id @default(uuid()) @db.Uuid
boardId String @map("board_id") @db.Uuid
name String
type CustomFieldType
options Json? // For SELECT/MULTI_SELECT: string[]
position String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
values CustomFieldValue[]
@@map("custom_field_defs")
}
model CustomFieldValue {
id String @id @default(uuid()) @db.Uuid
cardId String @map("card_id") @db.Uuid
fieldId String @map("field_id") @db.Uuid
value String
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
field CustomFieldDef @relation(fields: [fieldId], references: [id], onDelete: Cascade)
@@unique([cardId, fieldId])
@@map("custom_field_values")
}
// ─── Automation Rules ────────────────────────────────────────
enum AutomationTrigger {
CARD_MOVED
DUE_PASSED
LABEL_ADDED
CARD_CREATED
}
enum AutomationAction {
ADD_LABEL
REMOVE_LABEL
ASSIGN
SET_DUE
MOVE_TO_COLUMN
}
model AutomationRule {
id String @id @default(uuid()) @db.Uuid
boardId String @map("board_id") @db.Uuid
trigger AutomationTrigger
triggerConfig Json? @map("trigger_config")
action AutomationAction
actionConfig Json? @map("action_config")
active Boolean @default(true)
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
@@map("automation_rules")
}
// ─── Tenant Branding & Theming ───────────────────────────────
model TenantTheme {