Add webm_path and webp_path columns to ghosts (idempotent migration)

ALTER TABLE guarded by a column-existence check so existing production
databases gain the columns without data loss.
This commit is contained in:
2026-06-18 14:20:50 +10:00
parent 9866e44445
commit 22983caa18
+1 -74
View File
@@ -1,74 +1 @@
import Database from 'better-sqlite3'; aW1wb3J0IERhdGFiYXNlIGZyb20gJ2JldHRlci1zcWxpdGUzJzsKaW1wb3J0IHsgbWtkaXJTeW5jIH0gZnJvbSAnbm9kZTpmcyc7CmltcG9ydCB7IGRpcm5hbWUsIGpvaW4gfSBmcm9tICdub2RlOnBhdGgnOwppbXBvcnQgeyBmaWxlVVJMVG9QYXRoIH0gZnJvbSAnbm9kZTp1cmwnOwoKY29uc3QgX19kaXJuYW1lID0gZGlybmFtZShmaWxlVVJMVG9QYXRoKGltcG9ydC5tZXRhLnVybCkpOwpjb25zdCBEQl9QQVRIID0gam9pbihfX2Rpcm5hbWUsICduZXdidXJ5LnNxbGl0ZScpOwoKbWtkaXJTeW5jKF9fZGlybmFtZSwgeyByZWN1cnNpdmU6IHRydWUgfSk7Cgpjb25zdCBkYiA9IG5ldyBEYXRhYmFzZShEQl9QQVRIKTsKZGIucHJhZ21hKCdqb3VybmFsX21vZGUgPSBXQUwnKTsKZGIucHJhZ21hKCdmb3JlaWduX2tleXMgPSBPTicpOwoKZGIuZXhlYyhgCkNSRUFURSBUQUJMRSBJRiBOT1QgRVhJU1RTIHVzZXJzICgKICBpZCAgICAgICAgICAgIElOVEVHRVIgUFJJTUFSWSBLRVkgQVVUT0lOQ1JFTUVOVCwKICB1c2VybmFtZSAgICAgIFRFWFQgVU5JUVVFIE5PVCBOVUxMLAogIHBhc3N3b3JkX2hhc2ggVEVYVCBOT1QgTlVMTCwKICByb2xlICAgICAgICAgIFRFWFQgTk9UIE5VTEwgREVGQVVMVCAnYWRtaW4nLAogIGNyZWF0ZWRfYXQgICAgVEVYVCBOT1QgTlVMTCBERUZBVUxUIChkYXRldGltZSgnbm93JykpCik7CgpDUkVBVEUgVEFCTEUgSUYgTk9UIEVYSVNUUyBhYmlsaXRpZXMgKAogIGlkICAgICAgICBJTlRFR0VSIFBSSU1BUlkgS0VZIEFVVE9JTkNSRU1FTlQsCiAgbmFtZSAgICAgIFRFWFQgVU5JUVVFIE5PVCBOVUxMLAogIGtpbmQgICAgICBURVhUIE5PVCBOVUxMIERFRkFVTFQgJ2NvbW1vbicsICAgLS0gY29tbW9uIHwgYm9zcwogIGNoYXJnZXMgICBJTlRFR0VSLAogIGNvb2xkb3duICBURVhULAogIGVmZmVjdCAgICBURVhUCik7CgpDUkVBVEUgVEFCTEUgSUYgTk9UIEVYSVNUUyBnaG9zdHMgKAogIGlkICAgICAgICAgICBJTlRFR0VSIFBSSU1BUlkgS0VZIEFVVE9JTkNSRU1FTlQsCiAgbmFtZSAgICAgICAgIFRFWFQgTk9UIE5VTEwsCiAgZGlzcGxheV9uYW1lIFRFWFQsICAgICAgICAgICAgICAgICAgICAgICAgICAgLS0gc2hvd24gb24gdGhlIGxvY2stb24gbGFiZWw7IGZhbGxzIGJhY2sgdG8gbmFtZQogIHR5cGUgICAgICAgICBURVhUIE5PVCBOVUxMLCAgICAgICAgICAgICAgICAgIC0tIHJlZCB8IHllbGxvdyB8IGJsdWUKICByYXJpdHkgICAgICAgSU5URUdFUiBOT1QgTlVMTCwgICAgICAgICAgICAgICAtLSAxLi40IChzdGFycykKICBzcGVlZCAgICAgICAgSU5URUdFUiBOT1QgTlVMTCBERUZBVUxUIDAsCiAgcmFuZ2UgICAgICAgIElOVEVHRVIgTk9UIE5VTEwgREVGQVVMVCAwLAogIGNoYXJnZV9zaG90ICBJTlRFR0VSIE5PVCBOVUxMIERFRkFVTFQgMCwKICBoZWFsdGggICAgICAgSU5URUdFUiBOT1QgTlVMTCwKICBkYW1hZ2UgICAgICAgSU5URUdFUiBOT1QgTlVMTCwKICBhYmlsaXR5ICAgICAgVEVYVCwKICBpc19ib3NzICAgICAgSU5URUdFUiBOT1QgTlVMTCBERUZBVUxUIDAsCiAgc2V0X251bWJlciAgIFRFWFQsCiAgc2V0X25hbWUgICAgIFRFWFQsCiAgaW1hZ2VfcGF0aCAgIFRFWFQsICAgICAgICAgICAgICAgICAgICAgICAgICAgLS0gdXBsb2FkZWQgR0lGL1BORyBiaWxsYm9hcmQgKG51bGxhYmxlKQogIGVuYWJsZWQgICAgICBJTlRFR0VSIE5PVCBOVUxMIERFRkFVTFQgMSwKICBjcmVhdGVkX2F0ICAgVEVYVCBOT1QgTlVMTCBERUZBVUxUIChkYXRldGltZSgnbm93JykpCik7CkNSRUFURSBJTkRFWCBJRiBOT1QgRVhJU1RTIGlkeF9naG9zdHNfdHlwZSAgIE9OIGdob3N0cyh0eXBlKTsKQ1JFQVRFIElOREVYIElGIE5PVCBFWElTVFMgaWR4X2dob3N0c19yYXJpdHkgT04gZ2hvc3RzKHJhcml0eSk7CkNSRUFURSBJTkRFWCBJRiBOT1QgRVhJU1RTIGlkeF9naG9zdHNfYm9zcyAgIE9OIGdob3N0cyhpc19ib3NzKTsKCkNSRUFURSBUQUJMRSBJRiBOT1QgRVhJU1RTIHNldHMgKAogIGlkICAgICAgICAgIElOVEVHRVIgUFJJTUFSWSBLRVkgQVVUT0lOQ1JFTUVOVCwKICBjb2RlICAgICAgICBURVhUIFVOSVFVRSBOT1QgTlVMTCwgICAgICAgICAgICAtLSBRUiBwYXlsb2FkIC8gc2NhbiBjb2RlCiAgc2V0X251bWJlciAgVEVYVCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgLS0gZS5nLiA3MDQxOSAocmVmZXJlbmNlIG9ubHkpCiAgc2V0X25hbWUgICAgVEVYVCBOT1QgTlVMTCwKICBib3NzX2dob3N0X2lkIElOVEVHRVIgUkVGRVJFTkNFUyBnaG9zdHMoaWQpIE9OIERFTEVURSBTRVQgTlVMTCwKICBlbmFibGVkICAgICBJTlRFR0VSIE5PVCBOVUxMIERFRkFVTFQgMSwKICBjcmVhdGVkX2F0ICBURVhUIE5PVCBOVUxMIERFRkFVTFQgKGRhdGV0aW1lKCdub3cnKSkKKTsKCkNSRUFURSBUQUJMRSBJRiBOT1QgRVhJU1RTIHNldF9naG9zdHMgKAogIHNldF9pZCAgIElOVEVHRVIgTk9UIE5VTEwgUkVGRVJFTkNFUyBzZXRzKGlkKSBPTiBERUxFVEUgQ0FTQ0FERSwKICBnaG9zdF9pZCBJTlRFR0VSIE5PVCBOVUxMIFJFRkVSRU5DRVMgZ2hvc3RzKGlkKSBPTiBERUxFVEUgQ0FTQ0FERSwKICBQUklNQVJZIEtFWSAoc2V0X2lkLCBnaG9zdF9pZCkKKTsKYCk7CgovKiAtLS0tIE1pZ3JhdGlvbnMgKGlkZW1wb3RlbnQpIC0tLS0KICogQWRkIHRyYW5zcGFyZW50LXZpZGVvIHNwcml0ZSBjb2x1bW5zIHRvIHRoZSBleGlzdGluZyBnaG9zdHMgdGFibGUgd2l0aG91dAogKiBkcm9wcGluZyBkYXRhLiB3ZWJtX3BhdGggaXMgdGhlIFZQOSthbHBoYSBiaWxsYm9hcmQ7IHdlYnBfcGF0aCBpcyB0aGUKICogYW5pbWF0ZWQtV2ViUCBmYWxsYmFjayB1c2VkIHdoZXJlIFZQOSBhbHBoYSBpc24ndCBzdXBwb3J0ZWQgKGUuZy4gaU9TKS4KICovCmZ1bmN0aW9uIGFkZENvbHVtbklmTWlzc2luZyh0YWJsZSwgY29sdW1uLCBkZWNsKSB7CiAgY29uc3QgY29scyA9IGRiLnByZXBhcmUoYFBSQUdNQSB0YWJsZV9pbmZvKCR7dGFibGV9KWApLmFsbCgpOwogIGlmICghY29scy5zb21lKChjKSA9PiBjLm5hbWUgPT09IGNvbHVtbikpIHsKICAgIGRiLmV4ZWMoYEFMVEVSIFRBQkxFICR7dGFibGV9IEFERCBDT0xVTU4gJHtjb2x1bW59ICR7ZGVjbH1gKTsKICB9Cn0KYWRkQ29sdW1uSWZNaXNzaW5nKCdnaG9zdHMnLCAnd2VibV9wYXRoJywgJ1RFWFQnKTsgLy8gVlA5K2FscGhhIGJpbGxib2FyZCAobnVsbGFibGUpCmFkZENvbHVtbklmTWlzc2luZygnZ2hvc3RzJywgJ3dlYnBfcGF0aCcsICdURVhUJyk7IC8vIGFuaW1hdGVkLVdlYlAgZmFsbGJhY2sgKG51bGxhYmxlKQoKZXhwb3J0IGRlZmF1bHQgZGI7CmV4cG9ydCB7IERCX1BBVEggfTsK
import { mkdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DB_PATH = join(__dirname, 'newbury.sqlite');
mkdirSync(__dirname, { recursive: true });
const db = new Database(DB_PATH);
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'admin',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS abilities (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
kind TEXT NOT NULL DEFAULT 'common', -- common | boss
charges INTEGER,
cooldown TEXT,
effect TEXT
);
CREATE TABLE IF NOT EXISTS ghosts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
display_name TEXT, -- shown on the lock-on label; falls back to name
type TEXT NOT NULL, -- red | yellow | blue
rarity INTEGER NOT NULL, -- 1..4 (stars)
speed INTEGER NOT NULL DEFAULT 0,
range INTEGER NOT NULL DEFAULT 0,
charge_shot INTEGER NOT NULL DEFAULT 0,
health INTEGER NOT NULL,
damage INTEGER NOT NULL,
ability TEXT,
is_boss INTEGER NOT NULL DEFAULT 0,
set_number TEXT,
set_name TEXT,
image_path TEXT, -- uploaded GIF/PNG billboard (nullable)
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_ghosts_type ON ghosts(type);
CREATE INDEX IF NOT EXISTS idx_ghosts_rarity ON ghosts(rarity);
CREATE INDEX IF NOT EXISTS idx_ghosts_boss ON ghosts(is_boss);
CREATE TABLE IF NOT EXISTS sets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT UNIQUE NOT NULL, -- QR payload / scan code
set_number TEXT, -- e.g. 70419 (reference only)
set_name TEXT NOT NULL,
boss_ghost_id INTEGER REFERENCES ghosts(id) ON DELETE SET NULL,
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS set_ghosts (
set_id INTEGER NOT NULL REFERENCES sets(id) ON DELETE CASCADE,
ghost_id INTEGER NOT NULL REFERENCES ghosts(id) ON DELETE CASCADE,
PRIMARY KEY (set_id, ghost_id)
);
`);
export default db;
export { DB_PATH };