feat: add MariaDB schema migration and runner
This commit is contained in:
parent
be38cdc3dc
commit
ed45a84e5a
2 changed files with 176 additions and 0 deletions
133
server/migrations/001_initial_schema.sql
Normal file
133
server/migrations/001_initial_schema.sql
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
email VARCHAR(255) NOT NULL,
|
||||||
|
username VARCHAR(100) NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
avatar_url VARCHAR(500) DEFAULT NULL,
|
||||||
|
created_at DATETIME DEFAULT NOW(),
|
||||||
|
UNIQUE KEY uq_users_email (email)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS campaigns (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS campaign_members (
|
||||||
|
campaign_id INT UNSIGNED NOT NULL,
|
||||||
|
user_id INT UNSIGNED NOT NULL,
|
||||||
|
role ENUM('dm', 'player') NOT NULL,
|
||||||
|
joined_at DATETIME DEFAULT NOW(),
|
||||||
|
PRIMARY KEY (campaign_id, user_id),
|
||||||
|
FOREIGN KEY (campaign_id) REFERENCES campaigns(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS campaign_invites (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
campaign_id INT UNSIGNED NOT NULL,
|
||||||
|
token VARCHAR(64) NOT NULL,
|
||||||
|
created_by INT UNSIGNED NOT NULL,
|
||||||
|
expires_at DATETIME DEFAULT NULL,
|
||||||
|
created_at DATETIME DEFAULT NOW(),
|
||||||
|
UNIQUE KEY uq_invites_token (token),
|
||||||
|
FOREIGN KEY (campaign_id) REFERENCES campaigns(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS characters (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
campaign_id INT UNSIGNED NOT NULL,
|
||||||
|
user_id INT UNSIGNED DEFAULT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
class VARCHAR(100) NOT NULL DEFAULT 'Fighter',
|
||||||
|
ancestry VARCHAR(100) NOT NULL DEFAULT 'Human',
|
||||||
|
level INT NOT NULL DEFAULT 1,
|
||||||
|
xp INT NOT NULL DEFAULT 0,
|
||||||
|
hp_current INT NOT NULL DEFAULT 0,
|
||||||
|
hp_max INT NOT NULL DEFAULT 0,
|
||||||
|
ac INT NOT NULL DEFAULT 10,
|
||||||
|
alignment VARCHAR(50) NOT NULL DEFAULT 'Neutral',
|
||||||
|
title VARCHAR(255) DEFAULT '',
|
||||||
|
notes TEXT DEFAULT '',
|
||||||
|
background VARCHAR(255) DEFAULT '',
|
||||||
|
deity VARCHAR(255) DEFAULT '',
|
||||||
|
languages VARCHAR(500) DEFAULT '',
|
||||||
|
gp INT NOT NULL DEFAULT 0,
|
||||||
|
sp INT NOT NULL DEFAULT 0,
|
||||||
|
cp INT NOT NULL DEFAULT 0,
|
||||||
|
gear_slots_max INT NOT NULL DEFAULT 10,
|
||||||
|
overrides TEXT DEFAULT '{}',
|
||||||
|
color VARCHAR(100) DEFAULT '',
|
||||||
|
luck_token TINYINT NOT NULL DEFAULT 1,
|
||||||
|
torch_lit_at DATETIME DEFAULT NULL,
|
||||||
|
FOREIGN KEY (campaign_id) REFERENCES campaigns(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS character_stats (
|
||||||
|
character_id INT UNSIGNED NOT NULL,
|
||||||
|
stat_name VARCHAR(10) NOT NULL,
|
||||||
|
value INT NOT NULL DEFAULT 10,
|
||||||
|
PRIMARY KEY (character_id, stat_name),
|
||||||
|
FOREIGN KEY (character_id) REFERENCES characters(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS character_gear (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
character_id INT UNSIGNED NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
type VARCHAR(50) NOT NULL DEFAULT 'gear',
|
||||||
|
slot_count INT NOT NULL DEFAULT 1,
|
||||||
|
properties TEXT DEFAULT '{}',
|
||||||
|
effects TEXT DEFAULT '{}',
|
||||||
|
game_item_id INT UNSIGNED DEFAULT NULL,
|
||||||
|
FOREIGN KEY (character_id) REFERENCES characters(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS character_talents (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
character_id INT UNSIGNED NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT DEFAULT '',
|
||||||
|
effect TEXT DEFAULT '{}',
|
||||||
|
game_talent_id INT UNSIGNED DEFAULT NULL,
|
||||||
|
FOREIGN KEY (character_id) REFERENCES characters(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS game_items (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
type VARCHAR(50) NOT NULL,
|
||||||
|
slot_count INT NOT NULL DEFAULT 1,
|
||||||
|
effects TEXT DEFAULT '{}',
|
||||||
|
properties TEXT DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS game_talents (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
source VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT DEFAULT '',
|
||||||
|
effect TEXT DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS roll_log (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
campaign_id INT UNSIGNED NOT NULL,
|
||||||
|
character_id INT UNSIGNED DEFAULT NULL,
|
||||||
|
character_name VARCHAR(255) NOT NULL DEFAULT 'Roll',
|
||||||
|
type VARCHAR(50) NOT NULL DEFAULT 'custom',
|
||||||
|
label VARCHAR(255) NOT NULL,
|
||||||
|
dice_expression VARCHAR(255) NOT NULL,
|
||||||
|
rolls TEXT NOT NULL DEFAULT '[]',
|
||||||
|
modifier INT NOT NULL DEFAULT 0,
|
||||||
|
total INT NOT NULL DEFAULT 0,
|
||||||
|
advantage TINYINT NOT NULL DEFAULT 0,
|
||||||
|
disadvantage TINYINT NOT NULL DEFAULT 0,
|
||||||
|
nat20 TINYINT NOT NULL DEFAULT 0,
|
||||||
|
character_color VARCHAR(100) DEFAULT '',
|
||||||
|
created_at DATETIME DEFAULT NOW(),
|
||||||
|
FOREIGN KEY (campaign_id) REFERENCES campaigns(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
43
server/src/migrate.ts
Normal file
43
server/src/migrate.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import type { Pool } from "mysql2/promise";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const MIGRATIONS_DIR = path.join(__dirname, "..", "..", "migrations");
|
||||||
|
|
||||||
|
export async function runMigrations(pool: Pool): Promise<void> {
|
||||||
|
await pool.execute(`
|
||||||
|
CREATE TABLE IF NOT EXISTS _migrations (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
filename VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
run_at DATETIME DEFAULT NOW()
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
const files = fs
|
||||||
|
.readdirSync(MIGRATIONS_DIR)
|
||||||
|
.filter((f) => f.endsWith(".sql"))
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const [rows] = await pool.execute<import("mysql2").RowDataPacket[]>(
|
||||||
|
"SELECT id FROM _migrations WHERE filename = ?",
|
||||||
|
[file]
|
||||||
|
);
|
||||||
|
if (rows.length > 0) continue;
|
||||||
|
|
||||||
|
const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, file), "utf-8");
|
||||||
|
const statements = sql
|
||||||
|
.split(";")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter((s) => s.length > 0 && !s.startsWith("--"));
|
||||||
|
|
||||||
|
for (const stmt of statements) {
|
||||||
|
await pool.execute(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
await pool.execute("INSERT INTO _migrations (filename) VALUES (?)", [file]);
|
||||||
|
console.log(`Migration applied: ${file}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue