Files
dwroller/scripts/migrate-to-sqlite.js
Alex 9d53410aab Add migration script to import JSON data into SQLite database
- Created a new script `migrate-to-sqlite.js` to migrate data from JSON files into a SQLite database.
- Implemented functions to read JSON files safely and handle errors.
- Established database schema with tables for armour, weapons, bestiary, and rules.
- Added logic to insert data from JSON files into the corresponding database tables.
- Included backup functionality for the existing database before migration.
- Logged import totals and sample data from each table for verification.
- Added a new script `check-shop-stats.js` to fetch and log item statistics from the database.
2025-08-17 19:14:29 +02:00

167 lines
5.6 KiB
JavaScript

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const Database = require('better-sqlite3');
const dbPath = path.join(__dirname, '../database/sqlite/deathwatch.db');
const backupPath = dbPath + '.backup.' + Date.now();
function safeReadJSON(p) {
if (!fs.existsSync(p)) return null;
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (e) { console.error('JSON parse error', p, e.message); return null; }
}
console.log('=== MIGRATE JSON DATA INTO SQLITE ===');
if (!fs.existsSync(dbPath)) {
console.error('DB not found at', dbPath);
process.exit(1);
}
fs.copyFileSync(dbPath, backupPath);
console.log('Backup created:', backupPath);
const db = new Database(dbPath);
// Create consolidated tables
db.exec(`
CREATE TABLE IF NOT EXISTS armour (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
req INTEGER DEFAULT 0,
renown TEXT DEFAULT 'None',
category TEXT,
stats TEXT,
source TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS weapons (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
req INTEGER DEFAULT 0,
renown TEXT DEFAULT 'None',
category TEXT,
stats TEXT,
source TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS bestiary (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
book TEXT,
page TEXT,
pdf TEXT,
stats TEXT,
profile TEXT,
snippet TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS rules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
rule_id TEXT UNIQUE,
title TEXT,
content TEXT,
page INTEGER,
source TEXT,
source_abbr TEXT,
category TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
const insertArmour = db.prepare(`INSERT OR IGNORE INTO armour (name, req, renown, category, stats, source) VALUES (?, ?, ?, ?, ?, ?)`);
const insertWeapon = db.prepare(`INSERT OR IGNORE INTO weapons (name, req, renown, category, stats, source) VALUES (?, ?, ?, ?, ?, ?)`);
const insertBestiary = db.prepare(`INSERT INTO bestiary (name, book, page, pdf, stats, profile, snippet) VALUES (?, ?, ?, ?, ?, ?, ?)`);
const insertRule = db.prepare(`INSERT OR IGNORE INTO rules (rule_id, title, content, page, source, source_abbr, category) VALUES (?, ?, ?, ?, ?, ?, ?)`);
let totals = { armour:0, weapons:0, bestiary:0, rules:0 };
// Import armour
const armourFile = path.join(__dirname, '../database/public/deathwatch-armor.json');
const armourData = safeReadJSON(armourFile);
if (armourData) {
const categories = Object.keys(armourData);
categories.forEach(cat => {
const arr = armourData[cat];
if (!Array.isArray(arr)) return;
const insert = insertArmour;
db.transaction(() => {
for (const item of arr) {
const stats = JSON.stringify(item.stats || {});
const src = (item.stats && item.stats.source) || '';
insert.run(item.name || '(unnamed)', item.req || 0, item.renown || 'None', item.category || cat, stats, src);
totals.armour++;
}
})();
});
}
// Import weapons
const weaponsFile = path.join(__dirname, '../database/public/deathwatch-weapons-comprehensive.json');
const weaponsData = safeReadJSON(weaponsFile);
if (weaponsData) {
// many weapon files use keys like rangedWeapons, meleeWeapons
Object.keys(weaponsData).forEach(k => {
const arr = weaponsData[k];
if (!Array.isArray(arr)) return;
db.transaction(() => {
for (const w of arr) {
const stats = JSON.stringify(w.stats || {});
const src = (w.stats && w.stats.source) || '';
insertWeapon.run(w.name || '(unnamed)', w.req || 0, w.renown || 'Any', w.category || k, stats, src);
totals.weapons++;
}
})();
});
}
// Import bestiary
const bestiaryFile = path.join(__dirname, '../database/deathwatch-bestiary-extracted.json');
const bestiaryData = safeReadJSON(bestiaryFile);
if (bestiaryData && Array.isArray(bestiaryData.results)) {
db.transaction(() => {
for (const e of bestiaryData.results) {
const stats = JSON.stringify(e.stats || {});
const profile = JSON.stringify(e.profile || {});
const name = e.bestiaryName || e.name || '(unnamed)';
insertBestiary.run(name, e.book || '', e.page || '', e.pdf || '', stats, profile, e.stats && e.stats.snippet ? e.stats.snippet : '');
totals.bestiary++;
}
})();
}
// Import rules
const rulesFile = path.join(__dirname, '../database/rules/rules-database.json');
const rulesData = safeReadJSON(rulesFile);
if (rulesData && Array.isArray(rulesData.rules)) {
db.transaction(() => {
for (const r of rulesData.rules) {
insertRule.run(r.id || null, r.title || '', r.content || '', r.page || null, r.source || '', r.sourceAbbr || '', r.category || 'general');
totals.rules++;
}
})();
}
console.log('Import totals:', totals);
// Show row counts from DB for verification
const counts = {
armour: db.prepare('SELECT COUNT(*) as c FROM armour').get().c,
weapons: db.prepare('SELECT COUNT(*) as c FROM weapons').get().c,
bestiary: db.prepare('SELECT COUNT(*) as c FROM bestiary').get().c,
rules: db.prepare('SELECT COUNT(*) as c FROM rules').get().c
};
console.log('DB row counts:', counts);
// Print a small sample from each table
console.log('\nSample armour:', db.prepare('SELECT name, category, stats FROM armour LIMIT 3').all());
console.log('\nSample weapons:', db.prepare('SELECT name, category, stats FROM weapons LIMIT 3').all());
console.log('\nSample bestiary:', db.prepare('SELECT name, book, snippet FROM bestiary LIMIT 3').all());
console.log('\nSample rules:', db.prepare('SELECT rule_id, title FROM rules LIMIT 3').all());
db.close();
console.log('\nMigration complete. DB backed up at', backupPath);