const fs = require('fs'); const path = require('path'); const readline = require('readline'); const rootDir = path.resolve(__dirname, '..'); const PROD_DB_SUFFIX = '_prod'; const DEV_DB_NAME = 'gallery_dev'; const PROD_DB_NAME = 'gallery_prod'; const LEGACY_DB_NAMES = ['Gallery', 'gallery']; function parseEnvFile(filePath) { const out = {}; if (!fs.existsSync(filePath)) return out; const content = fs.readFileSync(filePath, 'utf8'); for (const line of content.split(/\r?\n/)) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eq = trimmed.indexOf('='); if (eq === -1) continue; const key = trimmed.slice(0, eq).trim(); let value = trimmed.slice(eq + 1).trim(); if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1); } out[key] = value; } return out; } /** Load into process.env without overwriting keys already set. */ function loadEnvFile(filePath) { const parsed = parseEnvFile(filePath); for (const [key, value] of Object.entries(parsed)) { if (process.env[key] === undefined) process.env[key] = value; } } function pgConfigFromMap(env) { const host = env.DB_HOST || '192.168.10.122'; const port = Number(env.DB_PORT || 5432); const user = env.DB_USER; const password = env.DB_PASSWORD; const database = env.DB_NAME; if (!user || !password || !database) { throw new Error('DB_USER, DB_PASSWORD, and DB_NAME are required'); } return { host, port, user, password, database }; } function pgConfigFromEnv() { return pgConfigFromMap(process.env); } function assertProdDatabase(dbName) { if (!dbName.endsWith(PROD_DB_SUFFIX) && dbName !== PROD_DB_NAME) { throw new Error( `Refusing prod operation on database "${dbName}". ` + `Target must be "${PROD_DB_NAME}" or end with "${PROD_DB_SUFFIX}".`, ); } return dbName; } function assertDevDatabase(dbName) { if (dbName === PROD_DB_NAME || dbName.endsWith(PROD_DB_SUFFIX)) { throw new Error( `Refusing dev operation on production database "${dbName}". ` + `Use "${DEV_DB_NAME}" for development.`, ); } return dbName; } /** * File values win over process.env so loading prod after dev (or vice versa) * in the same process cannot leave the wrong DB_NAME stuck. */ function loadDevPgConfig() { const filePath = path.join(rootDir, '.env'); const fileEnv = parseEnvFile(filePath); const config = pgConfigFromMap({ ...process.env, ...fileEnv }); assertDevDatabase(config.database); return config; } function loadProdPgConfig() { const filePath = path.join(rootDir, 'infra', 'docker', '.env.prod'); const fileEnv = parseEnvFile(filePath); const config = pgConfigFromMap({ ...process.env, ...fileEnv }); assertProdDatabase(config.database); return config; } async function confirmProdAction(message) { if (process.env.CONFIRM_PROD === '1') return; const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const answer = await new Promise((resolve) => { rl.question(`${message}\nType "yes" to continue: `, resolve); }); rl.close(); if (answer.trim().toLowerCase() !== 'yes') { throw new Error('Aborted.'); } } module.exports = { PROD_DB_SUFFIX, DEV_DB_NAME, PROD_DB_NAME, LEGACY_DB_NAMES, rootDir, parseEnvFile, loadEnvFile, pgConfigFromEnv, assertProdDatabase, assertDevDatabase, loadDevPgConfig, loadProdPgConfig, confirmProdAction, };