discord-media-server
Version:
Self-hosted Discord bot for scanning and serving information on local media files.
214 lines (182 loc) • 6.47 kB
JavaScript
// lib/database.js (universal MySQL + SQLite handler)
import dotenv from 'dotenv';
dotenv.config();
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { CONFIG_DIR, DATA_DIR } from './setupConfig.js';
let db;
let pool;
let isSQLite = false;
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Lazy-loaded config & connection
let initialized = false;
/** Initializes DB connection based on config.json */
export async function initDatabase() {
if (initialized) return;
const sqlite3pkg = await import('sqlite3');
const sqlite3 = sqlite3pkg.default.verbose();
const { open } = await import('sqlite');
try {
const dbType = process.env.DB_TYPE || 'sqlite';
isSQLite = dbType === 'sqlite';
if (isSQLite) {
//const sqlite3 = (await import('better-sqlite3')).default;
const dbFileName = path.basename(process.env.DB_FILE || 'media.sqlite');
const dataDir = DATA_DIR || path.resolve(CONFIG_DIR || __dirname, 'data');
const sqlitePath = path.resolve(dataDir, dbFileName);
console.log("RUNTIME DB PATH:", sqlitePath);
fs.mkdirSync(path.dirname(sqlitePath), { recursive: true });
//db = new sqlite3(sqlitePath);
db = await open({
filename: sqlitePath,
driver: sqlite3.Database
});
if (!db) {
throw new Error(`Failed to initialize SQLite DB at: ${sqlitePath}`);
}
//console.log(`SQLite DB loaded: ${sqlitePath}`);
// Sanity check
try {
await db.all('SELECT 1');
//db.close();
console.log(`SQLite database ready at ${sqlitePath}`);
} catch (err) {
console.error('SQLite connection failed:', err.message);
process.exit(1);
}
} else {
const mysql = await import('mysql2/promise');
pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10, // ✅ adjustable
queueLimit: 0
});
/*
db = await mysql.createConnection({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME
});
if (!db || typeof db.prepare !== 'function') {
throw new Error('Failed to initialize MySQL database.');
}
//console.log(`MySQL DB connected: ${config.dbConfig.database || config.dbName}`);
*/
// Sanity check
const [rows] = await pool.query('SELECT 1');
if (!rows) throw new Error('MySQL sanity check failed');
console.log('MySQL connection successful.');
}
initialized = true;
} catch(err) {
console.log(`\ninitDatabase error:\nPlease run setup first.\n\n${err.message}`)
}
}
/** Connects to SQLite for setup validation */
export async function connectSQLite(config) {
const sqlite3 = await import('sqlite3');
const { open } = await import('sqlite');
//const sqlite3 = (await import('better-sqlite3')).default;
const dbFileName = path.basename(process.env.DB_FILE || 'media.sqlite');
const dataDir = DATA_DIR || path.resolve(CONFIG_DIR || __dirname, 'data');
const dbPath = path.resolve(dataDir, dbFileName);
// Ensure the folder exists
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
try {
//const db = new sqlite3(dbPath);
db = await open({
filename: dbPath,
driver: sqlite3.Database
});
await db.all('SELECT 1');
db.close();
console.log(`SQLite database ready at ${dbPath}`);
} catch (err) {
console.error('SQLite connection failed:', err.message);
process.exit(1);
}
}
/** Connects to MySQL for setup validation */
export async function connectMySQL(config) {
try {
const mysql = await import('mysql2/promise');
const db = await mysql.createConnection({
host: config.host,
user: config.user,
password: config.password,
database: config.database
});
await db.query('SELECT 1');
await db.end();
console.log('MySQL connection successful');
} catch (err) {
console.error('MySQL connection failed:', err.message);
process.exit(1);
}
}
export async function query(sql, params = []) {
if (!initialized) await initDatabase();
if (isSQLite) {
if (!db) {
throw new Error('Database is not initialized.');
}
/*
const stmt = await db.run(sql);
if (sql.trim().toLowerCase().startsWith('select')) {
return stmt.all(...params);
} else {
return stmt.run(...params);
}
*/
const isSelect = sql.trim().toLowerCase().startsWith('select');
if (isSelect) {
return db.all(sql, params); // return all rows
} else {
return db.run(sql, params); // insert/update/delete
}
} else {
const [rows] = await pool.execute(sql, params);
return rows;
}
}
// lib/database.js
export async function backupSQLite() {
if (!initialized) await initDatabase();
if (!isSQLite || !db) return;
const dbFileName = path.basename(process.env.DB_FILE || 'media.sqlite');
const dataDir = DATA_DIR || path.resolve(CONFIG_DIR || __dirname, 'data');
const backupName = dbFileName.replace('.sqlite', '') + `_${new Date().toISOString().replace(/[:.]/g, '-')}.sqlite`;
const backupPath = path.resolve(dataDir, backupName);
console.log(`\nCreating SQLite backup: ${backupPath}`);
try {
// USE db.db to access the raw sqlite3 driver
const rawDb = db.db;
await new Promise((resolve, reject) => {
const backup = rawDb.backup(backupPath);
backup.step(-1, (err) => { if (err) reject(err); });
backup.finish((err) => { if (err) reject(err); else resolve(); });
});
console.log(`SQLite backup completed: ${backupPath}`);
return backupPath;
} catch (err) {
console.error("SQLite backup failed:", err.message);
throw err;
}
}
export async function close() {
if (!initialized) return;
if (isSQLite) {
await db.close();
} else if (pool) {
await pool.end();
}
}
export { isSQLite };