Phase 2a: Prisma schema + shared schemas for v1.0 features

- New models: Attachment, Webhook, Notification, SavedView
- New fields: User.notificationPrefs (Json), indexes on Ticket
- post-push.sql manages the tsvector columns + GIN indexes + triggers for
  FTS on Ticket (title/overview/displayId) and Comment (body); Prisma can't
  express these
- package.json scripts: db:push and start:prod now chain `prisma db execute`
  against post-push.sql after `prisma db push`
- db:migrate script removed — project uses push workflow, not migrations
- Shared Zod schemas: attachment (25MB limit + mimetype allowlist), savedView,
  notification (prefs, mark-read, webhook CRUD)
- Shared type additions: Attachment, Notification, SavedView, Webhook,
  PaginatedResponse<T>
- Test fixtures updated for the new User.notificationPrefs column

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 15:52:16 -04:00
parent 77679922a8
commit 0806aec4a4
12 changed files with 475 additions and 24 deletions
+97 -20
View File
@@ -22,20 +22,24 @@ enum TicketStatus {
}
model User {
id String @id @default(cuid())
username String @unique
email String @unique
passwordHash String
displayName String
role Role @default(AGENT)
apiKey String? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id String @id @default(cuid())
username String @unique
email String @unique
passwordHash String
displayName String
role Role @default(AGENT)
apiKey String? @unique
notificationPrefs Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
assignedTickets Ticket[] @relation("AssignedTickets")
createdTickets Ticket[] @relation("CreatedTickets")
assignedTickets Ticket[] @relation("AssignedTickets")
createdTickets Ticket[] @relation("CreatedTickets")
comments Comment[]
auditLogs AuditLog[]
attachments Attachment[]
notifications Notification[]
savedViews SavedView[]
}
model Category {
@@ -83,13 +87,21 @@ model Ticket {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
category Category @relation(fields: [categoryId], references: [id])
type Type @relation(fields: [typeId], references: [id])
item Item @relation(fields: [itemId], references: [id])
assignee User? @relation("AssignedTickets", fields: [assigneeId], references: [id])
createdBy User @relation("CreatedTickets", fields: [createdById], references: [id])
comments Comment[]
auditLogs AuditLog[]
category Category @relation(fields: [categoryId], references: [id])
type Type @relation(fields: [typeId], references: [id])
item Item @relation(fields: [itemId], references: [id])
assignee User? @relation("AssignedTickets", fields: [assigneeId], references: [id])
createdBy User @relation("CreatedTickets", fields: [createdById], references: [id])
comments Comment[]
auditLogs AuditLog[]
attachments Attachment[]
notifications Notification[]
@@index([status])
@@index([severity])
@@index([assigneeId])
@@index([createdById])
@@index([createdAt])
}
model Comment {
@@ -99,8 +111,73 @@ model Comment {
authorId String
createdAt DateTime @default(now())
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
author User @relation(fields: [authorId], references: [id])
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
author User @relation(fields: [authorId], references: [id])
attachments Attachment[]
notifications Notification[]
@@index([ticketId])
}
model Attachment {
id String @id @default(cuid())
filename String
mimetype String
size Int
storagePath String
ticketId String?
commentId String?
uploadedById String
createdAt DateTime @default(now())
ticket Ticket? @relation(fields: [ticketId], references: [id], onDelete: Cascade)
comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
uploadedBy User @relation(fields: [uploadedById], references: [id])
@@index([ticketId])
@@index([commentId])
}
model Webhook {
id String @id @default(cuid())
name String
url String
events String[]
secret String
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Notification {
id String @id @default(cuid())
userId String
kind String
ticketId String?
commentId String?
data Json?
readAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
ticket Ticket? @relation(fields: [ticketId], references: [id], onDelete: Cascade)
comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
@@index([userId, readAt])
@@index([userId, createdAt])
}
model SavedView {
id String @id @default(cuid())
userId String
name String
filters Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, name])
}
model AuditLog {