feat: add groupId support and redesign schedule GroupView hierarchy
- Add groupId field to ScheduleClass for admin-defined group identity - Add versioned DB migration system (replaces initTables) to prevent data loss - Redesign GroupView: Trainer → Class Type → Group → Datetimes hierarchy - Group datetimes by day, merge days with identical time sets - Auto-assign groupIds to legacy schedule entries in admin - Add mc_registrations CRUD to db.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
124
src/lib/db.ts
124
src/lib/db.ts
@@ -13,50 +13,104 @@ function getDb(): Database.Database {
|
||||
_db = new Database(DB_PATH);
|
||||
_db.pragma("journal_mode = WAL");
|
||||
_db.pragma("foreign_keys = ON");
|
||||
initTables(_db);
|
||||
runMigrations(_db);
|
||||
}
|
||||
return _db;
|
||||
}
|
||||
|
||||
function initTables(db: Database.Database) {
|
||||
// --- Migrations ---
|
||||
// Each migration has a unique version number and runs exactly once.
|
||||
// Add new migrations to the end of the array. Never modify existing ones.
|
||||
|
||||
interface Migration {
|
||||
version: number;
|
||||
name: string;
|
||||
up: (db: Database.Database) => void;
|
||||
}
|
||||
|
||||
const migrations: Migration[] = [
|
||||
{
|
||||
version: 1,
|
||||
name: "create_sections_and_team_members",
|
||||
up: (db) => {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS sections (
|
||||
key TEXT PRIMARY KEY,
|
||||
data TEXT NOT NULL,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS team_members (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
image TEXT NOT NULL,
|
||||
instagram TEXT,
|
||||
description TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
},
|
||||
},
|
||||
{
|
||||
version: 2,
|
||||
name: "add_team_bio_columns",
|
||||
up: (db) => {
|
||||
const cols = db.prepare("PRAGMA table_info(team_members)").all() as { name: string }[];
|
||||
const colNames = new Set(cols.map((c) => c.name));
|
||||
if (!colNames.has("experience")) db.exec("ALTER TABLE team_members ADD COLUMN experience TEXT");
|
||||
if (!colNames.has("victories")) db.exec("ALTER TABLE team_members ADD COLUMN victories TEXT");
|
||||
if (!colNames.has("education")) db.exec("ALTER TABLE team_members ADD COLUMN education TEXT");
|
||||
},
|
||||
},
|
||||
{
|
||||
version: 3,
|
||||
name: "create_mc_registrations",
|
||||
up: (db) => {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS mc_registrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
master_class_title TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
instagram TEXT NOT NULL,
|
||||
telegram TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function runMigrations(db: Database.Database) {
|
||||
// Create migrations tracking table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS sections (
|
||||
key TEXT PRIMARY KEY,
|
||||
data TEXT NOT NULL,
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mc_registrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
master_class_title TEXT NOT NULL,
|
||||
CREATE TABLE IF NOT EXISTS _migrations (
|
||||
version INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
instagram TEXT NOT NULL,
|
||||
telegram TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS team_members (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
image TEXT NOT NULL,
|
||||
instagram TEXT,
|
||||
description TEXT,
|
||||
experience TEXT,
|
||||
victories TEXT,
|
||||
education TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
updated_at TEXT DEFAULT (datetime('now'))
|
||||
applied_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
|
||||
// Migrate: add bio columns if missing
|
||||
const cols = db.prepare("PRAGMA table_info(team_members)").all() as { name: string }[];
|
||||
const colNames = new Set(cols.map((c) => c.name));
|
||||
if (!colNames.has("experience")) db.exec("ALTER TABLE team_members ADD COLUMN experience TEXT");
|
||||
if (!colNames.has("victories")) db.exec("ALTER TABLE team_members ADD COLUMN victories TEXT");
|
||||
if (!colNames.has("education")) db.exec("ALTER TABLE team_members ADD COLUMN education TEXT");
|
||||
const applied = new Set(
|
||||
(db.prepare("SELECT version FROM _migrations").all() as { version: number }[])
|
||||
.map((r) => r.version)
|
||||
);
|
||||
|
||||
const pending = migrations.filter((m) => !applied.has(m.version));
|
||||
if (pending.length === 0) return;
|
||||
|
||||
const insertMigration = db.prepare(
|
||||
"INSERT INTO _migrations (version, name) VALUES (?, ?)"
|
||||
);
|
||||
|
||||
const tx = db.transaction(() => {
|
||||
for (const m of pending) {
|
||||
m.up(db);
|
||||
insertMigration.run(m.version, m.name);
|
||||
}
|
||||
});
|
||||
tx();
|
||||
}
|
||||
|
||||
// --- Sections ---
|
||||
|
||||
Reference in New Issue
Block a user