Миграции: JS-строки, транзакции, _migrations таблица. Без fs/path. Версия 0.1.23

This commit is contained in:
2026-07-09 07:32:50 +04:00
parent 9557e83d47
commit 8e88d6eed7
3 changed files with 58 additions and 5 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ipwhitelist", "name": "ipwhitelist",
"version": "0.1.22", "version": "0.1.23",
"description": "IP WhiteList microservice for cloud provider", "description": "IP WhiteList microservice for cloud provider",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
+1 -1
View File
@@ -8,7 +8,7 @@
// ═══════════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════════
module.exports = { module.exports = {
version: '0.1.22', version: '0.1.23',
// ── Среда ───────────────────────────────────────────────────────────────── // ── Среда ─────────────────────────────────────────────────────────────────
// APP_ENV=test → тестовый стенд (жёлтый баннер в UI, можно включать тестовый API) // APP_ENV=test → тестовый стенд (жёлтый баннер в UI, можно включать тестовый API)
+56 -3
View File
@@ -1,11 +1,29 @@
// ═══════════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════════
// V2 — инициализация схемы БД // V2 — инициализация схемы БД + миграции
// //
// ЭТО: CREATE TABLE IF NOT EXISTS — безопасный повторный запуск. // ЭТО: CREATE TABLE IF NOT EXISTS (идемпотентно) + applyMigrations().
// НИКАКИХ ALTER, миграций, DO$$ блоков. Только создание таблиц. // ЗАЧЕМ: приложение само создаёт таблицы и применяет новые миграции при старте.
//
// МИГРАЦИИ: встроенные SQL-строки, не из файлов.
// Каждая в транзакции (BEGIN/COMMIT), ошибка логируется и пропускается.
// Без fs, path, process.exit.
// ═══════════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════════
// ── Миграции (упорядочены, выполняются один раз) ──────────────────────────────
const MIGRATIONS = [
{
name: '001_add_impersonated_by',
sql: `ALTER TABLE audit_log ADD COLUMN IF NOT EXISTS impersonated_by VARCHAR(255) DEFAULT NULL`,
},
{
name: '002_add_audit_restore',
sql: `ALTER TABLE audit_log DROP CONSTRAINT IF EXISTS audit_log_action_check;
ALTER TABLE audit_log ADD CONSTRAINT audit_log_action_check CHECK (action IN ('CREATE','UPDATE','DELETE','RESTORE'))`,
},
];
async function ensureSchema(pool) { async function ensureSchema(pool) {
// 1. Таблицы
await pool.query(` await pool.query(`
CREATE TABLE IF NOT EXISTS companies ( CREATE TABLE IF NOT EXISTS companies (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
@@ -47,9 +65,44 @@ async function ensureSchema(pool) {
CREATE INDEX IF NOT EXISTS idx_audit_log_company_time CREATE INDEX IF NOT EXISTS idx_audit_log_company_time
ON audit_log(company_id, created_at DESC); ON audit_log(company_id, created_at DESC);
-- Таблица учёта выполненных миграций
CREATE TABLE IF NOT EXISTS _migrations (
id SERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL,
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
`); `);
console.log('[db] Schema ensured'); console.log('[db] Schema ensured');
// 2. Миграции
await applyMigrations(pool);
}
async function applyMigrations(pool) {
try {
const done = new Set(
(await pool.query('SELECT name FROM _migrations')).rows.map(r => r.name)
);
for (const m of MIGRATIONS) {
if (done.has(m.name)) continue;
try {
await pool.query('BEGIN');
await pool.query(m.sql);
await pool.query('INSERT INTO _migrations (name) VALUES ($1)', [m.name]);
await pool.query('COMMIT');
console.log('[db] Migration applied:', m.name);
} catch (e) {
await pool.query('ROLLBACK').catch(() => {});
console.error('[db] Migration failed:', m.name, e.message);
}
}
} catch (e) {
console.error('[db] applyMigrations error:', e.message);
}
} }
module.exports = { ensureSchema }; module.exports = { ensureSchema };
module.exports = { ensureSchema };