feat(match3): Erweiterung der Match3-Admin-Funktionalitäten und -Modelle
- Implementierung neuer Endpunkte für die Verwaltung von Match3-Kampagnen, Levels, Objectives und Tile-Typen im Admin-Bereich. - Anpassung der Admin-Services zur Unterstützung von Benutzerberechtigungen und Fehlerbehandlung. - Einführung von neuen Modellen und Assoziationen für Match3-Levels und Tile-Typen in der Datenbank. - Verbesserung der Internationalisierung für Match3-spezifische Texte in Deutsch und Englisch. - Aktualisierung der Frontend-Routen und -Komponenten zur Verwaltung von Match3-Inhalten.
This commit is contained in:
@@ -7,7 +7,9 @@ const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, proces
|
||||
host: process.env.DB_HOST,
|
||||
dialect: 'postgres',
|
||||
define: {
|
||||
timestamps: false
|
||||
timestamps: false,
|
||||
underscored: true, // WICHTIG: Alle Datenbankfelder im snake_case Format
|
||||
freezeTableName: true // Verhindert Pluralisierung der Tabellennamen
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,9 +34,290 @@ const initializeDatabase = async () => {
|
||||
};
|
||||
|
||||
const syncModels = async (models) => {
|
||||
for (const model of Object.values(models)) {
|
||||
// Verwende force: false und alter: false, um Constraints nicht neu zu erstellen
|
||||
// Nur beim ersten Mal oder bei expliziten Schema-Änderungen sollte alter: true verwendet werden
|
||||
await model.sync({ alter: false, force: false });
|
||||
}
|
||||
};
|
||||
|
||||
// Intelligente Schema-Synchronisation - prüft ob Updates nötig sind
|
||||
const syncModelsWithUpdates = async (models) => {
|
||||
console.log('🔍 Prüfe ob Schema-Updates nötig sind...');
|
||||
|
||||
try {
|
||||
// Prüfe ob neue Felder existieren müssen
|
||||
const needsUpdate = await checkSchemaUpdates(models);
|
||||
|
||||
if (needsUpdate) {
|
||||
console.log('🔄 Schema-Updates nötig - verwende alter: true');
|
||||
for (const model of Object.values(models)) {
|
||||
await model.sync({ alter: true, force: false });
|
||||
}
|
||||
console.log('✅ Schema-Updates abgeschlossen');
|
||||
} else {
|
||||
console.log('✅ Keine Schema-Updates nötig - verwende alter: false');
|
||||
for (const model of Object.values(models)) {
|
||||
await model.sync({ alter: false, force: false });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler bei Schema-Synchronisation:', error);
|
||||
// Fallback: Normale Synchronisation ohne Updates
|
||||
console.log('🔄 Fallback: Normale Synchronisation ohne Updates');
|
||||
for (const model of Object.values(models)) {
|
||||
await model.sync({ alter: false, force: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Prüft ob Schema-Updates nötig sind
|
||||
const checkSchemaUpdates = async (models) => {
|
||||
try {
|
||||
console.log('🔍 Prüfe alle Schemas auf Updates...');
|
||||
|
||||
// Alle verfügbaren Schemas
|
||||
const schemas = [
|
||||
'community', 'logs', 'type', 'service', 'forum',
|
||||
'falukant_data', 'falukant_type', 'falukant_predefine', 'falukant_log',
|
||||
'chat', 'match3'
|
||||
];
|
||||
|
||||
let needsUpdate = false;
|
||||
|
||||
// Prüfe jedes Schema
|
||||
for (const schema of schemas) {
|
||||
const schemaNeedsUpdate = await checkSchemaForUpdates(schema, models);
|
||||
if (schemaNeedsUpdate) {
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
return needsUpdate;
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler bei Schema-Prüfung:', error);
|
||||
return false; // Im Zweifelsfall: Keine Updates
|
||||
}
|
||||
};
|
||||
|
||||
// Prüft ein spezifisches Schema auf Updates
|
||||
const checkSchemaForUpdates = async (schemaName, models) => {
|
||||
try {
|
||||
console.log(`🔍 Prüfe Schema: ${schemaName}`);
|
||||
|
||||
// Hole alle Tabellen in diesem Schema
|
||||
const tables = await sequelize.query(`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = :schemaName
|
||||
ORDER BY table_name
|
||||
`, {
|
||||
replacements: { schemaName },
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
});
|
||||
|
||||
if (tables.length === 0) {
|
||||
console.log(` 📊 Schema ${schemaName}: Keine Tabellen gefunden`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(` 📊 Schema ${schemaName}: ${tables.length} Tabellen gefunden`);
|
||||
|
||||
// Prüfe jede Tabelle auf Updates
|
||||
for (const table of tables) {
|
||||
const tableName = table.table_name;
|
||||
const tableNeedsUpdate = await checkTableForUpdates(schemaName, tableName, models);
|
||||
if (tableNeedsUpdate) {
|
||||
console.log(` 🔄 Tabelle ${tableName} braucht Updates`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfe auf fehlende Tabellen (neue Models)
|
||||
const missingTables = await checkForMissingTables(schemaName, models);
|
||||
if (missingTables.length > 0) {
|
||||
console.log(` 🔄 Neue Tabellen gefunden: ${missingTables.join(', ')}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(` ✅ Schema ${schemaName}: Keine Updates nötig`);
|
||||
return false;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Fehler beim Prüfen von Schema ${schemaName}:`, error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Prüft auf fehlende Tabellen (neue Models)
|
||||
const checkForMissingTables = async (schemaName, models) => {
|
||||
try {
|
||||
const missingTables = [];
|
||||
|
||||
// Hole alle erwarteten Tabellen aus den Models
|
||||
for (const [modelName, model] of Object.entries(models)) {
|
||||
if (model._schema === schemaName) {
|
||||
const tableExists = await sequelize.query(`
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_schema = :schemaName
|
||||
AND table_name = :tableName
|
||||
);
|
||||
`, {
|
||||
replacements: { schemaName, tableName: model.tableName },
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
});
|
||||
|
||||
if (!tableExists[0]?.exists) {
|
||||
missingTables.push(model.tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return missingTables;
|
||||
} catch (error) {
|
||||
console.error(`❌ Fehler beim Prüfen fehlender Tabellen:`, error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// Prüft eine spezifische Tabelle auf Updates
|
||||
const checkTableForUpdates = async (schemaName, tableName, models) => {
|
||||
try {
|
||||
// Finde das entsprechende Model
|
||||
const model = findModelForTable(schemaName, tableName, models);
|
||||
if (!model) {
|
||||
console.log(` ⚠️ Kein Model für Tabelle ${schemaName}.${tableName} gefunden`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hole aktuelle Spalten der Tabelle
|
||||
const currentColumns = await sequelize.query(`
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = :schemaName
|
||||
AND table_name = :tableName
|
||||
ORDER BY ordinal_position
|
||||
`, {
|
||||
replacements: { schemaName, tableName },
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
});
|
||||
|
||||
// Hole erwartete Spalten aus dem Model
|
||||
const expectedColumns = Object.keys(model.rawAttributes);
|
||||
|
||||
// Vergleiche aktuelle und erwartete Spalten
|
||||
const missingColumns = expectedColumns.filter(expectedCol => {
|
||||
return !currentColumns.some(currentCol =>
|
||||
currentCol.column_name === expectedCol
|
||||
);
|
||||
});
|
||||
|
||||
if (missingColumns.length > 0) {
|
||||
console.log(` 📊 Fehlende Spalten in ${tableName}: ${missingColumns.join(', ')}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prüfe auf geänderte Spalten-Typen oder Constraints
|
||||
for (const expectedCol of expectedColumns) {
|
||||
const currentCol = currentColumns.find(col => col.column_name === expectedCol);
|
||||
if (currentCol) {
|
||||
const needsUpdate = await checkColumnForUpdates(
|
||||
schemaName, tableName, expectedCol, currentCol, model.rawAttributes[expectedCol]
|
||||
);
|
||||
if (needsUpdate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Fehler beim Prüfen von Tabelle ${schemaName}.${tableName}:`, error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Prüft eine spezifische Spalte auf Updates
|
||||
const checkColumnForUpdates = async (schemaName, tableName, columnName, currentColumn, expectedAttribute) => {
|
||||
try {
|
||||
// Prüfe Datentyp-Änderungen
|
||||
if (currentColumn.data_type !== getExpectedDataType(expectedAttribute)) {
|
||||
console.log(` 🔄 Spalte ${columnName}: Datentyp geändert (${currentColumn.data_type} → ${getExpectedDataType(expectedAttribute)})`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prüfe NULL/NOT NULL Änderungen
|
||||
const currentNullable = currentColumn.is_nullable === 'YES';
|
||||
const expectedNullable = expectedAttribute.allowNull !== false;
|
||||
if (currentNullable !== expectedNullable) {
|
||||
console.log(` 🔄 Spalte ${columnName}: NULL-Constraint geändert (${currentNullable} → ${expectedNullable})`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prüfe Standardwert-Änderungen
|
||||
if (expectedAttribute.defaultValue !== undefined &&
|
||||
currentColumn.column_default !== getExpectedDefaultValue(expectedAttribute.defaultValue)) {
|
||||
console.log(` 🔄 Spalte ${columnName}: Standardwert geändert`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Fehler beim Prüfen von Spalte ${columnName}:`, error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Hilfsfunktion: Findet Model für eine Tabelle
|
||||
const findModelForTable = (schemaName, tableName, models) => {
|
||||
// Suche nach dem Model basierend auf Schema und Tabellenname
|
||||
for (const [modelName, model] of Object.entries(models)) {
|
||||
if (model.tableName === tableName &&
|
||||
model._schema === schemaName) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Hilfsfunktion: Konvertiert Sequelize-Datentyp zu PostgreSQL-Datentyp
|
||||
const getExpectedDataType = (attribute) => {
|
||||
const type = attribute.type;
|
||||
|
||||
if (type instanceof sequelize.DataTypes.INTEGER) return 'integer';
|
||||
if (type instanceof sequelize.DataTypes.STRING) return 'character varying';
|
||||
if (type instanceof sequelize.DataTypes.TEXT) return 'text';
|
||||
if (type instanceof sequelize.DataTypes.BOOLEAN) return 'boolean';
|
||||
if (type instanceof sequelize.DataTypes.DATE) return 'timestamp without time zone';
|
||||
if (type instanceof sequelize.DataTypes.JSON) return 'json';
|
||||
if (type instanceof sequelize.DataTypes.DECIMAL) return 'numeric';
|
||||
|
||||
// Fallback
|
||||
return 'text';
|
||||
};
|
||||
|
||||
// Hilfsfunktion: Konvertiert Sequelize-Default zu PostgreSQL-Default
|
||||
const getExpectedDefaultValue = (defaultValue) => {
|
||||
if (defaultValue === null) return null;
|
||||
if (typeof defaultValue === 'string') return `'${defaultValue}'`;
|
||||
if (typeof defaultValue === 'number') return defaultValue.toString();
|
||||
if (typeof defaultValue === 'boolean') return defaultValue.toString();
|
||||
if (defaultValue === sequelize.literal('CURRENT_TIMESTAMP')) return 'CURRENT_TIMESTAMP';
|
||||
|
||||
// Fallback
|
||||
return defaultValue?.toString() || null;
|
||||
};
|
||||
|
||||
// Separate Funktion für Schema-Updates (nur bei Bedarf aufrufen)
|
||||
const updateSchema = async (models) => {
|
||||
console.log('🔄 Aktualisiere Datenbankschema...');
|
||||
for (const model of Object.values(models)) {
|
||||
await model.sync({ alter: true, force: false });
|
||||
}
|
||||
console.log('✅ Datenbankschema aktualisiert');
|
||||
};
|
||||
|
||||
async function updateFalukantUserMoney(falukantUserId, moneyChange, activity, changedBy = null) {
|
||||
@@ -70,4 +353,4 @@ async function updateFalukantUserMoney(falukantUserId, moneyChange, activity, ch
|
||||
}
|
||||
}
|
||||
|
||||
export { sequelize, initializeDatabase, syncModels, updateFalukantUserMoney };
|
||||
export { sequelize, initializeDatabase, syncModels, syncModelsWithUpdates, updateSchema, updateFalukantUserMoney };
|
||||
|
||||
Reference in New Issue
Block a user