feat: add form schemas, modularize delete components, and migrate authentication to a dedicated database schema
This commit is contained in:
+2
-123
@@ -1,123 +1,2 @@
|
||||
import {
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
boolean,
|
||||
integer,
|
||||
jsonb,
|
||||
primaryKey,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import type { AdapterAccount } from "next-auth/adapters";
|
||||
|
||||
// ─── Auth.js tables ────────────────────────────────────────────────────────
|
||||
|
||||
export const users = pgTable("user", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
name: text("name"),
|
||||
email: text("email").unique(),
|
||||
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||
image: text("image"),
|
||||
discordId: text("discord_id"),
|
||||
});
|
||||
|
||||
export const accounts = pgTable(
|
||||
"account",
|
||||
{
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||
provider: text("provider").notNull(),
|
||||
providerAccountId: text("providerAccountId").notNull(),
|
||||
refresh_token: text("refresh_token"),
|
||||
access_token: text("access_token"),
|
||||
expires_at: integer("expires_at"),
|
||||
token_type: text("token_type"),
|
||||
scope: text("scope"),
|
||||
id_token: text("id_token"),
|
||||
session_state: text("session_state"),
|
||||
},
|
||||
(account) => [
|
||||
primaryKey({ columns: [account.provider, account.providerAccountId] }),
|
||||
]
|
||||
);
|
||||
|
||||
export const sessions = pgTable("session", {
|
||||
sessionToken: text("sessionToken").primaryKey(),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
});
|
||||
|
||||
export const verificationTokens = pgTable(
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: text("identifier").notNull(),
|
||||
token: text("token").notNull(),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
},
|
||||
(vt) => [primaryKey({ columns: [vt.identifier, vt.token] })]
|
||||
);
|
||||
|
||||
// ─── Form tables ────────────────────────────────────────────────────────────
|
||||
|
||||
export const forms = pgTable("form", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
||||
isOpen: boolean("is_open").notNull().default(true),
|
||||
discordWebhookUrl: text("discord_webhook_url"),
|
||||
discordWebhookTemplate: text("discord_webhook_template"),
|
||||
discordWebhookUpdateTemplate: text("discord_webhook_update_template"),
|
||||
});
|
||||
|
||||
export type QuestionType = "text" | "textarea" | "radio" | "checkbox";
|
||||
|
||||
export const questions = pgTable("question", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
formId: text("form_id")
|
||||
.notNull()
|
||||
.references(() => forms.id, { onDelete: "cascade" }),
|
||||
type: text("type").$type<QuestionType>().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<string[]>().default([]),
|
||||
});
|
||||
|
||||
export const submissions = pgTable("submission", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
formId: text("form_id")
|
||||
.notNull()
|
||||
.references(() => forms.id, { onDelete: "cascade" }),
|
||||
userDiscordId: text("user_discord_id"),
|
||||
userName: text("user_name"),
|
||||
submittedAt: timestamp("submitted_at", { mode: "date" }).defaultNow().notNull(),
|
||||
editHistory: jsonb("edit_history")
|
||||
.$type<{ editedAt: string; oldAnswers: Record<string, string> }[]>()
|
||||
.default([]),
|
||||
});
|
||||
|
||||
export const answers = pgTable("answer", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
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(""),
|
||||
});
|
||||
export * from "./schema/account";
|
||||
export * from "./schema/form";
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
pgSchema,
|
||||
text,
|
||||
timestamp,
|
||||
integer,
|
||||
primaryKey,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import type { AdapterAccount } from "next-auth/adapters";
|
||||
|
||||
export const accountSchema = pgSchema("account");
|
||||
|
||||
export const users = accountSchema.table("user", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
name: text("name"),
|
||||
email: text("email").unique(),
|
||||
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||
image: text("image"),
|
||||
discordId: text("discord_id"),
|
||||
});
|
||||
|
||||
export const accounts = accountSchema.table(
|
||||
"account",
|
||||
{
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||
provider: text("provider").notNull(),
|
||||
providerAccountId: text("providerAccountId").notNull(),
|
||||
refresh_token: text("refresh_token"),
|
||||
access_token: text("access_token"),
|
||||
expires_at: integer("expires_at"),
|
||||
token_type: text("token_type"),
|
||||
scope: text("scope"),
|
||||
id_token: text("id_token"),
|
||||
session_state: text("session_state"),
|
||||
},
|
||||
(account) => [
|
||||
primaryKey({ columns: [account.provider, account.providerAccountId] }),
|
||||
]
|
||||
);
|
||||
|
||||
export const sessions = accountSchema.table("session", {
|
||||
sessionToken: text("sessionToken").primaryKey(),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
});
|
||||
|
||||
export const verificationTokens = accountSchema.table(
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: text("identifier").notNull(),
|
||||
token: text("token").notNull(),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
},
|
||||
(vt) => [primaryKey({ columns: [vt.identifier, vt.token] })]
|
||||
);
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
boolean,
|
||||
integer,
|
||||
jsonb,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const forms = pgTable("form", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
||||
isOpen: boolean("is_open").notNull().default(true),
|
||||
discordWebhookUrl: text("discord_webhook_url"),
|
||||
discordWebhookTemplate: text("discord_webhook_template"),
|
||||
discordWebhookUpdateTemplate: text("discord_webhook_update_template"),
|
||||
});
|
||||
|
||||
export type QuestionType = "text" | "textarea" | "radio" | "checkbox";
|
||||
|
||||
export const questions = pgTable("question", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
formId: text("form_id")
|
||||
.notNull()
|
||||
.references(() => forms.id, { onDelete: "cascade" }),
|
||||
type: text("type").$type<QuestionType>().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<string[]>().default([]),
|
||||
});
|
||||
|
||||
export const submissions = pgTable("submission", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
formId: text("form_id")
|
||||
.notNull()
|
||||
.references(() => forms.id, { onDelete: "cascade" }),
|
||||
userDiscordId: text("user_discord_id"),
|
||||
userName: text("user_name"),
|
||||
submittedAt: timestamp("submitted_at", { mode: "date" }).defaultNow().notNull(),
|
||||
editHistory: jsonb("edit_history")
|
||||
.$type<{ editedAt: string; oldAnswers: Record<string, string> }[]>()
|
||||
.default([]),
|
||||
});
|
||||
|
||||
export const answers = pgTable("answer", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
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(""),
|
||||
});
|
||||
Reference in New Issue
Block a user