Compare commits
10 Commits
a0206dc8cb
...
a0aa678e7d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0aa678e7d | ||
|
|
a1b6e6ab59 | ||
|
|
73acf1d1cd | ||
|
|
48110e9a6f | ||
|
|
642e215c69 | ||
|
|
091b9ff70a | ||
|
|
86f753c745 | ||
|
|
c28f8b1384 | ||
|
|
9b36297171 | ||
|
|
7beed235d7 |
@@ -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, {
|
||||
|
||||
@@ -475,12 +475,249 @@ 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 verhindert, dass Sequelize Foreign Keys automatisch erstellt
|
||||
// Foreign Keys sollten nur über Migrations verwaltet werden
|
||||
// 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
|
||||
for (const key of associationKeys) {
|
||||
delete model.associations[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Entferne bestehende Foreign Keys vor dem Sync, damit Sequelize sie nicht aktualisiert
|
||||
try {
|
||||
const tableName = model.tableName;
|
||||
// Schema kann eine Funktion sein, daher prüfen wir model.options.schema direkt
|
||||
const schema = model.options?.schema || 'public';
|
||||
|
||||
console.log(` 🔍 Checking for foreign keys in ${schema}.${tableName}...`);
|
||||
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(` ⚠️ Found ${foreignKeys.length} existing foreign keys:`, foreignKeys.map(fk => fk.constraint_name).join(', '));
|
||||
console.log(` ⚠️ Removing ${foreignKeys.length} existing foreign keys from ${model.name} (schema: ${schema}) before sync`);
|
||||
for (const fk of foreignKeys) {
|
||||
console.log(` 🗑️ Dropping constraint: ${fk.constraint_name}`);
|
||||
await sequelize.query(`
|
||||
ALTER TABLE "${schema}"."${tableName}"
|
||||
DROP CONSTRAINT IF EXISTS "${fk.constraint_name}" CASCADE
|
||||
`);
|
||||
}
|
||||
console.log(` ✅ All foreign keys removed for ${model.name}`);
|
||||
} else {
|
||||
console.log(` ✅ No foreign keys found for ${model.name}`);
|
||||
}
|
||||
} catch (fkError) {
|
||||
console.warn(` ⚠️ Could not remove foreign keys for ${model.name}:`, fkError.message);
|
||||
console.warn(` ⚠️ Error details:`, fkError);
|
||||
}
|
||||
|
||||
console.log(` 🔄 Syncing model ${model.name} with constraints: false`);
|
||||
await model.sync({ alter: true, force: false, constraints: false });
|
||||
try {
|
||||
// Versuche doppelte pg_description Einträge vor dem Sync zu bereinigen
|
||||
// Hinweis: Benötigt Superuser-Rechte oder spezielle Berechtigungen
|
||||
try {
|
||||
const tableName = model.tableName;
|
||||
const schema = model.options?.schema || 'public';
|
||||
// Verwende direkte Parameter-Einsetzung, da DO $$ keine Parameterbindung unterstützt
|
||||
// Die Parameter sind sicher, da sie von Sequelize-Modell-Eigenschaften kommen
|
||||
await sequelize.query(`
|
||||
DELETE FROM pg_catalog.pg_description d1
|
||||
WHERE d1.objoid IN (
|
||||
SELECT c.oid
|
||||
FROM pg_catalog.pg_class c
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relname = '${tableName.replace(/'/g, "''")}'
|
||||
AND n.nspname = '${schema.replace(/'/g, "''")}'
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_catalog.pg_description d2
|
||||
WHERE d2.objoid = d1.objoid
|
||||
AND d2.objsubid = d1.objsubid
|
||||
AND d2.ctid < d1.ctid
|
||||
)
|
||||
`);
|
||||
} catch (descError) {
|
||||
// Ignoriere Berechtigungsfehler - das ist normal, wenn der Benutzer keine Superuser-Rechte hat
|
||||
if (descError.message && descError.message.includes('Berechtigung')) {
|
||||
console.log(` ℹ️ Cannot clean up duplicate pg_description entries (requires superuser privileges): ${model.name}`);
|
||||
} else {
|
||||
console.warn(` ⚠️ Could not clean up duplicate pg_description entries for ${model.name}:`, descError.message);
|
||||
}
|
||||
}
|
||||
|
||||
await model.sync({ alter: true, force: false, constraints: false });
|
||||
} catch (syncError) {
|
||||
// Wenn Sequelize einen "mehr als eine Zeile" Fehler hat, überspringe das Model
|
||||
// Dies kann durch doppelte pg_description Einträge oder mehrere Tabellen mit demselben Namen verursacht werden
|
||||
if (syncError.message && (syncError.message.includes('mehr als eine Zeile') || syncError.message.includes('more than one row'))) {
|
||||
const tableName = model.tableName;
|
||||
const schema = model.options?.schema || 'public';
|
||||
console.error(` ❌ Cannot sync ${model.name} (${schema}.${tableName}) due to Sequelize describeTable error`);
|
||||
console.error(` ❌ This is likely caused by multiple tables with the same name in different schemas`);
|
||||
console.error(` ❌ or duplicate pg_description entries (requires superuser to fix)`);
|
||||
console.error(` ⚠️ Skipping sync for ${model.name} - Schema is likely already correct`);
|
||||
// Überspringe dieses Model und fahre mit dem nächsten fort
|
||||
continue;
|
||||
}
|
||||
// Wenn eine referenzierte Tabelle noch nicht existiert, erstelle die Tabelle ohne Foreign Key
|
||||
else if (syncError.message && (syncError.message.includes('existiert nicht') || syncError.message.includes('does not exist') || syncError.message.includes('Relation'))) {
|
||||
const tableName = model.tableName;
|
||||
const schema = model.options?.schema || 'public';
|
||||
console.warn(` ⚠️ Cannot create ${model.name} (${schema}.${tableName}) with Foreign Key - referenced table does not exist yet`);
|
||||
console.warn(` ⚠️ Attempting to create table without Foreign Key constraint...`);
|
||||
|
||||
try {
|
||||
// Prüfe, ob die Tabelle bereits existiert
|
||||
const [tableExists] = await sequelize.query(`
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = :schema
|
||||
AND table_name = :tableName
|
||||
) as exists
|
||||
`, {
|
||||
replacements: { schema, tableName },
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
});
|
||||
|
||||
if (tableExists && tableExists.exists) {
|
||||
console.log(` ℹ️ Table ${schema}.${tableName} already exists, skipping creation`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Erstelle die Tabelle manuell ohne Foreign Key
|
||||
// Verwende queryInterface.createTable mit den Attributen, aber ohne Foreign Keys
|
||||
const queryInterface = sequelize.getQueryInterface();
|
||||
const attributes = {};
|
||||
|
||||
// Kopiere alle Attribute aus dem Model, aber entferne references
|
||||
for (const [key, attr] of Object.entries(model.rawAttributes)) {
|
||||
attributes[key] = { ...attr };
|
||||
// Entferne references, damit kein Foreign Key erstellt wird
|
||||
if (attributes[key].references) {
|
||||
delete attributes[key].references;
|
||||
}
|
||||
}
|
||||
|
||||
// Erstelle die Tabelle mit queryInterface.createTable ohne Foreign Keys
|
||||
await queryInterface.createTable(tableName, attributes, {
|
||||
schema,
|
||||
// Stelle sicher, dass keine Foreign Keys erstellt werden
|
||||
charset: model.options?.charset,
|
||||
collate: model.options?.collate
|
||||
});
|
||||
console.log(` ✅ Table ${schema}.${tableName} created successfully without Foreign Key`);
|
||||
} catch (createError) {
|
||||
console.error(` ❌ Failed to create table ${schema}.${tableName} without Foreign Key:`, createError.message);
|
||||
console.error(` ⚠️ Skipping ${model.name} - will retry after dependencies are created`);
|
||||
// Überspringe dieses Model und fahre mit dem nächsten fort
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Wenn Sequelize einen Foreign Key Constraint Fehler hat, entferne verwaiste Einträge oder überspringe das Model
|
||||
else if (syncError.name === 'SequelizeForeignKeyConstraintError' || (syncError.message && (syncError.message.includes('FOREIGN KEY') || syncError.message.includes('Fremdschlüssel')))) {
|
||||
const tableName = model.tableName;
|
||||
const schema = model.options?.schema || 'public';
|
||||
console.error(` ❌ Cannot sync ${model.name} (${schema}.${tableName}) due to Foreign Key Constraint Error`);
|
||||
console.error(` ❌ Detail: ${syncError.parent?.detail || syncError.message}`);
|
||||
console.error(` ⚠️ This usually means there are orphaned records. Cleanup should have removed them.`);
|
||||
console.error(` ⚠️ Skipping sync for ${model.name} - please check and fix orphaned records manually`);
|
||||
// Überspringe dieses Model und fahre mit dem nächsten fort
|
||||
continue;
|
||||
}
|
||||
// Wenn Sequelize versucht, Foreign Keys zu erstellen, entferne sie nach dem Fehler
|
||||
else 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Restore VIRTUAL fields after sync
|
||||
for (const [key, attr] of Object.entries(virtualFields)) {
|
||||
model.rawAttributes[key] = attr;
|
||||
|
||||
@@ -379,7 +379,23 @@ const syncDatabaseForDeployment = async () => {
|
||||
console.log(`✅ ${deletedCount7} verwaiste child_relation Einträge entfernt`);
|
||||
}
|
||||
|
||||
if (deletedCount1 === 0 && deletedCount2 === 0 && deletedCount3 === 0 && deletedCount4 === 0 && deletedCount5 === 0 && deletedCount6 === 0 && deletedCount7 === 0) {
|
||||
// Cleanup political_office mit ungültigen character_id, office_type_id oder region_id
|
||||
const result8 = await sequelize.query(`
|
||||
DELETE FROM falukant_data.political_office
|
||||
WHERE character_id NOT IN (
|
||||
SELECT id FROM falukant_data.character
|
||||
) OR office_type_id NOT IN (
|
||||
SELECT id FROM falukant_type.political_office_type
|
||||
) OR region_id NOT IN (
|
||||
SELECT id FROM falukant_data.region
|
||||
);
|
||||
`);
|
||||
const deletedCount8 = result8[1] || 0;
|
||||
if (deletedCount8 > 0) {
|
||||
console.log(`✅ ${deletedCount8} verwaiste political_office Einträge entfernt`);
|
||||
}
|
||||
|
||||
if (deletedCount1 === 0 && deletedCount2 === 0 && deletedCount3 === 0 && deletedCount4 === 0 && deletedCount5 === 0 && deletedCount6 === 0 && deletedCount7 === 0 && deletedCount8 === 0) {
|
||||
console.log("✅ Keine verwaisten Einträge gefunden");
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user