rr-locker-api / src /db.js
MD.Riad Rayhan
Node.js + Socket.IO backend (migrated from FastAPI)
839aef2 verified
Raw History Blame Contribute Delete
3.05 kB
// Force UTC so naive-UTC timestamps written by the old Python backend are read
// back without timezone drift. MUST run before any Date parsing.
process.env.TZ = 'UTC';
import fs from 'fs';
import path from 'path';
import { Sequelize } from 'sequelize';
import { settings } from './config.js';
/**
* Percent-encode username/password in a SQL connection URL so special chars in a
* Supabase/Neon password (@ : / # ? …) don't break URL parsing. Mirrors the
* Python `_sanitize_db_url`.
*/
function sanitizeDbUrl(url) {
const m = url.match(/^([^:]+):\/\/(.+)$/);
if (!m || !m[2].includes('@')) return url;
const scheme = m[1];
const body = m[2];
const at = body.lastIndexOf('@');
const userinfo = body.slice(0, at);
const host = body.slice(at + 1);
if (!userinfo.includes(':')) return url;
const colon = userinfo.indexOf(':');
const user = userinfo.slice(0, colon);
const password = userinfo.slice(colon + 1);
return `${scheme}://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}`;
}
function buildSequelize() {
let dbUrl = settings.DATABASE_URL;
if (dbUrl.startsWith('postgres://')) {
dbUrl = dbUrl.replace('postgres://', 'postgresql://');
}
// ── SQLite ──────────────────────────────────────────
if (dbUrl.includes('sqlite')) {
// Strip the SQLAlchemy "sqlite:///" prefix to a filesystem path.
let filePath = dbUrl.replace(/^sqlite:\/\/\//, '').replace(/^sqlite:\/\//, '');
if (filePath.startsWith('./')) filePath = filePath.slice(2);
// Relocate the default relative DB onto a persistent /data mount when present
// (Hugging Face Spaces / many PaaS), so data survives container rebuilds.
if (filePath === 'emi_locker.db' || filePath === './emi_locker.db') {
const persistentDir = '/data';
try {
if (fs.existsSync(persistentDir)) {
fs.accessSync(persistentDir, fs.constants.W_OK);
filePath = path.join(persistentDir, 'emi_locker.db');
}
} catch {
/* not writable β†’ keep local path */
}
}
return new Sequelize({
dialect: 'sqlite',
storage: filePath,
logging: false,
define: { timestamps: false },
});
}
// ── Postgres / MySQL ────────────────────────────────
const sanitized = sanitizeDbUrl(dbUrl);
const dialect = sanitized.startsWith('mysql') ? 'mysql' : 'postgres';
return new Sequelize(sanitized, {
dialect,
logging: false,
timezone: '+00:00',
pool: { max: 10, min: 0, idle: 10000 },
dialectOptions:
dialect === 'postgres'
? { keepAlive: true, ...(process.env.PGSSL ? { ssl: { require: true, rejectUnauthorized: false } } : {}) }
: {},
define: { timestamps: false },
});
}
export const sequelize = buildSequelize();
export default sequelize;