From b13e3c6abc24f883d65d672554981c9debe57e24 Mon Sep 17 00:00:00 2001 From: Gunshiz Date: Thu, 28 May 2026 19:20:07 +0700 Subject: [PATCH] feat: add form schemas, modularize delete components, and migrate authentication to a dedicated database schema --- app/form/admin/result/[id]/page.tsx | 2 +- app/form/admin/result/page.tsx | 3 +- app/form/client.tsx | 2 +- .../delete-buttons.tsx | 65 +++ components/delete-form-button.tsx | 71 --- db/schema.ts | 125 +--- db/schema/account.ts | 61 ++ db/schema/form.ts | 66 +++ drizzle/0005_handy_sally_floyd.sql | 46 ++ drizzle/meta/0005_snapshot.json | 537 ++++++++++++++++++ drizzle/meta/_journal.json | 7 + 11 files changed, 787 insertions(+), 198 deletions(-) rename app/form/admin/result/client.tsx => components/delete-buttons.tsx (65%) delete mode 100644 components/delete-form-button.tsx create mode 100644 db/schema/account.ts create mode 100644 db/schema/form.ts create mode 100644 drizzle/0005_handy_sally_floyd.sql create mode 100644 drizzle/meta/0005_snapshot.json diff --git a/app/form/admin/result/[id]/page.tsx b/app/form/admin/result/[id]/page.tsx index bcd4a18..bed404d 100644 --- a/app/form/admin/result/[id]/page.tsx +++ b/app/form/admin/result/[id]/page.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import { Button } from "@/components/ui/button"; import { ArrowLeft } from "lucide-react"; import SubmissionDetailClient from "./client"; -import { DeleteSubmissionButton } from "../client"; +import { DeleteSubmissionButton } from "@/components/delete-buttons"; interface SubmissionDetailPageProps { params: Promise<{ diff --git a/app/form/admin/result/page.tsx b/app/form/admin/result/page.tsx index 423f810..7204dde 100644 --- a/app/form/admin/result/page.tsx +++ b/app/form/admin/result/page.tsx @@ -4,7 +4,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { Button } from "@/components/ui/button"; import Link from "next/link"; import { Eye, Users } from "lucide-react"; -import { DeleteSubmissionButton, DeleteAllSubmissionsButton } from "./client"; +import { DeleteSubmissionButton, DeleteAllSubmissionsButton } from "@/components/delete-buttons"; export const dynamic = "force-dynamic"; @@ -73,7 +73,6 @@ export default async function ResultsListPage() { diff --git a/app/form/client.tsx b/app/form/client.tsx index 7ada417..c42fb58 100644 --- a/app/form/client.tsx +++ b/app/form/client.tsx @@ -37,7 +37,7 @@ export function FormLayoutClient({ diff --git a/app/form/admin/result/client.tsx b/components/delete-buttons.tsx similarity index 65% rename from app/form/admin/result/client.tsx rename to components/delete-buttons.tsx index 296c319..077bec8 100644 --- a/app/form/admin/result/client.tsx +++ b/components/delete-buttons.tsx @@ -3,9 +3,19 @@ import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Trash2, Loader2 } from "lucide-react"; +import { deleteForm } from "@/app/actions/form"; import { deleteSubmission, deleteAllSubmissions } from "@/app/actions/submissions"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, @@ -19,6 +29,61 @@ import { } from "@/components/ui/alert-dialog"; import { Spinner } from "@/components/ui/spinner"; +interface DeleteFormButtonProps { + formId: string; +} + +export function DeleteFormButton({ formId }: DeleteFormButtonProps) { + const [open, setOpen] = useState(false); + const [deleting, setDeleting] = useState(false); + + const handleDelete = async () => { + setDeleting(true); + try { + await deleteForm(formId); + toast.success("Form deleted successfully!"); + setOpen(false); + } catch (err: any) { + toast.error(err.message || "Failed to delete form."); + } finally { + setDeleting(false); + } + }; + + return ( + + + + + + + Are you absolutely sure? + + This action cannot be undone. This will permanently delete the form, all of its questions, and all submissions/answers. + + + + + + + + + ); +} + export function DeleteSubmissionButton({ id, redirectUrl }: { id: string, redirectUrl?: string }) { const [loading, setLoading] = useState(false); const router = useRouter(); diff --git a/components/delete-form-button.tsx b/components/delete-form-button.tsx deleted file mode 100644 index 6cdcdb1..0000000 --- a/components/delete-form-button.tsx +++ /dev/null @@ -1,71 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Trash2, Loader2 } from "lucide-react"; -import { deleteForm } from "@/app/actions/form"; -import { toast } from "sonner"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; - -interface DeleteFormButtonProps { - formId: string; -} - -export default function DeleteFormButton({ formId }: DeleteFormButtonProps) { - const [open, setOpen] = useState(false); - const [deleting, setDeleting] = useState(false); - - const handleDelete = async () => { - setDeleting(true); - try { - await deleteForm(formId); - toast.success("Form deleted successfully!"); - setOpen(false); - } catch (err: any) { - toast.error(err.message || "Failed to delete form."); - } finally { - setDeleting(false); - } - }; - - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the form, all of its questions, and all submissions/answers. - - - - - - - - - ); -} diff --git a/db/schema.ts b/db/schema.ts index a95a9a9..6e68719 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -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().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().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([]), -}); - -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 }[]>() - .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"; diff --git a/db/schema/account.ts b/db/schema/account.ts new file mode 100644 index 0000000..32e2a4b --- /dev/null +++ b/db/schema/account.ts @@ -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().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] })] +); diff --git a/db/schema/form.ts b/db/schema/form.ts new file mode 100644 index 0000000..1b80be4 --- /dev/null +++ b/db/schema/form.ts @@ -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().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([]), +}); + +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 }[]>() + .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(""), +}); diff --git a/drizzle/0005_handy_sally_floyd.sql b/drizzle/0005_handy_sally_floyd.sql new file mode 100644 index 0000000..929602d --- /dev/null +++ b/drizzle/0005_handy_sally_floyd.sql @@ -0,0 +1,46 @@ +CREATE SCHEMA "account"; +--> statement-breakpoint +CREATE TABLE "account"."account" ( + "userId" text NOT NULL, + "type" text NOT NULL, + "provider" text NOT NULL, + "providerAccountId" text NOT NULL, + "refresh_token" text, + "access_token" text, + "expires_at" integer, + "token_type" text, + "scope" text, + "id_token" text, + "session_state" text, + CONSTRAINT "account_provider_providerAccountId_pk" PRIMARY KEY("provider","providerAccountId") +); +--> statement-breakpoint +CREATE TABLE "account"."session" ( + "sessionToken" text PRIMARY KEY NOT NULL, + "userId" text NOT NULL, + "expires" timestamp NOT NULL +); +--> statement-breakpoint +CREATE TABLE "account"."user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text, + "email" text, + "emailVerified" timestamp, + "image" text, + "discord_id" text, + CONSTRAINT "user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "account"."verificationToken" ( + "identifier" text NOT NULL, + "token" text NOT NULL, + "expires" timestamp NOT NULL, + CONSTRAINT "verificationToken_identifier_token_pk" PRIMARY KEY("identifier","token") +); +--> statement-breakpoint +DROP TABLE "account" CASCADE;--> statement-breakpoint +DROP TABLE "session" CASCADE;--> statement-breakpoint +DROP TABLE "user" CASCADE;--> statement-breakpoint +DROP TABLE "verificationToken" CASCADE;--> statement-breakpoint +ALTER TABLE "account"."account" ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "account"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "account"."session" ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "account"."user"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..65e1358 --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,537 @@ +{ + "id": "9272e7bb-ff66-4aa5-8022-21ea3a0afb8f", + "prevId": "419e147d-270d-4731-afad-c4aaaef52951", + "version": "7", + "dialect": "postgresql", + "tables": { + "account.account": { + "name": "account", + "schema": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "schemaTo": "account", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": [ + "provider", + "providerAccountId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "account.session": { + "name": "session", + "schema": "account", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "schemaTo": "account", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "account.user": { + "name": "user", + "schema": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discord_id": { + "name": "discord_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "account.verificationToken": { + "name": "verificationToken", + "schema": "account", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": [ + "identifier", + "token" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.answer": { + "name": "answer", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "submission_id": { + "name": "submission_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "answer_submission_id_submission_id_fk": { + "name": "answer_submission_id_submission_id_fk", + "tableFrom": "answer", + "tableTo": "submission", + "columnsFrom": [ + "submission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "answer_question_id_question_id_fk": { + "name": "answer_question_id_question_id_fk", + "tableFrom": "answer", + "tableTo": "question", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.form": { + "name": "form", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_open": { + "name": "is_open", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "discord_webhook_url": { + "name": "discord_webhook_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discord_webhook_template": { + "name": "discord_webhook_template", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discord_webhook_update_template": { + "name": "discord_webhook_update_template", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.question": { + "name": "question", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "form_id": { + "name": "form_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "question_form_id_form_id_fk": { + "name": "question_form_id_form_id_fk", + "tableFrom": "question", + "tableTo": "form", + "columnsFrom": [ + "form_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.submission": { + "name": "submission", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "form_id": { + "name": "form_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_discord_id": { + "name": "user_discord_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "submitted_at": { + "name": "submitted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "edit_history": { + "name": "edit_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "submission_form_id_form_id_fk": { + "name": "submission_form_id_form_id_fk", + "tableFrom": "submission", + "tableTo": "form", + "columnsFrom": [ + "form_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": { + "account": "account" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 57f2ef7..a99a3be 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1779969012730, "tag": "0004_round_sebastian_shaw", "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1779969537666, + "tag": "0005_handy_sally_floyd", + "breakpoints": true } ] } \ No newline at end of file