import { pgSchema, pgTable, text, timestamp, boolean, integer, jsonb, index, customType, } from "drizzle-orm/pg-core"; import { uuidv7 } from "uuidv7"; export const formSchema = pgSchema("form"); export const bytea = customType<{ data: Buffer; driverData: Buffer; }>({ dataType() { return "bytea"; }, fromDriver(value) { return Buffer.isBuffer(value) ? value : Buffer.from(value); }, }); export const forms = formSchema.table("form", { id: text("id") .primaryKey() .$defaultFn(() => uuidv7()), title: text("title").notNull(), description: text("description"), createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), isOpen: boolean("is_open").notNull().default(true), closesAt: timestamp("closes_at", { mode: "date", withTimezone: true }), discordWebhookUrl: text("discord_webhook_url"), discordWebhookTemplate: text("discord_webhook_template"), discordWebhookUpdateTemplate: text("discord_webhook_update_template"), allowedRoles: jsonb("allowed_roles").$type().default([]), deniedRoles: jsonb("denied_roles").$type().default([]), allowMultipleSubmissions: boolean("allow_multiple_submissions").notNull().default(false), }); export type QuestionType = "text" | "textarea" | "radio" | "checkbox"; export type ImageAnswerMode = "none" | "optional" | "required"; export const questions = formSchema.table("question", { id: text("id") .primaryKey() .$defaultFn(() => uuidv7()), formId: text("form_id") .notNull() .references(() => forms.id, { onDelete: "cascade" }), type: text("type").$type().notNull().default("text"), label: text("label").notNull(), imageUrl: text("image_url"), displayOrder: integer("display_order").notNull().default(0), required: boolean("required").notNull().default(false), options: jsonb("options").$type().default([]), allowOther: boolean("allow_other").notNull().default(false), imageAnswerMode: text("image_answer_mode") .$type() .notNull() .default("none"), }); export const cdn = pgTable("cdn", { id: text("id") .primaryKey() .$defaultFn(() => uuidv7()), data: bytea("data").notNull(), name: text("name"), type: text("type").notNull(), size: integer("size").notNull(), ownerDiscordId: text("owner_discord_id"), createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), }); export const submissions = formSchema.table( "submission", { id: text("id") .primaryKey() .$defaultFn(() => uuidv7()), formId: text("form_id") .notNull() .references(() => forms.id, { onDelete: "cascade" }), userDiscordId: text("user_discord_id"), userName: text("user_name"), adminNote: text("admin_note"), submittedAt: timestamp("submitted_at", { mode: "date" }).defaultNow().notNull(), editHistory: jsonb("edit_history") .$type<{ editedAt: string; oldAnswers: Record }[]>() .default([]), }, (submission) => [ index("submission_form_submitted_at_id_idx").on( submission.formId, submission.submittedAt, submission.id ), ] ); export const answers = formSchema.table("answer", { id: text("id") .primaryKey() .$defaultFn(() => uuidv7()), submissionId: text("submission_id") .notNull() .references(() => submissions.id, { onDelete: "cascade" }), questionId: text("question_id") .notNull() .references(() => questions.id, { onDelete: "cascade" }), value: text("value").notNull().default(""), imageCdnId: text("image_cdn_id").references(() => cdn.id, { onUpdate: "cascade", onDelete: "set null", }), });