From 86f753c7453922d3d6bd2db1a31b3af7b97ac5c0 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 18 Dec 2025 17:44:17 +0100 Subject: [PATCH] Refactor associations in models to include constraints: false, preventing automatic foreign key creation. Update sequelize.js to enhance foreign key management during model synchronization, ensuring associations are restored correctly after sync operations. --- backend/models/associations.js | 24 ++++++---- backend/utils/sequelize.js | 88 ++++++++++++++++++++++++++++++---- 2 files changed, 96 insertions(+), 16 deletions(-) diff --git a/backend/models/associations.js b/backend/models/associations.js index 78abb7e..a98af91 100644 --- a/backend/models/associations.js +++ b/backend/models/associations.js @@ -598,44 +598,52 @@ export default function setupAssociations() { Learning.belongsTo(LearnRecipient, { foreignKey: 'learningRecipientId', - as: 'recipient' + as: 'recipient', + constraints: false } ); LearnRecipient.hasMany(Learning, { foreignKey: 'learningRecipientId', - as: 'learnings' + as: 'learnings', + constraints: false }); Learning.belongsTo(FalukantUser, { foreignKey: 'associatedFalukantUserId', - as: 'learner' + as: 'learner', + constraints: false } ); FalukantUser.hasMany(Learning, { foreignKey: 'associatedFalukantUserId', - as: 'learnings' + as: 'learnings', + constraints: false }); Learning.belongsTo(ProductType, { foreignKey: 'productId', - as: 'productType' + as: 'productType', + constraints: false }); ProductType.hasMany(Learning, { foreignKey: 'productId', - as: 'learnings' + as: 'learnings', + constraints: false }); Learning.belongsTo(FalukantCharacter, { foreignKey: 'associatedLearningCharacterId', - as: 'learningCharacter' + as: 'learningCharacter', + constraints: false }); FalukantCharacter.hasMany(Learning, { foreignKey: 'associatedLearningCharacterId', - as: 'learningsCharacter' + as: 'learningsCharacter', + constraints: false }); FalukantUser.hasMany(Credit, { diff --git a/backend/utils/sequelize.js b/backend/utils/sequelize.js index 3057f51..2dee9a6 100644 --- a/backend/utils/sequelize.js +++ b/backend/utils/sequelize.js @@ -475,13 +475,14 @@ const syncModelsAlways = async (models) => { } } + // constraints: false wird von Sequelize ignoriert wenn Associations vorhanden sind + // Wir müssen die Associations temporär entfernen, um Foreign Keys zu verhindern + const originalAssociations = model.associations ? { ...model.associations } : {}; + const associationKeys = Object.keys(originalAssociations); + try { - // constraints: false wird von Sequelize ignoriert wenn Associations vorhanden sind - // Wir müssen die Associations temporär entfernen, um Foreign Keys zu verhindern - const originalAssociations = model.associations ? { ...model.associations } : {}; - const associationKeys = Object.keys(originalAssociations); - // Entferne temporär alle Associations, damit Sequelize keine Foreign Keys erstellt + // Dies muss innerhalb des try Blocks sein, damit die Wiederherstellung im finally Block garantiert ist if (associationKeys.length > 0) { console.log(` ⚠️ Temporarily removing ${associationKeys.length} associations from ${model.name} to prevent FK creation`); // Lösche alle Associations temporär @@ -528,14 +529,85 @@ const syncModelsAlways = async (models) => { } console.log(` 🔄 Syncing model ${model.name} with constraints: false`); - await model.sync({ alter: true, force: false, constraints: false }); + try { + await model.sync({ alter: true, force: false, constraints: false }); + } catch (syncError) { + // Wenn Sequelize versucht, Foreign Keys zu erstellen, entferne sie nach dem Fehler + if (syncError.message && syncError.message.includes('REFERENCES')) { + console.log(` ⚠️ Sequelize tried to create FK despite constraints: false, removing any created FKs...`); + try { + const tableName = model.tableName; + const schema = model.options?.schema || 'public'; + const foreignKeys = await sequelize.query(` + SELECT tc.constraint_name + FROM information_schema.table_constraints AS tc + WHERE tc.constraint_type = 'FOREIGN KEY' + AND tc.table_name = :tableName + AND tc.table_schema = :schema + `, { + replacements: { tableName, schema }, + type: sequelize.QueryTypes.SELECT + }); + + if (foreignKeys && foreignKeys.length > 0) { + for (const fk of foreignKeys) { + await sequelize.query(` + ALTER TABLE "${schema}"."${tableName}" + DROP CONSTRAINT IF EXISTS "${fk.constraint_name}" CASCADE + `); + } + } + // Versuche Sync erneut ohne Foreign Keys + console.log(` 🔄 Retrying sync without foreign keys...`); + await model.sync({ alter: true, force: false, constraints: false }); + } catch (retryError) { + console.error(` ❌ Retry failed:`, retryError.message); + console.error(` ❌ Original sync error:`, syncError.message); + // Kombiniere beide Fehler für besseres Debugging + const combinedError = new Error(`Sync failed: ${syncError.message}. Retry also failed: ${retryError.message}`); + combinedError.originalError = syncError; + combinedError.retryError = retryError; + throw combinedError; + } + } else { + throw syncError; + } + } - // Stelle die Associations wieder her + // Entferne alle Foreign Keys, die Sequelize möglicherweise trotzdem erstellt hat + try { + const tableName = model.tableName; + const schema = model.options?.schema || 'public'; + const foreignKeys = await sequelize.query(` + SELECT tc.constraint_name + FROM information_schema.table_constraints AS tc + WHERE tc.constraint_type = 'FOREIGN KEY' + AND tc.table_name = :tableName + AND tc.table_schema = :schema + `, { + replacements: { tableName, schema }, + type: sequelize.QueryTypes.SELECT + }); + + if (foreignKeys && foreignKeys.length > 0) { + console.log(` ⚠️ Sequelize created ${foreignKeys.length} foreign keys despite constraints: false, removing them...`); + for (const fk of foreignKeys) { + await sequelize.query(` + ALTER TABLE "${schema}"."${tableName}" + DROP CONSTRAINT IF EXISTS "${fk.constraint_name}" CASCADE + `); + } + } + } catch (fkError) { + console.warn(` ⚠️ Could not check/remove foreign keys after sync:`, fkError.message); + } + } finally { + // Stelle die Associations wieder her (IMMER, auch bei Fehlern) if (associationKeys.length > 0) { console.log(` ✅ Restoring ${associationKeys.length} associations for ${model.name}`); model.associations = originalAssociations; } - } finally { + // Restore VIRTUAL fields after sync for (const [key, attr] of Object.entries(virtualFields)) { model.rawAttributes[key] = attr;