refactor: migrate database from SQLite to PostgreSQL
This commit is contained in:
+78
-61
@@ -1,70 +1,87 @@
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { Pool } = require('pg');
|
||||
require('dotenv').config();
|
||||
|
||||
const dbPath = path.join(__dirname, 'data', 'database.sqlite');
|
||||
const dataDir = path.dirname(dbPath);
|
||||
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
const db = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error('Error opening database', err.message);
|
||||
} else {
|
||||
console.log('Connected to the SQLite database.');
|
||||
db.run(`CREATE TABLE IF NOT EXISTS profiles (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT UNIQUE,
|
||||
password_hash TEXT,
|
||||
role TEXT DEFAULT 'user',
|
||||
api_credits INTEGER DEFAULT 10,
|
||||
storage_limit_mb INTEGER DEFAULT 500,
|
||||
is_active INTEGER DEFAULT 0
|
||||
)`, (err) => {
|
||||
// Attempt to add column for existing databases (fails silently if exists)
|
||||
if (!err) {
|
||||
db.run('ALTER TABLE profiles ADD COLUMN is_active INTEGER DEFAULT 0', () => {});
|
||||
}
|
||||
});
|
||||
|
||||
db.run(`CREATE TABLE IF NOT EXISTS projects (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT,
|
||||
name TEXT,
|
||||
scene_data TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES profiles (id)
|
||||
)`);
|
||||
}
|
||||
// Coolify uses DATABASE_URL
|
||||
// If not set, we can fallback to a local postgres or just let it fail
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
});
|
||||
|
||||
const run = (sql, params = []) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, params, function (err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ id: this.lastID, changes: this.changes });
|
||||
});
|
||||
});
|
||||
pool.on('error', (err, client) => {
|
||||
console.error('Unexpected error on idle client', err);
|
||||
process.exit(-1);
|
||||
});
|
||||
|
||||
const initDB = async () => {
|
||||
try {
|
||||
const client = await pool.connect();
|
||||
console.log('Connected to the PostgreSQL database.');
|
||||
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS profiles (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT UNIQUE,
|
||||
password_hash TEXT,
|
||||
role TEXT DEFAULT 'user',
|
||||
api_credits INTEGER DEFAULT 10,
|
||||
storage_limit_mb INTEGER DEFAULT 500,
|
||||
is_active BOOLEAN DEFAULT false
|
||||
)
|
||||
`);
|
||||
|
||||
// In Postgres, ALTER TABLE ADD COLUMN IF NOT EXISTS requires PG >= 9.6
|
||||
// So we can catch the error if column exists just like sqlite logic
|
||||
try {
|
||||
await client.query(`ALTER TABLE profiles ADD COLUMN is_active BOOLEAN DEFAULT false`);
|
||||
} catch (err) {
|
||||
// Ignore if it already exists
|
||||
}
|
||||
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT REFERENCES profiles(id),
|
||||
name TEXT,
|
||||
scene_data TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
client.release();
|
||||
} catch (err) {
|
||||
console.error('Error initializing database:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const get = (sql, params = []) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(sql, params, (err, result) => {
|
||||
if (err) reject(err);
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
initDB();
|
||||
|
||||
// Wrapper functions to simulate the old sqlite API but backed by pg
|
||||
|
||||
const run = async (sql, params = []) => {
|
||||
// convert ? to $1, $2, etc.
|
||||
let paramIndex = 1;
|
||||
const pgSql = sql.replace(/\?/g, () => `$${paramIndex++}`);
|
||||
|
||||
const res = await pool.query(pgSql, params);
|
||||
// `run` in sqlite returns { id: this.lastID, changes: this.changes }
|
||||
// Postgres doesn't return lastID unless RETURNING is used, but for uuids it doesn't matter.
|
||||
return { changes: res.rowCount };
|
||||
};
|
||||
|
||||
const all = (sql, params = []) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(sql, params, (err, rows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(rows);
|
||||
});
|
||||
});
|
||||
const get = async (sql, params = []) => {
|
||||
let paramIndex = 1;
|
||||
const pgSql = sql.replace(/\?/g, () => `$${paramIndex++}`);
|
||||
|
||||
const res = await pool.query(pgSql, params);
|
||||
return res.rows[0];
|
||||
};
|
||||
|
||||
module.exports = { db, run, get, all };
|
||||
const all = async (sql, params = []) => {
|
||||
let paramIndex = 1;
|
||||
const pgSql = sql.replace(/\?/g, () => `$${paramIndex++}`);
|
||||
|
||||
const res = await pool.query(pgSql, params);
|
||||
return res.rows;
|
||||
};
|
||||
|
||||
module.exports = { db: pool, run, get, all };
|
||||
|
||||
Reference in New Issue
Block a user