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.

This commit is contained in:
Torsten Schulz (local)
2025-12-18 17:44:17 +01:00
parent c28f8b1384
commit 86f753c745
2 changed files with 96 additions and 16 deletions

View File

@@ -598,44 +598,52 @@ export default function setupAssociations() {
Learning.belongsTo(LearnRecipient, { Learning.belongsTo(LearnRecipient, {
foreignKey: 'learningRecipientId', foreignKey: 'learningRecipientId',
as: 'recipient' as: 'recipient',
constraints: false
} }
); );
LearnRecipient.hasMany(Learning, { LearnRecipient.hasMany(Learning, {
foreignKey: 'learningRecipientId', foreignKey: 'learningRecipientId',
as: 'learnings' as: 'learnings',
constraints: false
}); });
Learning.belongsTo(FalukantUser, { Learning.belongsTo(FalukantUser, {
foreignKey: 'associatedFalukantUserId', foreignKey: 'associatedFalukantUserId',
as: 'learner' as: 'learner',
constraints: false
} }
); );
FalukantUser.hasMany(Learning, { FalukantUser.hasMany(Learning, {
foreignKey: 'associatedFalukantUserId', foreignKey: 'associatedFalukantUserId',
as: 'learnings' as: 'learnings',
constraints: false
}); });
Learning.belongsTo(ProductType, { Learning.belongsTo(ProductType, {
foreignKey: 'productId', foreignKey: 'productId',
as: 'productType' as: 'productType',
constraints: false
}); });
ProductType.hasMany(Learning, { ProductType.hasMany(Learning, {
foreignKey: 'productId', foreignKey: 'productId',
as: 'learnings' as: 'learnings',
constraints: false
}); });
Learning.belongsTo(FalukantCharacter, { Learning.belongsTo(FalukantCharacter, {
foreignKey: 'associatedLearningCharacterId', foreignKey: 'associatedLearningCharacterId',
as: 'learningCharacter' as: 'learningCharacter',
constraints: false
}); });
FalukantCharacter.hasMany(Learning, { FalukantCharacter.hasMany(Learning, {
foreignKey: 'associatedLearningCharacterId', foreignKey: 'associatedLearningCharacterId',
as: 'learningsCharacter' as: 'learningsCharacter',
constraints: false
}); });
FalukantUser.hasMany(Credit, { FalukantUser.hasMany(Credit, {

View File

@@ -475,13 +475,14 @@ const syncModelsAlways = async (models) => {
} }
} }
try {
// constraints: false wird von Sequelize ignoriert wenn Associations vorhanden sind // constraints: false wird von Sequelize ignoriert wenn Associations vorhanden sind
// Wir müssen die Associations temporär entfernen, um Foreign Keys zu verhindern // Wir müssen die Associations temporär entfernen, um Foreign Keys zu verhindern
const originalAssociations = model.associations ? { ...model.associations } : {}; const originalAssociations = model.associations ? { ...model.associations } : {};
const associationKeys = Object.keys(originalAssociations); const associationKeys = Object.keys(originalAssociations);
try {
// Entferne temporär alle Associations, damit Sequelize keine Foreign Keys erstellt // 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) { if (associationKeys.length > 0) {
console.log(` ⚠️ Temporarily removing ${associationKeys.length} associations from ${model.name} to prevent FK creation`); console.log(` ⚠️ Temporarily removing ${associationKeys.length} associations from ${model.name} to prevent FK creation`);
// Lösche alle Associations temporär // Lösche alle Associations temporär
@@ -528,14 +529,85 @@ const syncModelsAlways = async (models) => {
} }
console.log(` 🔄 Syncing model ${model.name} with constraints: false`); console.log(` 🔄 Syncing model ${model.name} with constraints: false`);
try {
await model.sync({ alter: true, force: false, constraints: false }); 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
});
// Stelle die Associations wieder her 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;
}
}
// 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) { if (associationKeys.length > 0) {
console.log(` ✅ Restoring ${associationKeys.length} associations for ${model.name}`); console.log(` ✅ Restoring ${associationKeys.length} associations for ${model.name}`);
model.associations = originalAssociations; model.associations = originalAssociations;
} }
} finally {
// Restore VIRTUAL fields after sync // Restore VIRTUAL fields after sync
for (const [key, attr] of Object.entries(virtualFields)) { for (const [key, attr] of Object.entries(virtualFields)) {
model.rawAttributes[key] = attr; model.rawAttributes[key] = attr;