From 56fe6944585c86d2a107a8acb91903f9bf4acedc Mon Sep 17 00:00:00 2001 From: DeathKaioken Date: Sun, 18 Jan 2026 16:19:08 +0100 Subject: [PATCH] bug: prod mysql conflict --- database/createDb.js | 266 ++++++++++++-------- debug-pdf/template_1_html_full.html | 1 - debug-pdf/template_1_html_raw.bin | 1 - debug-pdf/template_1_sanitized_preview.html | 1 - 4 files changed, 160 insertions(+), 109 deletions(-) delete mode 100644 debug-pdf/template_1_html_full.html delete mode 100644 debug-pdf/template_1_html_raw.bin delete mode 100644 debug-pdf/template_1_sanitized_preview.html diff --git a/database/createDb.js b/database/createDb.js index ef6fb02..d1b4804 100644 --- a/database/createDb.js +++ b/database/createDb.js @@ -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 + + // 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 - 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); - `); - await connection.query(` - ALTER TABLE user_documents + 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 { diff --git a/debug-pdf/template_1_html_full.html b/debug-pdf/template_1_html_full.html deleted file mode 100644 index 6a402d4..0000000 --- a/debug-pdf/template_1_html_full.html +++ /dev/null @@ -1 +0,0 @@ -safdasdffasdafd \ No newline at end of file diff --git a/debug-pdf/template_1_html_raw.bin b/debug-pdf/template_1_html_raw.bin deleted file mode 100644 index 6a402d4..0000000 --- a/debug-pdf/template_1_html_raw.bin +++ /dev/null @@ -1 +0,0 @@ -safdasdffasdafd \ No newline at end of file diff --git a/debug-pdf/template_1_sanitized_preview.html b/debug-pdf/template_1_sanitized_preview.html deleted file mode 100644 index 6a402d4..0000000 --- a/debug-pdf/template_1_sanitized_preview.html +++ /dev/null @@ -1 +0,0 @@ -safdasdffasdafd \ No newline at end of file