bug: prod mysql conflict
This commit is contained in:
parent
8d4c70b631
commit
56fe694458
@ -65,7 +65,65 @@ async function ensureIndex(conn, table, indexName, indexDDL) {
|
||||
}
|
||||
}
|
||||
|
||||
async function createDatabase() {
|
||||
// --- Schema pre-check helpers (Approach A) ---
|
||||
async function columnExists(conn, table, column) {
|
||||
const [rows] = await conn.query(
|
||||
`SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = ?
|
||||
AND COLUMN_NAME = ?
|
||||
LIMIT 1`,
|
||||
[table, column]
|
||||
);
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
async function addColumnIfMissing(conn, table, column, ddlFragment /* includes type + nullability + AFTER/etc */) {
|
||||
if (await columnExists(conn, table, column)) return false;
|
||||
await conn.query(`ALTER TABLE \`${table}\` ADD COLUMN \`${column}\` ${ddlFragment}`);
|
||||
console.log(`🆕 Added column ${table}.${column}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function constraintExists(conn, table, constraintName) {
|
||||
const [rows] = await conn.query(
|
||||
`SELECT 1
|
||||
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = ?
|
||||
AND CONSTRAINT_NAME = ?
|
||||
LIMIT 1`,
|
||||
[table, constraintName]
|
||||
);
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
async function addForeignKeyIfMissing(conn, table, constraintName, ddl /* full ALTER TABLE ... ADD CONSTRAINT ... */) {
|
||||
if (await constraintExists(conn, table, constraintName)) return false;
|
||||
await conn.query(ddl);
|
||||
console.log(`🆕 Added constraint ${constraintName} on ${table}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function getPrimaryKeyColumns(conn, table) {
|
||||
const [rows] = await conn.query(
|
||||
`SELECT k.COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS t
|
||||
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
|
||||
ON t.CONSTRAINT_NAME = k.CONSTRAINT_NAME
|
||||
AND t.TABLE_SCHEMA = k.TABLE_SCHEMA
|
||||
AND t.TABLE_NAME = k.TABLE_NAME
|
||||
WHERE t.TABLE_SCHEMA = DATABASE()
|
||||
AND t.TABLE_NAME = ?
|
||||
AND t.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||
ORDER BY k.ORDINAL_POSITION`,
|
||||
[table]
|
||||
);
|
||||
return rows.map(r => r.COLUMN_NAME);
|
||||
}
|
||||
|
||||
const createDatabase = async () => {
|
||||
console.log('🚀 Starting MySQL database initialization...');
|
||||
console.log('📍 Database host:', process.env.DB_HOST);
|
||||
console.log('📍 Database name:', process.env.DB_NAME);
|
||||
@ -285,22 +343,28 @@ async function createDatabase() {
|
||||
INDEX idx_object_storage_id (object_storage_id)
|
||||
);
|
||||
`);
|
||||
// Backfill in case table already existed without contract_type
|
||||
await connection.query(`
|
||||
ALTER TABLE user_documents
|
||||
ADD COLUMN IF NOT EXISTS contract_type ENUM('contract','gdpr') NOT NULL DEFAULT 'contract' AFTER document_type;
|
||||
`);
|
||||
await connection.query(`
|
||||
CREATE INDEX IF NOT EXISTS idx_user_contract_type ON user_documents (user_id, contract_type);
|
||||
`);
|
||||
|
||||
// Backfill/migrate for older schemas (NO IF NOT EXISTS; pre-check instead)
|
||||
await addColumnIfMissing(
|
||||
connection,
|
||||
'user_documents',
|
||||
'contract_type',
|
||||
`ENUM('contract','gdpr') NOT NULL DEFAULT 'contract' AFTER document_type`
|
||||
);
|
||||
await ensureIndex(connection, 'user_documents', 'idx_user_contract_type', '`user_id`, `contract_type`');
|
||||
|
||||
await connection.query(`
|
||||
ALTER TABLE user_documents
|
||||
MODIFY COLUMN object_storage_id VARCHAR(255) NULL;
|
||||
`);
|
||||
await connection.query(`
|
||||
ALTER TABLE user_documents
|
||||
ADD COLUMN IF NOT EXISTS signatureBase64 LONGTEXT NULL AFTER object_storage_id;
|
||||
`);
|
||||
|
||||
await addColumnIfMissing(
|
||||
connection,
|
||||
'user_documents',
|
||||
'signatureBase64',
|
||||
`LONGTEXT NULL AFTER object_storage_id`
|
||||
);
|
||||
|
||||
console.log('✅ User documents table created/verified');
|
||||
|
||||
// 8c. document_templates table: Stores template metadata and object storage keys
|
||||
@ -370,54 +434,36 @@ async function createDatabase() {
|
||||
console.log('✅ User action logs table created/verified');
|
||||
|
||||
// --- Add missing user_id column for existing databases + backfill ---
|
||||
// (convert from "try/catch duplicate" to pre-check)
|
||||
if (await addColumnIfMissing(connection, 'user_action_logs', 'user_id', `INT NULL`)) {
|
||||
await addForeignKeyIfMissing(
|
||||
connection,
|
||||
'user_action_logs',
|
||||
'fk_user_action_logs_user',
|
||||
`
|
||||
ALTER TABLE user_action_logs
|
||||
ADD CONSTRAINT fk_user_action_logs_user
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
`
|
||||
);
|
||||
await ensureIndex(connection, 'user_action_logs', 'idx_user_id', '`user_id`');
|
||||
}
|
||||
|
||||
// Backfill: prefer performed_by_user_id, else affected_user_id
|
||||
try {
|
||||
// Add column if missing
|
||||
await connection.query(`ALTER TABLE user_action_logs ADD COLUMN user_id INT NULL`);
|
||||
console.log('🆕 Added user_action_logs.user_id column');
|
||||
|
||||
// Add FK if just added
|
||||
try {
|
||||
await connection.query(`
|
||||
ALTER TABLE user_action_logs
|
||||
ADD CONSTRAINT fk_user_action_logs_user
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
`);
|
||||
console.log('🆕 Added FK fk_user_action_logs_user');
|
||||
} catch (e) {
|
||||
console.log('ℹ️ FK fk_user_action_logs_user already exists or cannot add:', e.message);
|
||||
}
|
||||
|
||||
// Add index if missing
|
||||
try {
|
||||
await connection.query(`CREATE INDEX idx_user_id ON user_action_logs (user_id)`);
|
||||
console.log('🆕 Added index idx_user_id on user_action_logs.user_id');
|
||||
} catch (e) {
|
||||
console.log('ℹ️ idx_user_id already exists or cannot add:', e.message);
|
||||
}
|
||||
|
||||
// Backfill: prefer performed_by_user_id, else affected_user_id
|
||||
try {
|
||||
const [res1] = await connection.query(`
|
||||
UPDATE user_action_logs
|
||||
SET user_id = performed_by_user_id
|
||||
WHERE user_id IS NULL AND performed_by_user_id IS NOT NULL
|
||||
`);
|
||||
const [res2] = await connection.query(`
|
||||
await connection.query(`
|
||||
UPDATE user_action_logs
|
||||
SET user_id = affected_user_id
|
||||
WHERE user_id IS NULL AND affected_user_id IS NOT NULL
|
||||
`);
|
||||
console.log('🧹 Backfilled user_action_logs.user_id from performed_by_user_id/affected_user_id');
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Could not backfill user_action_logs.user_id:', e.message);
|
||||
}
|
||||
} catch (e) {
|
||||
// Column may already exist; ignore
|
||||
if (!/Duplicate column name|exists/i.test(e.message)) {
|
||||
console.log('ℹ️ user_action_logs.user_id add skipped or not required:', e.message);
|
||||
} else {
|
||||
console.log('ℹ️ user_action_logs.user_id already exists');
|
||||
}
|
||||
console.warn('⚠️ Could not backfill user_action_logs.user_id:', e.message);
|
||||
}
|
||||
|
||||
// --- Email & Registration Flow Tables ---
|
||||
@ -818,26 +864,17 @@ async function createDatabase() {
|
||||
console.log('✅ Invoice payments table created/verified');
|
||||
|
||||
// Extend coffee_abonement_history with optional related_invoice_id
|
||||
try {
|
||||
await connection.query(`ALTER TABLE coffee_abonement_history ADD COLUMN related_invoice_id BIGINT NULL`);
|
||||
console.log('🆕 Added coffee_abonement_history.related_invoice_id');
|
||||
} catch (e) {
|
||||
if (!/Duplicate column name|exists/i.test(e.message)) {
|
||||
console.log('ℹ️ related_invoice_id add skipped or not required:', e.message);
|
||||
} else {
|
||||
console.log('ℹ️ coffee_abonement_history.related_invoice_id already exists');
|
||||
}
|
||||
}
|
||||
try {
|
||||
await connection.query(`
|
||||
await addColumnIfMissing(connection, 'coffee_abonement_history', 'related_invoice_id', `BIGINT NULL`);
|
||||
await addForeignKeyIfMissing(
|
||||
connection,
|
||||
'coffee_abonement_history',
|
||||
'fk_hist_invoice',
|
||||
`
|
||||
ALTER TABLE coffee_abonement_history
|
||||
ADD CONSTRAINT fk_hist_invoice FOREIGN KEY (related_invoice_id)
|
||||
REFERENCES invoices(id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
`);
|
||||
console.log('🆕 Added FK fk_hist_invoice on coffee_abonement_history.related_invoice_id');
|
||||
} catch (e) {
|
||||
console.log('ℹ️ fk_hist_invoice already exists or failed:', e.message);
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
// --- Matrix: Global 5-ary tree config and relations ---
|
||||
await connection.query(`
|
||||
@ -851,15 +888,9 @@ async function createDatabase() {
|
||||
CONSTRAINT chk_matrix_singleton CHECK (id = 1)
|
||||
);
|
||||
`);
|
||||
// Safeguard: if table pre-existed without name column, add it
|
||||
try {
|
||||
await connection.query(`ALTER TABLE matrix_config ADD COLUMN name VARCHAR(255) NULL`);
|
||||
console.log('🆕 Added missing matrix_config.name column');
|
||||
} catch (e) {
|
||||
if (!/Duplicate column/i.test(e.message)) {
|
||||
console.log('ℹ️ matrix_config.name alter skipped:', e.message);
|
||||
}
|
||||
}
|
||||
// Safeguard: if table pre-existed without name column, add it (pre-check)
|
||||
await addColumnIfMissing(connection, 'matrix_config', 'name', `VARCHAR(255) NULL`);
|
||||
|
||||
console.log('✅ Matrix config table created/verified');
|
||||
|
||||
await connection.query(`
|
||||
@ -1012,30 +1043,34 @@ async function createDatabase() {
|
||||
}
|
||||
|
||||
// --- Ensure multi-instance columns (idempotent) ---
|
||||
try { await connection.query(`ALTER TABLE user_tree_edges ADD COLUMN matrix_instance_id INT NULL`); } catch (_) {}
|
||||
try { await connection.query(`ALTER TABLE user_tree_edges ADD COLUMN rogue_user BOOLEAN DEFAULT FALSE`); } catch (_) {}
|
||||
try {
|
||||
await connection.query(`
|
||||
await addColumnIfMissing(connection, 'user_tree_edges', 'matrix_instance_id', `INT NULL`);
|
||||
await addColumnIfMissing(connection, 'user_tree_edges', 'rogue_user', `BOOLEAN DEFAULT FALSE`);
|
||||
|
||||
await addForeignKeyIfMissing(
|
||||
connection,
|
||||
'user_tree_edges',
|
||||
'fk_edges_instance',
|
||||
`
|
||||
ALTER TABLE user_tree_edges
|
||||
ADD CONSTRAINT fk_edges_instance FOREIGN KEY (matrix_instance_id)
|
||||
REFERENCES matrix_instances(id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
`);
|
||||
console.log('🆕 FK fk_edges_instance added');
|
||||
} catch (e) {
|
||||
console.log('ℹ️ fk_edges_instance already exists or failed:', e.message);
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
// Backfill matrix_instance_id for existing edges
|
||||
const [firstInstRow] = await connection.query(`SELECT id FROM matrix_instances ORDER BY id ASC LIMIT 1`);
|
||||
const firstInstanceId = firstInstRow[0]?.id;
|
||||
if (firstInstanceId) {
|
||||
await connection.query(`UPDATE user_tree_edges SET matrix_instance_id = ? WHERE matrix_instance_id IS NULL`, [firstInstanceId]);
|
||||
await connection.query(
|
||||
`UPDATE user_tree_edges SET matrix_instance_id = ? WHERE matrix_instance_id IS NULL`,
|
||||
[firstInstanceId]
|
||||
);
|
||||
}
|
||||
|
||||
// Indexes (idempotent)
|
||||
try { await connection.query(`CREATE INDEX idx_edges_instance_parent ON user_tree_edges (matrix_instance_id, parent_user_id)`); } catch (_) {}
|
||||
try { await connection.query(`CREATE INDEX idx_edges_instance_child ON user_tree_edges (matrix_instance_id, child_user_id)`); } catch (_) {}
|
||||
try { await connection.query(`CREATE INDEX idx_edges_rogue ON user_tree_edges (matrix_instance_id, rogue_user)`); } catch (_) {}
|
||||
// Indexes (pre-check)
|
||||
await ensureIndex(connection, 'user_tree_edges', 'idx_edges_instance_parent', '`matrix_instance_id`, `parent_user_id`');
|
||||
await ensureIndex(connection, 'user_tree_edges', 'idx_edges_instance_child', '`matrix_instance_id`, `child_user_id`');
|
||||
await ensureIndex(connection, 'user_tree_edges', 'idx_edges_rogue', '`matrix_instance_id`, `rogue_user`');
|
||||
|
||||
// --- Alter user_tree_closure: add matrix_instance_id ---
|
||||
await connection.query(`
|
||||
@ -1054,23 +1089,42 @@ async function createDatabase() {
|
||||
`);
|
||||
console.log('✅ user_tree_closure (multi) created/verified');
|
||||
|
||||
// If legacy closure rows without matrix_instance_id (older separate table definition), attempt add column & backfill
|
||||
try {
|
||||
await connection.query(`ALTER TABLE user_tree_closure ADD COLUMN matrix_instance_id INT NOT NULL`);
|
||||
console.log('🆕 Added matrix_instance_id column to existing user_tree_closure');
|
||||
} catch (e) {
|
||||
// already integrated new definition
|
||||
// If legacy closure table existed without matrix_instance_id, migrate it safely (pre-check)
|
||||
if (!(await columnExists(connection, 'user_tree_closure', 'matrix_instance_id'))) {
|
||||
await connection.query(`ALTER TABLE user_tree_closure ADD COLUMN matrix_instance_id INT NULL`);
|
||||
if (firstInstanceId) {
|
||||
await connection.query(
|
||||
`UPDATE user_tree_closure SET matrix_instance_id = ? WHERE matrix_instance_id IS NULL`,
|
||||
[firstInstanceId]
|
||||
);
|
||||
}
|
||||
// Best-effort: enforce NOT NULL after backfill
|
||||
try {
|
||||
await connection.query(`ALTER TABLE user_tree_closure MODIFY COLUMN matrix_instance_id INT NOT NULL`);
|
||||
} catch (e) {
|
||||
console.log('ℹ️ user_tree_closure.matrix_instance_id NOT NULL enforcement skipped:', e.message);
|
||||
}
|
||||
|
||||
// Best-effort: align PK to (matrix_instance_id, ancestor_user_id, descendant_user_id)
|
||||
try {
|
||||
const pkCols = await getPrimaryKeyColumns(connection, 'user_tree_closure');
|
||||
const wants = ['matrix_instance_id', 'ancestor_user_id', 'descendant_user_id'];
|
||||
const matches =
|
||||
pkCols.length === wants.length && pkCols.every((c, i) => c === wants[i]);
|
||||
|
||||
if (!matches) {
|
||||
await connection.query(`ALTER TABLE user_tree_closure DROP PRIMARY KEY`);
|
||||
await connection.query(
|
||||
`ALTER TABLE user_tree_closure ADD PRIMARY KEY (matrix_instance_id, ancestor_user_id, descendant_user_id)`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('ℹ️ user_tree_closure PK alignment skipped:', e.message);
|
||||
}
|
||||
}
|
||||
if (firstInstanceId) {
|
||||
try {
|
||||
await connection.query(`UPDATE user_tree_closure SET matrix_instance_id = ? WHERE matrix_instance_id IS NULL`, [firstInstanceId]);
|
||||
console.log('🧩 Backfilled closure matrix_instance_id');
|
||||
} catch (e) {
|
||||
console.log('ℹ️ Closure backfill skipped:', e.message);
|
||||
}
|
||||
}
|
||||
try { await connection.query(`CREATE INDEX idx_closure_instance_depth ON user_tree_closure (matrix_instance_id, depth)`); } catch (_) {}
|
||||
try { await connection.query(`CREATE INDEX idx_closure_instance_ancestor ON user_tree_closure (matrix_instance_id, ancestor_user_id)`); } catch (_) {}
|
||||
|
||||
await ensureIndex(connection, 'user_tree_closure', 'idx_closure_instance_depth', '`matrix_instance_id`, `depth`');
|
||||
await ensureIndex(connection, 'user_tree_closure', 'idx_closure_instance_ancestor', '`matrix_instance_id`, `ancestor_user_id`');
|
||||
|
||||
// Remove singleton constraint if present (best effort)
|
||||
try {
|
||||
|
||||
@ -1 +0,0 @@
|
||||
safdasdffasdafd
|
||||
@ -1 +0,0 @@
|
||||
safdasdffasdafd
|
||||
@ -1 +0,0 @@
|
||||
safdasdffasdafd
|
||||
Loading…
Reference in New Issue
Block a user