@@ -0,0 +1,64 @@
|
||||
import Database from "better-sqlite3";
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import * as schema from "./schema";
|
||||
import path from "path";
|
||||
|
||||
const DB_PATH = path.join(process.cwd(), "data", "bingo.db");
|
||||
|
||||
function getDb() {
|
||||
const sqlite = new Database(DB_PATH);
|
||||
sqlite.pragma("journal_mode = WAL");
|
||||
sqlite.pragma("foreign_keys = ON");
|
||||
return sqlite;
|
||||
}
|
||||
|
||||
function initDatabase(sqlite: Database.Database) {
|
||||
sqlite.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
nickname TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
is_admin INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
expires_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS campaigns (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
grid_size INTEGER NOT NULL DEFAULT 5,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_by TEXT NOT NULL REFERENCES users(id),
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS bingo_items (
|
||||
id TEXT PRIMARY KEY,
|
||||
campaign_id TEXT NOT NULL REFERENCES campaigns(id) ON DELETE CASCADE,
|
||||
text TEXT NOT NULL,
|
||||
emoji TEXT NOT NULL DEFAULT '💀',
|
||||
sound_category TEXT NOT NULL DEFAULT 'horn',
|
||||
sound_url TEXT,
|
||||
grid_index INTEGER NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS marks (
|
||||
id TEXT PRIMARY KEY,
|
||||
campaign_id TEXT NOT NULL REFERENCES campaigns(id) ON DELETE CASCADE,
|
||||
item_id TEXT NOT NULL REFERENCES bingo_items(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_campaign_item_pos ON bingo_items(campaign_id, grid_index);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_mark ON marks(campaign_id, item_id);
|
||||
`);
|
||||
|
||||
try { sqlite.exec("ALTER TABLE bingo_items ADD COLUMN sound_url TEXT"); } catch {}
|
||||
}
|
||||
|
||||
const sqlite = getDb();
|
||||
initDatabase(sqlite);
|
||||
export const db = drizzle(sqlite, { schema });
|
||||
@@ -0,0 +1,48 @@
|
||||
import { sqliteTable, text, integer, uniqueIndex } from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const users = sqliteTable("users", {
|
||||
id: text("id").primaryKey(),
|
||||
nickname: text("nickname").unique().notNull(),
|
||||
passwordHash: text("password_hash").notNull(),
|
||||
isAdmin: integer("is_admin", { mode: "boolean" }).default(false).notNull(),
|
||||
createdAt: text("created_at").notNull(),
|
||||
});
|
||||
|
||||
export const sessions = sqliteTable("sessions", {
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
expiresAt: text("expires_at").notNull(),
|
||||
createdAt: text("created_at").notNull(),
|
||||
});
|
||||
|
||||
export const campaigns = sqliteTable("campaigns", {
|
||||
id: text("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
gridSize: integer("grid_size").notNull().default(5),
|
||||
status: text("status").notNull().default("active"),
|
||||
createdBy: text("created_by").notNull().references(() => users.id),
|
||||
createdAt: text("created_at").notNull(),
|
||||
});
|
||||
|
||||
export const bingoItems = sqliteTable("bingo_items", {
|
||||
id: text("id").primaryKey(),
|
||||
campaignId: text("campaign_id").notNull().references(() => campaigns.id, { onDelete: "cascade" }),
|
||||
text: text("text").notNull(),
|
||||
emoji: text("emoji").notNull().default("💀"),
|
||||
soundCategory: text("sound_category").notNull().default("horn"),
|
||||
soundUrl: text("sound_url"),
|
||||
gridIndex: integer("grid_index").notNull(),
|
||||
createdAt: text("created_at").notNull(),
|
||||
}, (table) => [
|
||||
uniqueIndex("idx_campaign_item_pos").on(table.campaignId, table.gridIndex),
|
||||
]);
|
||||
|
||||
export const marks = sqliteTable("marks", {
|
||||
id: text("id").primaryKey(),
|
||||
campaignId: text("campaign_id").notNull().references(() => campaigns.id, { onDelete: "cascade" }),
|
||||
itemId: text("item_id").notNull().references(() => bingoItems.id, { onDelete: "cascade" }),
|
||||
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
createdAt: text("created_at").notNull(),
|
||||
}, (table) => [
|
||||
uniqueIndex("idx_unique_mark").on(table.campaignId, table.itemId),
|
||||
]);
|
||||
Reference in New Issue
Block a user