import { pgTable, text, timestamp, integer, index, pgEnum } from "drizzle-orm/pg-core"; import { relations } from "drizzle-orm"; import { users } from "./users"; export const supportTicketTypeEnum = pgEnum("support_ticket_type", ["bug", "suggestion", "question"]); export const supportTicketStatusEnum = pgEnum("support_ticket_status", ["open", "triaged", "closed"]); export const supportCommentAuthorTypeEnum = pgEnum("support_comment_author_type", ["user", "admin", "gitea"]); export const supportTickets = pgTable("support_tickets", { id: text("id").primaryKey(), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), type: supportTicketTypeEnum("type").notNull(), title: text("title").notNull(), description: text("description").notNull(), status: supportTicketStatusEnum("status").notNull().default("open"), giteaIssueUrl: text("gitea_issue_url"), giteaIssueNumber: integer("gitea_issue_number"), giteaError: text("gitea_error"), createdAt: timestamp("created_at").notNull().defaultNow(), updatedAt: timestamp("updated_at").notNull().defaultNow(), }, (t) => [ index("support_tickets_user_idx").on(t.userId, t.createdAt), index("support_tickets_status_idx").on(t.status, t.createdAt), index("support_tickets_gitea_issue_idx").on(t.giteaIssueNumber), ]); // A comment thread mirrored both ways with the linked Gitea issue: rows with // authorType "user"/"admin" originate here and get pushed to Gitea (their // giteaCommentId is filled in once posted); rows with authorType "gitea" // arrived via the inbound webhook. giteaCommentId also doubles as the // dedupe key so a comment Epicure just posted doesn't get re-inserted when // its own webhook delivery arrives back. export const supportTicketComments = pgTable("support_ticket_comments", { id: text("id").primaryKey(), ticketId: text("ticket_id").notNull().references(() => supportTickets.id, { onDelete: "cascade" }), authorType: supportCommentAuthorTypeEnum("author_type").notNull(), authorId: text("author_id"), body: text("body").notNull(), giteaCommentId: integer("gitea_comment_id"), createdAt: timestamp("created_at").notNull().defaultNow(), }, (t) => [ index("support_ticket_comments_ticket_idx").on(t.ticketId, t.createdAt), index("support_ticket_comments_gitea_comment_idx").on(t.giteaCommentId), ]); // Dedupes inbound Gitea webhook deliveries by their X-Gitea-Delivery id, // same pattern as processedStripeEvents. export const processedGiteaEvents = pgTable("processed_gitea_events", { id: text("id").primaryKey(), createdAt: timestamp("created_at").notNull().defaultNow(), }); export const supportTicketAttachments = pgTable("support_ticket_attachments", { id: text("id").primaryKey(), ticketId: text("ticket_id").notNull().references(() => supportTickets.id, { onDelete: "cascade" }), storageKey: text("storage_key").notNull(), contentType: text("content_type").notNull(), sizeBytes: integer("size_bytes").notNull(), createdAt: timestamp("created_at").notNull().defaultNow(), }, (t) => [ index("support_ticket_attachments_ticket_idx").on(t.ticketId), ]); export const supportTicketsRelations = relations(supportTickets, ({ one, many }) => ({ user: one(users, { fields: [supportTickets.userId], references: [users.id] }), attachments: many(supportTicketAttachments), comments: many(supportTicketComments), })); export const supportTicketAttachmentsRelations = relations(supportTicketAttachments, ({ one }) => ({ ticket: one(supportTickets, { fields: [supportTicketAttachments.ticketId], references: [supportTickets.id] }), })); export const supportTicketCommentsRelations = relations(supportTicketComments, ({ one }) => ({ ticket: one(supportTickets, { fields: [supportTicketComments.ticketId], references: [supportTickets.id] }), }));