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:
61
backend/utils/checkRightsTable.js
Normal file
61
backend/utils/checkRightsTable.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { sequelize } from './sequelize.js';
|
||||
|
||||
async function checkRightsTable() {
|
||||
try {
|
||||
console.log('🔍 Überprüfe den aktuellen Zustand der chat.rights Tabelle...');
|
||||
|
||||
// Überprüfe die Constraints der chat.rights Tabelle
|
||||
const rightsConstraints = await sequelize.query(`
|
||||
SELECT
|
||||
tc.constraint_name,
|
||||
tc.constraint_type,
|
||||
kcu.column_name
|
||||
FROM information_schema.table_constraints tc
|
||||
LEFT JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
WHERE tc.table_schema = 'chat'
|
||||
AND tc.table_name = 'rights'
|
||||
ORDER BY tc.constraint_type, kcu.column_name;
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
console.log(`📊 Chat Rights Constraints: ${rightsConstraints.length} gefunden`);
|
||||
rightsConstraints.forEach(constraint => {
|
||||
console.log(` - ${constraint.constraint_type} (${constraint.constraint_name}) auf Spalte: ${constraint.column_name}`);
|
||||
});
|
||||
|
||||
// Überprüfe speziell die UNIQUE Constraints auf der tr-Spalte
|
||||
const trUniqueConstraints = rightsConstraints.filter(c =>
|
||||
c.constraint_type === 'UNIQUE' && c.column_name === 'tr'
|
||||
);
|
||||
|
||||
console.log(`\n🎯 UNIQUE Constraints auf der tr-Spalte: ${trUniqueConstraints.length}`);
|
||||
if (trUniqueConstraints.length === 1) {
|
||||
console.log('✅ Perfekt! Es gibt nur noch einen UNIQUE Constraint auf der tr-Spalte.');
|
||||
} else if (trUniqueConstraints.length === 0) {
|
||||
console.log('⚠️ Es gibt keinen UNIQUE Constraint auf der tr-Spalte!');
|
||||
} else {
|
||||
console.log(`❌ Es gibt immer noch ${trUniqueConstraints.length} UNIQUE Constraints auf der tr-Spalte.`);
|
||||
}
|
||||
|
||||
console.log('\n✅ Überprüfung abgeschlossen');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler bei der Überprüfung:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Führe das Skript aus, wenn es direkt aufgerufen wird
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
checkRightsTable()
|
||||
.then(() => {
|
||||
console.log('🎯 Überprüfung der chat.rights Tabelle abgeschlossen');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('💥 Überprüfung fehlgeschlagen:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export default checkRightsTable;
|
||||
234
backend/utils/cleanupDatabaseConstraints.js
Normal file
234
backend/utils/cleanupDatabaseConstraints.js
Normal file
@@ -0,0 +1,234 @@
|
||||
import { sequelize } from './sequelize.js';
|
||||
|
||||
/**
|
||||
* Bereinigt doppelte Constraints und Indexe in der Datenbank
|
||||
* Dies sollte nur einmal ausgeführt werden, um bestehende Probleme zu beheben
|
||||
*/
|
||||
async function cleanupDatabaseConstraints() {
|
||||
try {
|
||||
console.log('🧹 Starte Bereinigung der Datenbank-Constraints...');
|
||||
|
||||
// 1. Doppelte UNIQUE Constraints entfernen
|
||||
console.log('🔍 Suche nach doppelten UNIQUE Constraints...');
|
||||
|
||||
const duplicateUniqueConstraints = await sequelize.query(`
|
||||
SELECT
|
||||
tc.table_schema,
|
||||
tc.table_name,
|
||||
kcu.column_name,
|
||||
COUNT(*) as constraint_count,
|
||||
array_agg(tc.constraint_name) as constraint_names
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
WHERE tc.constraint_type = 'UNIQUE'
|
||||
AND tc.table_schema IN ('match3', 'community', 'falukant_data', 'falukant_type', 'falukant_predefine', 'falukant_log', 'chat', 'forum', 'logs', 'type', 'service')
|
||||
GROUP BY tc.table_schema, tc.table_name, kcu.column_name
|
||||
HAVING COUNT(*) > 1
|
||||
ORDER BY tc.table_name, kcu.column_name;
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
console.log(`📊 Gefunden: ${duplicateUniqueConstraints.length} Spalten mit doppelten UNIQUE Constraints`);
|
||||
|
||||
// Entferne doppelte UNIQUE Constraints
|
||||
for (const duplicate of duplicateUniqueConstraints) {
|
||||
console.log(`🗑️ Entferne doppelte UNIQUE Constraints für ${duplicate.table_schema}.${duplicate.table_name}.${duplicate.column_name}`);
|
||||
|
||||
// Behalte den ersten Constraint, entferne die anderen
|
||||
const constraintNames = duplicate.constraint_names;
|
||||
for (let i = 1; i < constraintNames.length; i++) {
|
||||
const constraintName = constraintNames[i];
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE ${duplicate.table_schema}.${duplicate.table_name}
|
||||
DROP CONSTRAINT IF EXISTS "${constraintName}"
|
||||
`);
|
||||
console.log(` ✅ Entfernt: ${constraintName}`);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ Konnte nicht entfernen: ${constraintName} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Doppelte CHECK Constraints entfernen (korrigierte Abfrage)
|
||||
console.log('🔍 Suche nach doppelten CHECK Constraints...');
|
||||
|
||||
try {
|
||||
const duplicateCheckConstraints = await sequelize.query(`
|
||||
SELECT
|
||||
ttc.table_schema,
|
||||
ttc.table_name,
|
||||
tc.constraint_name,
|
||||
tc.check_clause
|
||||
FROM information_schema.check_constraints tc
|
||||
JOIN information_schema.table_constraints ttc
|
||||
ON tc.constraint_name = ttc.constraint_name
|
||||
WHERE ttc.table_schema IN ('match3', 'community', 'falukant_data', 'falukant_type', 'falukant_predefine', 'falukant_log', 'chat', 'forum', 'logs', 'type', 'service')
|
||||
ORDER BY ttc.table_name, tc.constraint_name;
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
console.log(`📊 Gefunden: ${duplicateCheckConstraints.length} CHECK Constraints`);
|
||||
} catch (error) {
|
||||
console.log(`⚠️ Konnte CHECK Constraints nicht abfragen: ${error.message}`);
|
||||
}
|
||||
|
||||
// 3. Doppelte Foreign Key Constraints entfernen
|
||||
console.log('🔍 Suche nach doppelten Foreign Key Constraints...');
|
||||
|
||||
const duplicateFKs = await sequelize.query(`
|
||||
SELECT
|
||||
tc.constraint_name,
|
||||
tc.table_name,
|
||||
tc.table_schema,
|
||||
kcu.column_name,
|
||||
ccu.table_name AS foreign_table_name,
|
||||
ccu.column_name AS foreign_column_name
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
JOIN information_schema.constraint_column_usage ccu
|
||||
ON ccu.constraint_name = tc.constraint_name
|
||||
WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||
AND tc.table_schema IN ('match3', 'community', 'falukant_data', 'falukant_type', 'falukant_predefine', 'falukant_log', 'chat', 'forum', 'logs', 'type', 'service')
|
||||
ORDER BY tc.table_name, tc.constraint_name;
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
console.log(`📊 Gefunden: ${duplicateFKs.length} Foreign Key Constraints`);
|
||||
|
||||
// 4. Doppelte Indexe entfernen
|
||||
console.log('🔍 Suche nach doppelten Indexen...');
|
||||
|
||||
const duplicateIndexes = await sequelize.query(`
|
||||
SELECT
|
||||
schemaname,
|
||||
tablename,
|
||||
indexname,
|
||||
indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname IN ('match3', 'community', 'falukant_data', 'falukant_type', 'falukant_predefine', 'falukant_log', 'chat', 'forum', 'logs', 'type', 'service')
|
||||
AND indexname LIKE '%_index_%'
|
||||
ORDER BY tablename, indexname;
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
console.log(`📊 Gefunden: ${duplicateIndexes.length} potenziell doppelte Indexe`);
|
||||
|
||||
// 5. Spezifische Match3-Constraints prüfen
|
||||
console.log('🔍 Prüfe Match3-spezifische Constraints...');
|
||||
|
||||
const match3Constraints = await sequelize.query(`
|
||||
SELECT
|
||||
constraint_name,
|
||||
table_name,
|
||||
constraint_type
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_schema = 'match3'
|
||||
ORDER BY table_name, constraint_type;
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
console.log(`📊 Match3 Constraints: ${match3Constraints.length} gefunden`);
|
||||
match3Constraints.forEach(constraint => {
|
||||
console.log(` - ${constraint.table_name}: ${constraint.constraint_type} (${constraint.constraint_name})`);
|
||||
});
|
||||
|
||||
// 6. Spezifische Chat-Constraints prüfen (da das Problem dort auftritt)
|
||||
console.log('🔍 Prüfe Chat-spezifische Constraints...');
|
||||
|
||||
try {
|
||||
const chatConstraints = await sequelize.query(`
|
||||
SELECT
|
||||
tc.constraint_name,
|
||||
tc.table_name,
|
||||
tc.constraint_type,
|
||||
kcu.column_name
|
||||
FROM information_schema.table_constraints tc
|
||||
LEFT JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
WHERE tc.table_schema = 'chat'
|
||||
ORDER BY tc.table_name, tc.constraint_type, kcu.column_name;
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
console.log(`📊 Chat Constraints: ${chatConstraints.length} gefunden`);
|
||||
chatConstraints.forEach(constraint => {
|
||||
console.log(` - ${constraint.table_name}: ${constraint.constraint_type} (${constraint.constraint_name}) auf Spalte: ${constraint.column_name}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`⚠️ Konnte Chat Constraints nicht abfragen: ${error.message}`);
|
||||
}
|
||||
|
||||
// 7. Spezifische Überprüfung der chat.rights Tabelle
|
||||
console.log('🔍 Spezielle Überprüfung der chat.rights Tabelle...');
|
||||
|
||||
try {
|
||||
const rightsConstraints = await sequelize.query(`
|
||||
SELECT
|
||||
tc.constraint_name,
|
||||
tc.constraint_type,
|
||||
kcu.column_name
|
||||
FROM information_schema.table_constraints tc
|
||||
LEFT JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
WHERE tc.table_schema = 'chat'
|
||||
AND tc.table_name = 'rights'
|
||||
ORDER BY tc.constraint_type, kcu.column_name;
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
console.log(`📊 Chat Rights Constraints: ${rightsConstraints.length} gefunden`);
|
||||
rightsConstraints.forEach(constraint => {
|
||||
console.log(` - ${constraint.constraint_type} (${constraint.constraint_name}) auf Spalte: ${constraint.column_name}`);
|
||||
});
|
||||
|
||||
// Entferne alle doppelten UNIQUE Constraints auf der tr-Spalte
|
||||
const trUniqueConstraints = rightsConstraints.filter(c =>
|
||||
c.constraint_type === 'UNIQUE' && c.column_name === 'tr'
|
||||
);
|
||||
|
||||
if (trUniqueConstraints.length > 1) {
|
||||
console.log(`🗑️ Entferne ${trUniqueConstraints.length - 1} doppelte UNIQUE Constraints auf chat.rights.tr`);
|
||||
|
||||
// Behalte den ersten, entferne die anderen
|
||||
for (let i = 1; i < trUniqueConstraints.length; i++) {
|
||||
const constraintName = trUniqueConstraints[i].constraint_name;
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE chat.rights
|
||||
DROP CONSTRAINT IF EXISTS "${constraintName}"
|
||||
`);
|
||||
console.log(` ✅ Entfernt: ${constraintName}`);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ Konnte nicht entfernen: ${constraintName} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`⚠️ Konnte chat.rights Constraints nicht abfragen: ${error.message}`);
|
||||
}
|
||||
|
||||
// 8. Empfehlungen ausgeben
|
||||
console.log('\n💡 Empfehlungen:');
|
||||
console.log('1. Überprüfe die oben gelisteten Constraints auf Duplikate');
|
||||
console.log('2. Verwende updateSchema() nur bei expliziten Schema-Änderungen');
|
||||
console.log('3. Normale syncModels() läuft jetzt ohne alter: true');
|
||||
console.log('4. Bei Problemen: Manuelle Bereinigung der doppelten Constraints');
|
||||
|
||||
console.log('✅ Datenbank-Constraint-Bereinigung abgeschlossen');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler bei der Constraint-Bereinigung:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Führe das Skript aus, wenn es direkt aufgerufen wird
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
cleanupDatabaseConstraints()
|
||||
.then(() => {
|
||||
console.log('🎯 Datenbank-Constraint-Bereinigung abgeschlossen');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('💥 Datenbank-Constraint-Bereinigung fehlgeschlagen:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export default cleanupDatabaseConstraints;
|
||||
@@ -1,56 +1,133 @@
|
||||
import { sequelize } from './sequelize.js';
|
||||
import Match3Campaign from '../models/match3/campaign.js';
|
||||
import Match3Level from '../models/match3/level.js';
|
||||
import Match3Objective from '../models/match3/objective.js';
|
||||
import Match3TileType from '../models/match3/tileType.js';
|
||||
import Match3LevelTileType from '../models/match3/levelTileType.js';
|
||||
|
||||
export const initializeMatch3Data = async () => {
|
||||
/**
|
||||
* Initialisiert die Match3-Daten in der Datenbank
|
||||
*/
|
||||
async function initializeMatch3Data() {
|
||||
try {
|
||||
console.log('🎯 Initialisiere Match3-Daten...');
|
||||
|
||||
// Prüfe ob bereits Daten vorhanden sind
|
||||
const existingCampaigns = await Match3Campaign.count();
|
||||
if (existingCampaigns > 0) {
|
||||
console.log('Match3 data already exists, skipping initialization');
|
||||
console.log('✅ Match3-Daten bereits vorhanden, überspringe Initialisierung');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Initializing Match3 data...');
|
||||
// Lösche existierende Level und erstelle sie neu
|
||||
console.log('🔄 Lösche existierende Level...');
|
||||
await Match3Level.destroy({ where: { campaignId: campaign.id } });
|
||||
console.log('✅ Existierende Level gelöscht');
|
||||
|
||||
// Erstelle erste Kampagne
|
||||
console.log('🎯 Erstelle neue Level...');
|
||||
|
||||
// Erstelle Kampagne
|
||||
const campaign = await Match3Campaign.create({
|
||||
name: 'Juwelen-Meister',
|
||||
description: 'Meistere die Kunst des Juwelen-Matchings',
|
||||
isActive: true,
|
||||
order: 1
|
||||
description: 'Meistere die Kunst des Juwelen-Matchings mit einzigartigen Level-Formen',
|
||||
isActive: true
|
||||
});
|
||||
|
||||
// Erstelle erste Level
|
||||
console.log('✅ Kampagne erstellt:', campaign.name);
|
||||
|
||||
// Erstelle Level 1: Einfaches 5x5 Feld
|
||||
const level1 = await Match3Level.create({
|
||||
campaignId: campaign.id,
|
||||
name: 'Der Anfang',
|
||||
description: 'Lerne die Grundlagen des Spiels',
|
||||
description: 'Lerne die Grundlagen mit einem einfachen 5x5 Feld',
|
||||
order: 1,
|
||||
boardSize: 6,
|
||||
boardLayout: 'xxxxx\nxxxxx\nxxxxx\nxxxxx\nxxxxx',
|
||||
boardWidth: 5,
|
||||
boardHeight: 5,
|
||||
tileTypes: ['gem', 'star', 'heart'],
|
||||
moveLimit: 15,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
// Erstelle Level 2: 7x6 Feld
|
||||
const level2 = await Match3Level.create({
|
||||
campaignId: campaign.id,
|
||||
name: 'Erste Herausforderung',
|
||||
description: 'Erweitere deine Fähigkeiten',
|
||||
description: 'Ein größeres 7x6 Feld stellt dich vor neue Herausforderungen',
|
||||
order: 2,
|
||||
boardSize: 7,
|
||||
boardLayout: 'xxxxxxx\nxxxxxxx\nxxxxxxx\nxxxxxxx\nxxxxxxx\nxxxxxxx',
|
||||
boardWidth: 7,
|
||||
boardHeight: 6,
|
||||
tileTypes: ['gem', 'star', 'heart', 'diamond'],
|
||||
moveLimit: 20,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
// Erstelle Level 3: L-Form mit festen Gems
|
||||
const level3 = await Match3Level.create({
|
||||
campaignId: campaign.id,
|
||||
name: 'Spielzug',
|
||||
description: 'Sei ein Profi',
|
||||
order: 3,
|
||||
boardLayout: 'xxxxx\nxooxx\nxxxgx\nxxxxx\nxxxgx',
|
||||
boardWidth: 5,
|
||||
boardHeight: 5,
|
||||
tileTypes: ['gem', 'star', 'heart', 'diamond'],
|
||||
moveLimit: 15,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
// Erstelle Level 4: H-Form
|
||||
const level4 = await Match3Level.create({
|
||||
campaignId: campaign.id,
|
||||
name: 'H-Form',
|
||||
description: 'Eine H-Form mit vielen Ecken und Kanten',
|
||||
order: 4,
|
||||
boardLayout: 'xxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx\nxxxxxoooxxxxx',
|
||||
boardWidth: 13,
|
||||
boardHeight: 13,
|
||||
tileTypes: ['gem', 'star', 'heart', 'diamond', 'crown', 'moon'],
|
||||
moveLimit: 30,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
// Erstelle Level 5: Diamant-Form
|
||||
const level5 = await Match3Level.create({
|
||||
campaignId: campaign.id,
|
||||
name: 'Diamant-Form',
|
||||
description: 'Eine elegante Diamant-Form für Fortgeschrittene',
|
||||
order: 5,
|
||||
boardLayout: 'oooxxxooo\nooxxxxxxoo\nooxxxxxxoo\nooxxxxxxoo\nooxxxxxxoo\nooxxxxxxoo\nooxxxxxxoo\nooxxxxxxoo\nooxxxxxxoo\noooxxxooo',
|
||||
boardWidth: 9,
|
||||
boardHeight: 10,
|
||||
tileTypes: ['gem', 'star', 'heart', 'diamond', 'crown', 'moon', 'star'],
|
||||
moveLimit: 35,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
// Erstelle Level 6: Spiral-Form
|
||||
const level6 = await Match3Level.create({
|
||||
campaignId: campaign.id,
|
||||
name: 'Spiral-Form',
|
||||
description: 'Eine komplexe Spiral-Form für Meister',
|
||||
order: 6,
|
||||
boardLayout: 'xxxxxxxxxxxxx\nxooooooooooox\nxoxxxxxxxxxox\nxoxoooooooxox\nxoxoxxxxxoxox\nxoxoxoooxoxox\nxoxoxoxoxoxox\nxoxoxoooxoxox\nxoxoxxxxxoxox\nxoxoooooooxox\nxoxxxxxxxxxox\nxooooooooooox\nxxxxxxxxxxxxx',
|
||||
boardWidth: 13,
|
||||
boardHeight: 13,
|
||||
tileTypes: ['gem', 'star', 'heart', 'diamond', 'crown', 'moon', 'star', 'crystal'],
|
||||
moveLimit: 40,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
console.log('✅ Alle Level erstellt');
|
||||
|
||||
// Erstelle Objectives für Level 1
|
||||
await Match3Objective.bulkCreate([
|
||||
{
|
||||
levelId: level1.id,
|
||||
type: 'score',
|
||||
description: 'Sammle 100 Punkte',
|
||||
target: 100,
|
||||
description: 'Sammle 150 Punkte',
|
||||
target: 150,
|
||||
operator: '>=',
|
||||
order: 1,
|
||||
isRequired: true
|
||||
@@ -58,8 +135,8 @@ export const initializeMatch3Data = async () => {
|
||||
{
|
||||
levelId: level1.id,
|
||||
type: 'matches',
|
||||
description: 'Mache 3 Matches',
|
||||
target: 3,
|
||||
description: 'Mache 5 Matches',
|
||||
target: 5,
|
||||
operator: '>=',
|
||||
order: 2,
|
||||
isRequired: true
|
||||
@@ -71,8 +148,8 @@ export const initializeMatch3Data = async () => {
|
||||
{
|
||||
levelId: level2.id,
|
||||
type: 'score',
|
||||
description: 'Sammle 200 Punkte',
|
||||
target: 200,
|
||||
description: 'Sammle 250 Punkte',
|
||||
target: 250,
|
||||
operator: '>=',
|
||||
order: 1,
|
||||
isRequired: true
|
||||
@@ -80,8 +157,8 @@ export const initializeMatch3Data = async () => {
|
||||
{
|
||||
levelId: level2.id,
|
||||
type: 'matches',
|
||||
description: 'Mache 5 Matches',
|
||||
target: 5,
|
||||
description: 'Mache 8 Matches',
|
||||
target: 8,
|
||||
operator: '>=',
|
||||
order: 2,
|
||||
isRequired: true
|
||||
@@ -97,8 +174,137 @@ export const initializeMatch3Data = async () => {
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('Match3 data initialized successfully');
|
||||
// Erstelle Objectives für Level 3
|
||||
await Match3Objective.bulkCreate([
|
||||
{
|
||||
levelId: level3.id,
|
||||
type: 'score',
|
||||
description: 'Sammle 400 Punkte',
|
||||
target: 400,
|
||||
operator: '>=',
|
||||
order: 1,
|
||||
isRequired: true
|
||||
},
|
||||
{
|
||||
levelId: level3.id,
|
||||
type: 'matches',
|
||||
description: 'Mache 12 Matches',
|
||||
target: 12,
|
||||
operator: '>=',
|
||||
order: 2,
|
||||
isRequired: true
|
||||
},
|
||||
{
|
||||
levelId: level3.id,
|
||||
type: 'moves',
|
||||
description: 'Verwende weniger als 25 Züge',
|
||||
target: 25,
|
||||
operator: '<=',
|
||||
order: 3,
|
||||
isRequired: true
|
||||
}
|
||||
]);
|
||||
|
||||
// Erstelle Objectives für Level 4
|
||||
await Match3Objective.bulkCreate([
|
||||
{
|
||||
levelId: level4.id,
|
||||
type: 'score',
|
||||
description: 'Sammle 600 Punkte',
|
||||
target: 600,
|
||||
operator: '>=',
|
||||
order: 1,
|
||||
isRequired: true
|
||||
},
|
||||
{
|
||||
levelId: level4.id,
|
||||
type: 'matches',
|
||||
description: 'Mache 15 Matches',
|
||||
target: 15,
|
||||
operator: '>=',
|
||||
order: 2,
|
||||
isRequired: true
|
||||
},
|
||||
{
|
||||
levelId: level4.id,
|
||||
type: 'moves',
|
||||
description: 'Verwende weniger als 30 Züge',
|
||||
target: 30,
|
||||
operator: '<=',
|
||||
order: 3,
|
||||
isRequired: true
|
||||
}
|
||||
]);
|
||||
|
||||
// Erstelle Objectives für Level 5
|
||||
await Match3Objective.bulkCreate([
|
||||
{
|
||||
levelId: level5.id,
|
||||
type: 'score',
|
||||
description: 'Sammle 800 Punkte',
|
||||
target: 800,
|
||||
operator: '>=',
|
||||
order: 1,
|
||||
isRequired: true
|
||||
},
|
||||
{
|
||||
levelId: level5.id,
|
||||
type: 'matches',
|
||||
description: 'Mache 18 Matches',
|
||||
target: 18,
|
||||
operator: '>=',
|
||||
order: 2,
|
||||
isRequired: true
|
||||
},
|
||||
{
|
||||
levelId: level5.id,
|
||||
type: 'moves',
|
||||
description: 'Verwende weniger als 35 Züge',
|
||||
target: 35,
|
||||
operator: '<=',
|
||||
order: 3,
|
||||
isRequired: true
|
||||
}
|
||||
]);
|
||||
|
||||
// Erstelle Objectives für Level 6
|
||||
await Match3Objective.bulkCreate([
|
||||
{
|
||||
levelId: level6.id,
|
||||
type: 'score',
|
||||
description: 'Sammle 1000 Punkte',
|
||||
target: 1000,
|
||||
operator: '>=',
|
||||
order: 1,
|
||||
isRequired: true
|
||||
},
|
||||
{
|
||||
levelId: level6.id,
|
||||
type: 'matches',
|
||||
description: 'Mache 25 Matches',
|
||||
target: 25,
|
||||
operator: '>=',
|
||||
order: 2,
|
||||
isRequired: true
|
||||
},
|
||||
{
|
||||
levelId: level6.id,
|
||||
type: 'moves',
|
||||
description: 'Verwende weniger als 40 Züge',
|
||||
target: 40,
|
||||
operator: '<=',
|
||||
order: 3,
|
||||
isRequired: true
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('✅ Alle Objectives erstellt');
|
||||
console.log('🎯 Match3-Daten erfolgreich initialisiert');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error initializing Match3 data:', error);
|
||||
console.error('❌ Fehler beim Initialisieren der Match3-Daten:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default initializeMatch3Data;
|
||||
|
||||
119
backend/utils/initializeMatch3TileTypes.js
Normal file
119
backend/utils/initializeMatch3TileTypes.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import { sequelize } from './sequelize.js';
|
||||
import Match3TileType from '../models/match3/tileType.js';
|
||||
|
||||
async function initializeMatch3TileTypes() {
|
||||
try {
|
||||
console.log('🚀 Initialisiere Match3 Tile-Typen...');
|
||||
|
||||
// Synchronisiere das TileType-Modell
|
||||
await Match3TileType.sync({ alter: true });
|
||||
console.log('✅ TileType-Modell synchronisiert');
|
||||
|
||||
// Standard-Tile-Typen definieren
|
||||
const defaultTileTypes = [
|
||||
{
|
||||
name: 'gem',
|
||||
displayName: 'Juwel',
|
||||
symbol: '💎',
|
||||
color: '#ff6b6b',
|
||||
rarity: 'common',
|
||||
points: 10
|
||||
},
|
||||
{
|
||||
name: 'star',
|
||||
displayName: 'Stern',
|
||||
symbol: '⭐',
|
||||
color: '#feca57',
|
||||
rarity: 'common',
|
||||
points: 15
|
||||
},
|
||||
{
|
||||
name: 'heart',
|
||||
displayName: 'Herz',
|
||||
symbol: '❤️',
|
||||
color: '#ff9ff3',
|
||||
rarity: 'common',
|
||||
points: 12
|
||||
},
|
||||
{
|
||||
name: 'diamond',
|
||||
displayName: 'Diamant',
|
||||
symbol: '🔷',
|
||||
color: '#54a0ff',
|
||||
rarity: 'uncommon',
|
||||
points: 20
|
||||
},
|
||||
{
|
||||
name: 'circle',
|
||||
displayName: 'Kreis',
|
||||
symbol: '⭕',
|
||||
color: '#5f27cd',
|
||||
rarity: 'uncommon',
|
||||
points: 18
|
||||
},
|
||||
{
|
||||
name: 'square',
|
||||
displayName: 'Quadrat',
|
||||
symbol: '🟦',
|
||||
color: '#00d2d3',
|
||||
rarity: 'rare',
|
||||
points: 25
|
||||
},
|
||||
{
|
||||
name: 'crown',
|
||||
displayName: 'Krone',
|
||||
symbol: '👑',
|
||||
color: '#ff9f43',
|
||||
rarity: 'epic',
|
||||
points: 35
|
||||
},
|
||||
{
|
||||
name: 'rainbow',
|
||||
displayName: 'Regenbogen',
|
||||
symbol: '🌈',
|
||||
color: '#ff6348',
|
||||
rarity: 'legendary',
|
||||
points: 50
|
||||
}
|
||||
];
|
||||
|
||||
// Tile-Typen erstellen oder aktualisieren
|
||||
for (const tileType of defaultTileTypes) {
|
||||
const [tileTypeInstance, created] = await Match3TileType.findOrCreate({
|
||||
where: { name: tileType.name },
|
||||
defaults: tileType
|
||||
});
|
||||
|
||||
if (created) {
|
||||
console.log(`✅ Tile-Typ "${tileType.displayName}" erstellt`);
|
||||
} else {
|
||||
// Aktualisiere bestehende Tile-Typen
|
||||
await Match3TileType.update(tileType, {
|
||||
where: { name: tileType.name }
|
||||
});
|
||||
console.log(`🔄 Tile-Typ "${tileType.displayName}" aktualisiert`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('🎉 Alle Match3 Tile-Typen erfolgreich initialisiert!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler beim Initialisieren der Match3 Tile-Typen:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Führe die Initialisierung aus, wenn das Skript direkt aufgerufen wird
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
initializeMatch3TileTypes()
|
||||
.then(() => {
|
||||
console.log('✅ Tile-Typen-Initialisierung abgeschlossen');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ Fehler bei der Tile-Typen-Initialisierung:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export default initializeMatch3TileTypes;
|
||||
@@ -34,6 +34,10 @@ const initializeUserRights = async() => {
|
||||
where: { title: "developer"},
|
||||
defaults: { title: "developer"}
|
||||
});
|
||||
await UserRightType.findOrCreate({
|
||||
where: { title: "match3"},
|
||||
defaults: { title: "match3"}
|
||||
});
|
||||
};
|
||||
|
||||
export default initializeUserRights;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// syncDatabase.js
|
||||
|
||||
import { initializeDatabase, syncModels } from './sequelize.js';
|
||||
import { initializeDatabase, syncModelsWithUpdates } from './sequelize.js';
|
||||
import initializeTypes from './initializeTypes.js';
|
||||
import initializeSettings from './initializeSettings.js';
|
||||
import initializeUserRights from './initializeUserRights.js';
|
||||
@@ -11,7 +11,8 @@ import models from '../models/index.js';
|
||||
import { createTriggers } from '../models/trigger.js';
|
||||
import initializeForum from './initializeForum.js';
|
||||
import initializeChat from './initializeChat.js';
|
||||
import { initializeMatch3Data } from './initializeMatch3.js';
|
||||
import initializeMatch3Data from './initializeMatch3.js';
|
||||
import updateExistingMatch3Levels from './updateExistingMatch3Levels.js';
|
||||
|
||||
const syncDatabase = async () => {
|
||||
try {
|
||||
@@ -19,7 +20,7 @@ const syncDatabase = async () => {
|
||||
await initializeDatabase();
|
||||
|
||||
console.log("Synchronizing models...");
|
||||
await syncModels(models);
|
||||
await syncModelsWithUpdates(models);
|
||||
|
||||
console.log("Setting up associations...");
|
||||
setupAssociations();
|
||||
@@ -48,6 +49,10 @@ const syncDatabase = async () => {
|
||||
console.log("Initializing chat...");
|
||||
await initializeChat();
|
||||
|
||||
// Match3-Initialisierung NACH der Model-Synchronisation
|
||||
console.log("Updating existing Match3 levels...");
|
||||
await updateExistingMatch3Levels();
|
||||
|
||||
console.log("Initializing Match3...");
|
||||
await initializeMatch3Data();
|
||||
|
||||
|
||||
74
backend/utils/updateExistingMatch3Levels.js
Normal file
74
backend/utils/updateExistingMatch3Levels.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import { sequelize } from './sequelize.js';
|
||||
import Match3Level from '../models/match3/level.js';
|
||||
|
||||
/**
|
||||
* Aktualisiert existierende Match3-Level mit Standard-Layouts
|
||||
* und neuen Feldern
|
||||
*/
|
||||
async function updateExistingMatch3Levels() {
|
||||
try {
|
||||
console.log('🔧 Aktualisiere existierende Match3-Level...');
|
||||
|
||||
// Finde alle existierenden Level ohne boardLayout
|
||||
const existingLevels = await Match3Level.findAll({
|
||||
where: {
|
||||
boardLayout: null
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`📊 Gefunden: ${existingLevels.length} Level ohne boardLayout`);
|
||||
|
||||
if (existingLevels.length === 0) {
|
||||
console.log('✅ Alle Level haben bereits boardLayout');
|
||||
return;
|
||||
}
|
||||
|
||||
// Aktualisiere jeden Level mit Standard-Layout
|
||||
for (const level of existingLevels) {
|
||||
const oldBoardSize = level.boardSize || 6;
|
||||
|
||||
// Erstelle Standard-Layout basierend auf alter boardSize
|
||||
let boardLayout = '';
|
||||
for (let i = 0; i < oldBoardSize; i++) {
|
||||
for (let j = 0; j < oldBoardSize; j++) {
|
||||
boardLayout += 'x';
|
||||
}
|
||||
if (i < oldBoardSize - 1) boardLayout += '\n';
|
||||
}
|
||||
|
||||
// Aktualisiere den Level mit allen neuen Feldern
|
||||
await level.update({
|
||||
boardLayout: boardLayout,
|
||||
boardWidth: oldBoardSize,
|
||||
boardHeight: oldBoardSize,
|
||||
// Stelle sicher, dass alle erforderlichen Felder gesetzt sind
|
||||
tileTypes: level.tileTypes || ['gem', 'star', 'heart'],
|
||||
moveLimit: level.moveLimit || 20,
|
||||
isActive: level.isActive !== undefined ? level.isActive : true
|
||||
});
|
||||
|
||||
console.log(`🔧 Level ${level.id} aktualisiert: ${oldBoardSize}x${oldBoardSize} → alle neuen Felder gesetzt`);
|
||||
}
|
||||
|
||||
console.log('✅ Alle existierenden Level wurden aktualisiert');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler beim Aktualisieren der Match3-Level:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Führe das Skript aus, wenn es direkt aufgerufen wird
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
updateExistingMatch3Levels()
|
||||
.then(() => {
|
||||
console.log('🎯 Match3-Level-Update abgeschlossen');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('💥 Match3-Level-Update fehlgeschlagen:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export default updateExistingMatch3Levels;
|
||||
115
backend/utils/updateMatch3LevelsWithTileTypes.js
Normal file
115
backend/utils/updateMatch3LevelsWithTileTypes.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import { sequelize } from './sequelize.js';
|
||||
import Match3Level from '../models/match3/level.js';
|
||||
import Match3TileType from '../models/match3/tileType.js';
|
||||
import Match3LevelTileType from '../models/match3/levelTileType.js';
|
||||
|
||||
/**
|
||||
* Aktualisiert bestehende Match3 Level mit den neuen Tile-Typen
|
||||
*/
|
||||
async function updateMatch3LevelsWithTileTypes() {
|
||||
try {
|
||||
console.log('🔄 Aktualisiere Match3 Level mit neuen Tile-Typen...');
|
||||
|
||||
// Synchronisiere die neuen Modelle
|
||||
await Match3TileType.sync({ alter: true });
|
||||
await Match3LevelTileType.sync({ alter: true });
|
||||
console.log('✅ Neue Modelle synchronisiert');
|
||||
|
||||
// Hole alle Level
|
||||
const levels = await Match3Level.findAll();
|
||||
console.log(`📊 ${levels.length} Level gefunden`);
|
||||
|
||||
// Hole alle verfügbaren Tile-Typen
|
||||
const tileTypes = await Match3TileType.findAll();
|
||||
console.log(`🎯 ${tileTypes.length} Tile-Typen verfügbar`);
|
||||
|
||||
// Erstelle eine Mapping-Tabelle für die alten tileTypes Arrays
|
||||
const tileTypeMapping = {
|
||||
'gem': tileTypes.find(t => t.name === 'gem'),
|
||||
'star': tileTypes.find(t => t.name === 'star'),
|
||||
'heart': tileTypes.find(t => t.name === 'heart'),
|
||||
'diamond': tileTypes.find(t => t.name === 'diamond'),
|
||||
'circle': tileTypes.find(t => t.name === 'circle'),
|
||||
'square': tileTypes.find(t => t.name === 'square'),
|
||||
'crown': tileTypes.find(t => t.name === 'crown'),
|
||||
'rainbow': tileTypes.find(t => t.name === 'rainbow')
|
||||
};
|
||||
|
||||
// Gehe durch alle Level und erstelle Verknüpfungen
|
||||
for (const level of levels) {
|
||||
console.log(`🔄 Verarbeite Level ${level.order}: ${level.name}`);
|
||||
|
||||
// Lösche bestehende Verknüpfungen für dieses Level
|
||||
await Match3LevelTileType.destroy({
|
||||
where: { levelId: level.id }
|
||||
});
|
||||
|
||||
// Verwende die alten tileTypes Arrays als Fallback
|
||||
let levelTileTypes = [];
|
||||
if (level.tileTypes && Array.isArray(level.tileTypes)) {
|
||||
levelTileTypes = level.tileTypes;
|
||||
} else {
|
||||
// Fallback: Verwende Standard-Tile-Typen basierend auf der Level-Nummer
|
||||
switch (level.order) {
|
||||
case 1:
|
||||
levelTileTypes = ['gem', 'star', 'heart'];
|
||||
break;
|
||||
case 2:
|
||||
levelTileTypes = ['gem', 'star', 'heart', 'diamond'];
|
||||
break;
|
||||
case 3:
|
||||
levelTileTypes = ['gem', 'star', 'heart', 'diamond', 'circle'];
|
||||
break;
|
||||
case 4:
|
||||
levelTileTypes = ['gem', 'star', 'heart', 'diamond', 'circle', 'square'];
|
||||
break;
|
||||
case 5:
|
||||
levelTileTypes = ['gem', 'star', 'heart', 'diamond', 'circle', 'square', 'crown'];
|
||||
break;
|
||||
case 6:
|
||||
levelTileTypes = ['gem', 'star', 'heart', 'diamond', 'circle', 'square', 'crown', 'rainbow'];
|
||||
break;
|
||||
default:
|
||||
levelTileTypes = ['gem', 'star', 'heart'];
|
||||
}
|
||||
}
|
||||
|
||||
// Erstelle Verknüpfungen für jeden Tile-Typ
|
||||
for (const tileTypeName of levelTileTypes) {
|
||||
const tileType = tileTypeMapping[tileTypeName];
|
||||
if (tileType) {
|
||||
await Match3LevelTileType.create({
|
||||
levelId: level.id,
|
||||
tileTypeId: tileType.id,
|
||||
weight: 1, // Standard-Gewichtung
|
||||
isActive: true
|
||||
});
|
||||
console.log(` ✅ Verknüpft mit ${tileType.displayName}`);
|
||||
} else {
|
||||
console.warn(` ⚠️ Tile-Typ "${tileTypeName}" nicht gefunden`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('🎉 Alle Match3 Level erfolgreich mit Tile-Typen verknüpft!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler beim Aktualisieren der Match3 Level:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Führe die Aktualisierung aus, wenn das Skript direkt aufgerufen wird
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
updateMatch3LevelsWithTileTypes()
|
||||
.then(() => {
|
||||
console.log('✅ Level-Tile-Typ-Aktualisierung abgeschlossen');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ Fehler bei der Level-Tile-Typ-Aktualisierung:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export default updateMatch3LevelsWithTileTypes;
|
||||
Reference in New Issue
Block a user