File size: 3,045 Bytes
839aef2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// 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;