Files
dwroller/database/migrate-sqlite-to-mariadb.js

402 lines
14 KiB
JavaScript

#!/usr/bin/env node
const Database = require('better-sqlite3');
const mysql = require('mysql2/promise');
const path = require('path');
const dotenv = require('dotenv');
dotenv.config({ path: path.join(__dirname, '.env') });
dotenv.config({ path: path.join(__dirname, '..', '.env') });
if (!process.env.DB_PASSWORD) {
throw new Error('DB_PASSWORD must be set in database/.env or .env');
}
// SQLite database path
const sqliteDbPath = path.join(__dirname, 'sqlite', 'deathwatch.db');
// MariaDB configuration
const mariadbConfig = {
host: process.env.DB_HOST || '192.168.1.113',
user: process.env.DB_USER || 'deathwatch',
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME || 'deathwatch',
port: Number(process.env.DB_PORT || 3307)
};
// Helper function to convert SQLite datetime to MySQL format
function convertDateTime(dateString) {
if (!dateString) return null;
try {
return new Date(dateString).toISOString().slice(0, 19).replace('T', ' ');
} catch (error) {
console.warn(`Warning: Could not convert datetime: ${dateString}`);
return null;
}
}
async function migrateData() {
let sqliteDb = null;
let mariadbConnection = null;
try {
console.log('Starting data migration from SQLite to MariaDB...');
// Connect to SQLite
console.log('Connecting to SQLite database...');
sqliteDb = new Database(sqliteDbPath);
// Connect to MariaDB
console.log('Connecting to MariaDB database...');
mariadbConnection = await mysql.createConnection(mariadbConfig);
// Migrate players table (includes requisition_points and renown_level)
console.log('Migrating players table...');
const players = sqliteDb.prepare('SELECT * FROM players').all();
console.log(`Found ${players.length} players to migrate`);
for (const player of players) {
console.log(` Migrating player: ${player.name}`);
// Convert text fields to proper JSON
const rollerInfo = player.roller_info || '{}';
const shopInfo = player.shop_info || '{}';
const tabInfo = player.tab_info || '{}';
await mariadbConnection.execute(
`INSERT INTO players (id, name, roller_info, shop_info, tab_info, pw, pw_hash, created_at, updated_at, requisition_points, renown_level)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
roller_info = VALUES(roller_info),
shop_info = VALUES(shop_info),
tab_info = VALUES(tab_info),
pw = VALUES(pw),
pw_hash = VALUES(pw_hash),
updated_at = VALUES(updated_at),
requisition_points = VALUES(requisition_points),
renown_level = VALUES(renown_level)`,
[
player.id,
player.name,
rollerInfo,
shopInfo,
tabInfo,
player.pw || '',
player.pw_hash || '',
convertDateTime(player.created_at),
convertDateTime(player.updated_at),
player.requisition_points || 0,
player.renown_level || 'None'
]
);
}
// Migrate sessions table
console.log('Migrating sessions table...');
const sessions = sqliteDb.prepare('SELECT * FROM sessions').all();
console.log(`Found ${sessions.length} sessions to migrate`);
for (const session of sessions) {
console.log(` Migrating session: ${session.session_id}`);
await mariadbConnection.execute(
`INSERT INTO sessions (id, session_id, data, expires_at, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
data = VALUES(data),
expires_at = VALUES(expires_at),
updated_at = VALUES(updated_at)`,
[
session.id,
session.session_id,
session.data || '{}',
convertDateTime(session.expires_at),
convertDateTime(session.created_at),
convertDateTime(session.updated_at)
]
);
}
// Migrate shop_items table
console.log('Migrating shop_items table...');
const shopItems = sqliteDb.prepare('SELECT * FROM shop_items').all();
console.log(`Found ${shopItems.length} shop items to migrate`);
for (const item of shopItems) {
console.log(` Migrating shop item: ${item.name}`);
await mariadbConnection.execute(
`INSERT INTO shop_items (id, name, category, requisition_cost, renown_requirement, item_type, stats, source, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
category = VALUES(category),
requisition_cost = VALUES(requisition_cost),
renown_requirement = VALUES(renown_requirement),
item_type = VALUES(item_type),
stats = VALUES(stats),
source = VALUES(source),
updated_at = VALUES(updated_at)`,
[
item.id,
item.name,
item.category,
item.requisition_cost,
item.renown_requirement,
item.item_type,
item.stats,
item.source,
convertDateTime(item.created_at),
convertDateTime(item.updated_at)
]
);
}
// Migrate player_inventory table
console.log('Migrating player_inventory table...');
const playerInventory = sqliteDb.prepare('SELECT * FROM player_inventory').all();
console.log(`Found ${playerInventory.length} inventory items to migrate`);
for (const invItem of playerInventory) {
console.log(` Migrating inventory item for player ${invItem.player_id}`);
await mariadbConnection.execute(
`INSERT INTO player_inventory (id, player_id, item_id, quantity, acquired_at, transaction_details)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
quantity = VALUES(quantity),
transaction_details = VALUES(transaction_details)`,
[
invItem.id,
invItem.player_id,
invItem.item_id,
invItem.quantity,
convertDateTime(invItem.acquired_at),
invItem.transaction_details
]
);
}
// Migrate transactions table
console.log('Migrating transactions table...');
const transactions = sqliteDb.prepare('SELECT * FROM transactions').all();
console.log(`Found ${transactions.length} transactions to migrate`);
for (const transaction of transactions) {
console.log(` Migrating transaction ${transaction.id}`);
await mariadbConnection.execute(
`INSERT INTO transactions (id, player_id, item_id, requisition_cost, previous_rp, new_rp, transaction_date)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
transaction.id,
transaction.player_id,
transaction.item_id,
transaction.requisition_cost,
transaction.previous_rp,
transaction.new_rp,
convertDateTime(transaction.transaction_date)
]
);
}
// Migrate armour table
console.log('Migrating armour table...');
const armour = sqliteDb.prepare('SELECT * FROM armour').all();
console.log(`Found ${armour.length} armour items to migrate`);
for (const armourItem of armour) {
console.log(` Migrating armour: ${armourItem.name}`);
await mariadbConnection.execute(
`INSERT INTO armour (id, name, req, renown, category, stats, source, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
req = VALUES(req),
renown = VALUES(renown),
category = VALUES(category),
stats = VALUES(stats),
source = VALUES(source)`,
[
armourItem.id,
armourItem.name,
armourItem.req,
armourItem.renown,
armourItem.category,
armourItem.stats,
armourItem.source,
convertDateTime(armourItem.created_at)
]
);
}
// Migrate weapons table
console.log('Migrating weapons table...');
const weapons = sqliteDb.prepare('SELECT * FROM weapons').all();
console.log(`Found ${weapons.length} weapons to migrate`);
for (const weapon of weapons) {
console.log(` Migrating weapon: ${weapon.name}`);
await mariadbConnection.execute(
`INSERT INTO weapons (id, name, req, renown, category, stats, source, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
req = VALUES(req),
renown = VALUES(renown),
category = VALUES(category),
stats = VALUES(stats),
source = VALUES(source)`,
[
weapon.id,
weapon.name,
weapon.req,
weapon.renown,
weapon.category,
weapon.stats,
weapon.source,
convertDateTime(weapon.created_at)
]
);
}
// Migrate bestiary table
console.log('Migrating bestiary table...');
const bestiary = sqliteDb.prepare('SELECT * FROM bestiary').all();
console.log(`Found ${bestiary.length} bestiary entries to migrate`);
for (const beast of bestiary) {
console.log(` Migrating bestiary: ${beast.name}`);
await mariadbConnection.execute(
`INSERT INTO bestiary (id, name, book, page, pdf, stats, profile, snippet, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
book = VALUES(book),
page = VALUES(page),
pdf = VALUES(pdf),
stats = VALUES(stats),
profile = VALUES(profile),
snippet = VALUES(snippet)`,
[
beast.id,
beast.name,
beast.book,
beast.page,
beast.pdf,
beast.stats,
beast.profile,
beast.snippet,
convertDateTime(beast.created_at)
]
);
}
// Migrate rules table
console.log('Migrating rules table...');
const rules = sqliteDb.prepare('SELECT * FROM rules').all();
console.log(`Found ${rules.length} rules to migrate`);
for (const rule of rules) {
console.log(` Migrating rule: ${rule.title}`);
// Truncate rule_id if it's too long (max 255 chars)
const ruleId = rule.rule_id ? rule.rule_id.substring(0, 255) : null;
// Truncate title if it's too long (max 500 chars)
const title = rule.title ? rule.title.substring(0, 500) : null;
await mariadbConnection.execute(
`INSERT INTO rules (id, rule_id, title, content, page, source, source_abbr, category, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
content = VALUES(content),
page = VALUES(page),
source = VALUES(source),
source_abbr = VALUES(source_abbr),
category = VALUES(category)`,
[
rule.id,
ruleId,
title,
rule.content,
rule.page,
rule.source,
rule.source_abbr,
rule.category,
convertDateTime(rule.created_at)
]
);
}
// Migrate rules_staging table
console.log('Migrating rules_staging table...');
const rulesStaging = sqliteDb.prepare('SELECT * FROM rules_staging').all();
console.log(`Found ${rulesStaging.length} rules_staging entries to migrate`);
for (const rule of rulesStaging) {
console.log(` Migrating rules_staging: ${rule.title}`);
await mariadbConnection.execute(
`INSERT INTO rules_staging (id, title, content, category, page, original_json, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
content = VALUES(content),
category = VALUES(category),
page = VALUES(page),
original_json = VALUES(original_json)`,
[
rule.id,
rule.title,
rule.content,
rule.category,
rule.page,
rule.original_json,
convertDateTime(rule.created_at)
]
);
}
console.log('Migration completed successfully!');
// Verify migration
console.log('\nVerifying migration...');
const [mariadbPlayers] = await mariadbConnection.execute('SELECT COUNT(*) as count FROM players');
const [mariadbSessions] = await mariadbConnection.execute('SELECT COUNT(*) as count FROM sessions');
const [mariadbShopItems] = await mariadbConnection.execute('SELECT COUNT(*) as count FROM shop_items');
const [mariadbInventory] = await mariadbConnection.execute('SELECT COUNT(*) as count FROM player_inventory');
const [mariadbTransactions] = await mariadbConnection.execute('SELECT COUNT(*) as count FROM transactions');
const [mariadbArmour] = await mariadbConnection.execute('SELECT COUNT(*) as count FROM armour');
const [mariadbWeapons] = await mariadbConnection.execute('SELECT COUNT(*) as count FROM weapons');
const [mariadbBestiary] = await mariadbConnection.execute('SELECT COUNT(*) as count FROM bestiary');
const [mariadbRules] = await mariadbConnection.execute('SELECT COUNT(*) as count FROM rules');
const [mariadbRulesStaging] = await mariadbConnection.execute('SELECT COUNT(*) as count FROM rules_staging');
console.log(`MariaDB players: ${mariadbPlayers[0].count}`);
console.log(`MariaDB sessions: ${mariadbSessions[0].count}`);
console.log(`MariaDB shop_items: ${mariadbShopItems[0].count}`);
console.log(`MariaDB player_inventory: ${mariadbInventory[0].count}`);
console.log(`MariaDB transactions: ${mariadbTransactions[0].count}`);
console.log(`MariaDB armour: ${mariadbArmour[0].count}`);
console.log(`MariaDB weapons: ${mariadbWeapons[0].count}`);
console.log(`MariaDB bestiary: ${mariadbBestiary[0].count}`);
console.log(`MariaDB rules: ${mariadbRules[0].count}`);
console.log(`MariaDB rules_staging: ${mariadbRulesStaging[0].count}`);
} catch (error) {
console.error('Migration failed:', error);
process.exit(1);
} finally {
if (sqliteDb) {
sqliteDb.close();
}
if (mariadbConnection) {
await mariadbConnection.end();
}
}
}
// Run migration
migrateData().catch(console.error);