feat: Einführung von Umgebungsvariablen und Startskripten für die Backend-Anwendung
- Hinzufügen eines zentralen Skripts zum Laden von Umgebungsvariablen aus einer .env-Datei. - Implementierung von Start- und Entwicklungs-Skripten in der package.json für eine vereinfachte Ausführung der Anwendung. - Bereinigung und Entfernung nicht mehr benötigter Minigame-Modelle und -Services zur Verbesserung der Codebasis. - Anpassungen an den Datenbankmodellen zur Unterstützung von neuen Assoziationen und zur Verbesserung der Lesbarkeit.
This commit is contained in:
@@ -1,57 +0,0 @@
|
||||
import { sequelize } from './sequelize.js';
|
||||
|
||||
async function checkLoginTable() {
|
||||
console.log('🔍 Überprüfe logs.login Tabelle...');
|
||||
|
||||
try {
|
||||
// Prüfe die Spaltenstruktur der logs.login Tabelle
|
||||
const columns = await sequelize.query(
|
||||
`SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'logs' AND table_name = 'login'
|
||||
ORDER BY ordinal_position`,
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
if (columns.length === 0) {
|
||||
console.log('❌ Tabelle logs.login existiert nicht!');
|
||||
} else {
|
||||
console.log('📊 logs.login Tabellen-Struktur:');
|
||||
columns.forEach(col => {
|
||||
const defaultValue = col.column_default ? ` (default: ${col.column_default})` : '';
|
||||
console.log(` ✅ ${col.column_name} (${col.data_type}, nullable: ${col.is_nullable})${defaultValue}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Prüfe auch ein paar Beispieldaten
|
||||
const sampleData = await sequelize.query(
|
||||
`SELECT * FROM logs.login LIMIT 3`,
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
if (sampleData.length > 0) {
|
||||
console.log('\n📋 Beispieldaten:');
|
||||
sampleData.forEach((row, index) => {
|
||||
console.log(` ${index + 1}: ${JSON.stringify(row)}`);
|
||||
});
|
||||
} else {
|
||||
console.log('\n📋 Keine Daten in der Tabelle gefunden.');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler beim Überprüfen der logs.login Tabelle:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Führe die Überprüfung aus
|
||||
checkLoginTable()
|
||||
.then(() => {
|
||||
console.log('\n✅ Login-Tabellenüberprüfung abgeschlossen!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('💥 Login-Tabellenüberprüfung fehlgeschlagen:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -8,7 +8,11 @@ async function cleanupDatabaseConstraints() {
|
||||
try {
|
||||
console.log('🧹 Starte Bereinigung der Datenbank-Constraints...');
|
||||
|
||||
// 1. Doppelte UNIQUE Constraints entfernen
|
||||
// 1. Spezielle Bereinigung für community.user Tabelle
|
||||
console.log('🔍 Spezielle Bereinigung für community.user Tabelle...');
|
||||
await cleanupUserTableConstraints();
|
||||
|
||||
// 2. Doppelte UNIQUE Constraints entfernen
|
||||
console.log('🔍 Suche nach doppelten UNIQUE Constraints...');
|
||||
|
||||
const duplicateUniqueConstraints = await sequelize.query(`
|
||||
@@ -50,7 +54,11 @@ async function cleanupDatabaseConstraints() {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Doppelte CHECK Constraints entfernen (korrigierte Abfrage)
|
||||
// 3. Entferne alle Constraints mit numerischen Suffixen
|
||||
console.log('🔍 Entferne alle Constraints mit numerischen Suffixen...');
|
||||
await removeNumericalSuffixConstraints();
|
||||
|
||||
// 4. Doppelte CHECK Constraints entfernen (korrigierte Abfrage)
|
||||
console.log('🔍 Suche nach doppelten CHECK Constraints...');
|
||||
|
||||
try {
|
||||
@@ -72,7 +80,7 @@ async function cleanupDatabaseConstraints() {
|
||||
console.log(`⚠️ Konnte CHECK Constraints nicht abfragen: ${error.message}`);
|
||||
}
|
||||
|
||||
// 3. Doppelte Foreign Key Constraints entfernen
|
||||
// 5. Doppelte Foreign Key Constraints entfernen
|
||||
console.log('🔍 Suche nach doppelten Foreign Key Constraints...');
|
||||
|
||||
const duplicateFKs = await sequelize.query(`
|
||||
@@ -95,7 +103,7 @@ async function cleanupDatabaseConstraints() {
|
||||
|
||||
console.log(`📊 Gefunden: ${duplicateFKs.length} Foreign Key Constraints`);
|
||||
|
||||
// 4. Doppelte Indexe entfernen
|
||||
// 6. Doppelte Indexe entfernen
|
||||
console.log('🔍 Suche nach doppelten Indexen...');
|
||||
|
||||
const duplicateIndexes = await sequelize.query(`
|
||||
@@ -112,7 +120,7 @@ async function cleanupDatabaseConstraints() {
|
||||
|
||||
console.log(`📊 Gefunden: ${duplicateIndexes.length} potenziell doppelte Indexe`);
|
||||
|
||||
// 5. Spezifische Match3-Constraints prüfen
|
||||
// 7. Spezifische Match3-Constraints prüfen
|
||||
console.log('🔍 Prüfe Match3-spezifische Constraints...');
|
||||
|
||||
const match3Constraints = await sequelize.query(`
|
||||
@@ -130,7 +138,7 @@ async function cleanupDatabaseConstraints() {
|
||||
console.log(` - ${constraint.table_name}: ${constraint.constraint_type} (${constraint.constraint_name})`);
|
||||
});
|
||||
|
||||
// 6. Spezifische Chat-Constraints prüfen (da das Problem dort auftritt)
|
||||
// 8. Spezifische Chat-Constraints prüfen (da das Problem dort auftritt)
|
||||
console.log('🔍 Prüfe Chat-spezifische Constraints...');
|
||||
|
||||
try {
|
||||
@@ -155,7 +163,7 @@ async function cleanupDatabaseConstraints() {
|
||||
console.log(`⚠️ Konnte Chat Constraints nicht abfragen: ${error.message}`);
|
||||
}
|
||||
|
||||
// 7. Spezifische Überprüfung der chat.rights Tabelle
|
||||
// 9. Spezifische Überprüfung der chat.rights Tabelle
|
||||
console.log('🔍 Spezielle Überprüfung der chat.rights Tabelle...');
|
||||
|
||||
try {
|
||||
@@ -203,7 +211,7 @@ async function cleanupDatabaseConstraints() {
|
||||
console.log(`⚠️ Konnte chat.rights Constraints nicht abfragen: ${error.message}`);
|
||||
}
|
||||
|
||||
// 8. Empfehlungen ausgeben
|
||||
// 10. 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');
|
||||
@@ -218,6 +226,133 @@ async function cleanupDatabaseConstraints() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spezielle Bereinigung für die community.user Tabelle
|
||||
* Entfernt alle Constraints mit numerischen Suffixen
|
||||
*/
|
||||
async function cleanupUserTableConstraints() {
|
||||
try {
|
||||
console.log('🔍 Bereinige community.user Tabelle...');
|
||||
|
||||
// Finde alle Constraints der user Tabelle
|
||||
const userConstraints = 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 = 'community'
|
||||
AND tc.table_name = 'user'
|
||||
ORDER BY tc.constraint_type, kcu.column_name;
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
console.log(`📊 Community.user Constraints gefunden: ${userConstraints.length}`);
|
||||
|
||||
// Gruppiere Constraints nach Spalte und Typ
|
||||
const constraintsByColumn = {};
|
||||
userConstraints.forEach(constraint => {
|
||||
const key = `${constraint.constraint_type}_${constraint.column_name || 'general'}`;
|
||||
if (!constraintsByColumn[key]) {
|
||||
constraintsByColumn[key] = [];
|
||||
}
|
||||
constraintsByColumn[key].push(constraint.constraint_name);
|
||||
});
|
||||
|
||||
// Entferne doppelte Constraints, behalte nur einen pro Spalte/Typ
|
||||
for (const [key, constraintNames] of Object.entries(constraintsByColumn)) {
|
||||
if (constraintNames.length > 1) {
|
||||
console.log(`🗑️ Entferne ${constraintNames.length - 1} doppelte Constraints für ${key}`);
|
||||
|
||||
// Sortiere nach Namen, um konsistente Ergebnisse zu erhalten
|
||||
constraintNames.sort();
|
||||
|
||||
// Behalte den ersten, entferne die anderen
|
||||
for (let i = 1; i < constraintNames.length; i++) {
|
||||
const constraintName = constraintNames[i];
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE community."user"
|
||||
DROP CONSTRAINT IF EXISTS "${constraintName}"
|
||||
`);
|
||||
console.log(` ✅ Entfernt: ${constraintName}`);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ Konnte nicht entfernen: ${constraintName} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entferne alle Constraints mit numerischen Suffixen
|
||||
const numericalConstraints = userConstraints.filter(c =>
|
||||
/\d+$/.test(c.constraint_name) &&
|
||||
(c.constraint_name.includes('_key') || c.constraint_name.includes('_index'))
|
||||
);
|
||||
|
||||
if (numericalConstraints.length > 0) {
|
||||
console.log(`🗑️ Entferne ${numericalConstraints.length} Constraints mit numerischen Suffixen`);
|
||||
|
||||
for (const constraint of numericalConstraints) {
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE community."user"
|
||||
DROP CONSTRAINT IF EXISTS "${constraint.constraint_name}"
|
||||
`);
|
||||
console.log(` ✅ Entfernt: ${constraint.constraint_name}`);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ Konnte nicht entfernen: ${constraint.constraint_name} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(`⚠️ Konnte community.user Constraints nicht bereinigen: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt alle Constraints mit numerischen Suffixen aus allen Tabellen
|
||||
*/
|
||||
async function removeNumericalSuffixConstraints() {
|
||||
try {
|
||||
console.log('🔍 Entferne alle Constraints mit numerischen Suffixen...');
|
||||
|
||||
// Finde alle Constraints mit numerischen Suffixen
|
||||
const numericalConstraints = await sequelize.query(`
|
||||
SELECT
|
||||
tc.table_schema,
|
||||
tc.table_name,
|
||||
tc.constraint_name,
|
||||
tc.constraint_type
|
||||
FROM information_schema.table_constraints tc
|
||||
WHERE tc.table_schema IN ('match3', 'community', 'falukant_data', 'falukant_type', 'falukant_predefine', 'falukant_log', 'chat', 'forum', 'logs', 'type', 'service')
|
||||
AND tc.constraint_name ~ '.*\\d+$'
|
||||
AND (tc.constraint_name LIKE '%_key%' OR tc.constraint_name LIKE '%_index%')
|
||||
ORDER BY tc.table_schema, tc.table_name, tc.constraint_name;
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
console.log(`📊 Gefunden: ${numericalConstraints.length} Constraints mit numerischen Suffixen`);
|
||||
|
||||
if (numericalConstraints.length > 0) {
|
||||
for (const constraint of numericalConstraints) {
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE ${constraint.table_schema}.${constraint.table_name}
|
||||
DROP CONSTRAINT IF EXISTS "${constraint.constraint_name}"
|
||||
`);
|
||||
console.log(` ✅ Entfernt: ${constraint.table_schema}.${constraint.table_name}.${constraint.constraint_name}`);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ Konnte nicht entfernen: ${constraint.constraint_name} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(`⚠️ Konnte numerische Suffix-Constraints nicht entfernen: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Führe das Skript aus, wenn es direkt aufgerufen wird
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
cleanupDatabaseConstraints()
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
const algorithm = 'aes-256-ecb'; // Verwende ECB-Modus, der keinen IV benötigt
|
||||
const key = crypto.scryptSync(process.env.SECRET_KEY, 'salt', 32); // Der Schlüssel sollte eine Länge von 32 Bytes haben
|
||||
const secret = process.env.SECRET_KEY;
|
||||
if (!secret) {
|
||||
console.warn('[crypto] SECRET_KEY fehlt – verwende unsicheren Fallback (nur Entwicklung).');
|
||||
}
|
||||
const key = crypto.scryptSync(secret || 'DEV_FALLBACK_SECRET', 'salt', 32); // Der Schlüssel sollte eine Länge von 32 Bytes haben
|
||||
|
||||
export const encrypt = (text) => {
|
||||
const cipher = crypto.createCipheriv(algorithm, key, null); // Kein IV verwendet
|
||||
|
||||
@@ -2,7 +2,11 @@ import crypto from 'crypto';
|
||||
|
||||
const algorithm = 'aes-256-ecb';
|
||||
|
||||
const key = crypto.scryptSync(process.env.SECRET_KEY, 'salt', 32);
|
||||
const secret = process.env.SECRET_KEY;
|
||||
if (!secret) {
|
||||
console.warn('[encryption] SECRET_KEY fehlt – verwende unsicheren Fallback (nur für Entwicklung).');
|
||||
}
|
||||
const key = crypto.scryptSync(secret || 'DEV_FALLBACK_SECRET', 'salt', 32);
|
||||
|
||||
export const generateIv = () => {
|
||||
return crypto.randomBytes(16).toString('base64');
|
||||
|
||||
@@ -6,6 +6,7 @@ import CharacterTrait from "../../models/falukant/type/character_trait.js";
|
||||
import PromotionalGift from "../../models/falukant/type/promotional_gift.js";
|
||||
import PromotionalGiftCharacterTrait from "../../models/falukant/predefine/promotional_gift_character_trait.js";
|
||||
import PromotionalGiftMood from "../../models/falukant/predefine/promotional_gift_mood.js";
|
||||
import { sequelize } from '../sequelize.js';
|
||||
import HouseType from '../../models/falukant/type/house.js';
|
||||
import TitleOfNobility from "../../models/falukant/type/title_of_nobility.js";
|
||||
import PartyType from "../../models/falukant/type/party.js";
|
||||
@@ -16,6 +17,9 @@ import PoliticalOfficeType from "../../models/falukant/type/political_office_typ
|
||||
import PoliticalOfficePrerequisite from "../../models/falukant/predefine/political_office_prerequisite.js";
|
||||
import UndergroundType from "../../models/falukant/type/underground.js";
|
||||
|
||||
// Debug-Flag: Nur wenn DEBUG_FALUKANT=1 gesetzt ist, werden ausführliche Logs ausgegeben.
|
||||
const falukantDebug = process.env.DEBUG_FALUKANT === '1';
|
||||
|
||||
export const initializeFalukantTypes = async () => {
|
||||
await initializeFalukantTypeRegions();
|
||||
await initializeFalukantRelationships();
|
||||
@@ -631,7 +635,7 @@ export const initializeFalukantRegions = async () => {
|
||||
console.error(`Parent region not found for: ${region.parentTr}`);
|
||||
continue;
|
||||
}
|
||||
console.log('Creating/Fetching Region:', region.labelTr, 'Type:', regionType.labelTr, 'Parent:', parentRegion?.name);
|
||||
if (falukantDebug) console.log('[Falukant] Region', region.labelTr, 'type', regionType.labelTr, 'parent', parentRegion?.name);
|
||||
await RegionData.findOrCreate({
|
||||
where: { name: region.labelTr },
|
||||
defaults: {
|
||||
@@ -682,44 +686,102 @@ export const initializeFalukantPromotionalGifts = async () => {
|
||||
};
|
||||
|
||||
export const initializePromotionalGiftTraitLinks = async () => {
|
||||
const giftRows = await PromotionalGift.findAll({ attributes: ['id','name'], raw: true });
|
||||
const traitRows = await CharacterTrait.findAll({ attributes: ['id','tr'], raw: true });
|
||||
const giftMap = new Map(giftRows.map(g=>[g.name,g.id]));
|
||||
const traitMap = new Map(traitRows.map(t=>[t.tr,t.id]));
|
||||
let created = 0, updated = 0, skipped = 0;
|
||||
for (const link of promotionalGiftTraitLinks) {
|
||||
const gift = await PromotionalGift.findOne({ where: { name: link.gift } });
|
||||
const trait = await CharacterTrait.findOne({ where: { tr: link.trait } });
|
||||
if (!gift || !trait) {
|
||||
console.error(`Gift or Trait not found for: ${link.gift}, ${link.trait}`);
|
||||
continue;
|
||||
const giftId = giftMap.get(link.gift);
|
||||
const traitId = traitMap.get(link.trait);
|
||||
if (giftId == null || traitId == null) { skipped++; continue; }
|
||||
try {
|
||||
const [record, wasCreated] = await PromotionalGiftCharacterTrait.findOrCreate({
|
||||
where: { giftId, traitId },
|
||||
defaults: { giftId, traitId, suitability: link.suitability }
|
||||
});
|
||||
if (wasCreated) { created++; }
|
||||
else if (record.suitability !== link.suitability) { record.suitability = link.suitability; await record.save(); updated++; }
|
||||
} catch (e) {
|
||||
if (falukantDebug) console.error('[Falukant] TraitLink Fehler (Model)', { giftId, traitId, error: e.message });
|
||||
// Fallback RAW Insert/Upsert
|
||||
try {
|
||||
await sequelize.query('INSERT INTO falukant_predefine.promotional_gift_character_trait (gift_id, trait_id, suitability) VALUES (:g,:t,:s) ON CONFLICT (gift_id, trait_id) DO UPDATE SET suitability = EXCLUDED.suitability', {
|
||||
replacements: { g: giftId, t: traitId, s: link.suitability }
|
||||
});
|
||||
created++;
|
||||
} catch (rawErr) {
|
||||
console.error('[Falukant] TraitLink RAW fail', { giftId, traitId, error: rawErr.message });
|
||||
throw rawErr;
|
||||
}
|
||||
}
|
||||
await PromotionalGiftCharacterTrait.findOrCreate({
|
||||
where: {
|
||||
gift_id: gift.id,
|
||||
trait_id: trait.id,
|
||||
},
|
||||
defaults: {
|
||||
suitability: link.suitability,
|
||||
gift_id: gift.id,
|
||||
trait_id: trait.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log(`[Falukant] TraitLinks neu=${created} upd=${updated}${skipped?` skip=${skipped}`:''}`);
|
||||
};
|
||||
|
||||
export const initializePromotionalGiftMoodLinks = async () => {
|
||||
// Diagnose: Zähle vorhandene Gifts & Moods bevor Links erstellt werden
|
||||
try {
|
||||
const totalGifts = await PromotionalGift.count();
|
||||
const totalMoods = await Mood.count();
|
||||
if (falukantDebug) console.log(`[Falukant] MoodLinks start gifts=${totalGifts} moods=${totalMoods} defs=${promotionalGiftMoodLinks.length}`);
|
||||
} catch (e) {
|
||||
if (falukantDebug) console.warn('[Falukant] MoodLinks count fail:', e.message);
|
||||
}
|
||||
// Prefetch Maps um Lookup stabil zu machen und potentielle Race-Conditions auszuschließen
|
||||
const giftRows = await PromotionalGift.findAll({ attributes: ['id', 'name'], raw: true });
|
||||
const moodRows = await Mood.findAll({ attributes: ['id', 'tr'], raw: true });
|
||||
const giftMap = new Map(giftRows.map(g => [g.name, g.id]));
|
||||
const moodMap = new Map(moodRows.map(m => [m.tr, m.id]));
|
||||
// (logging minimiert)
|
||||
|
||||
let nullIdIssues = 0;
|
||||
let createdCount = 0;
|
||||
let updatedCount = 0;
|
||||
const anomalies = [];
|
||||
for (const link of promotionalGiftMoodLinks) {
|
||||
const gift = await PromotionalGift.findOne({ where: { name: link.gift } });
|
||||
const mood = await Mood.findOne({ where: { tr: link.mood } });
|
||||
if (!gift || !mood) {
|
||||
console.error(`Gift or Mood not found for: ${link.gift}, ${link.mood}`);
|
||||
const giftId = giftMap.get(link.gift);
|
||||
const moodId = moodMap.get(link.mood);
|
||||
if (giftId == null || moodId == null) {
|
||||
nullIdIssues++;
|
||||
anomalies.push({ link, giftId, moodId });
|
||||
if (falukantDebug && nullIdIssues <= 10) console.error('[Falukant] MoodLink Lookup miss', { link, giftId, moodId });
|
||||
continue;
|
||||
}
|
||||
|
||||
await PromotionalGiftMood.create({
|
||||
gift_id: gift.id,
|
||||
mood_id: mood.id,
|
||||
suitability: link.suitability,
|
||||
}).catch(err => {
|
||||
if (err.name !== 'SequelizeUniqueConstraintError') throw err;
|
||||
});
|
||||
try {
|
||||
// Verwende Model API mit field-Mapping (giftId -> gift_id) jetzt vorhanden im Model
|
||||
const [record, created] = await PromotionalGiftMood.findOrCreate({
|
||||
where: { giftId, moodId },
|
||||
defaults: { suitability: link.suitability }
|
||||
});
|
||||
if (!created && record.suitability !== link.suitability) {
|
||||
record.suitability = link.suitability;
|
||||
await record.save();
|
||||
updatedCount++;
|
||||
} else if (created) {
|
||||
createdCount++;
|
||||
}
|
||||
// kein per-Link Logging mehr
|
||||
} catch (err) {
|
||||
if (falukantDebug) console.error('[Falukant] MoodLink Fehler (Model)', { link, giftId, moodId, error: err.message });
|
||||
// Fallback: versuche noch einmal per RAW (sollte nicht nötig sein)
|
||||
try {
|
||||
await sequelize.query('INSERT INTO falukant_predefine.promotional_gift_mood (gift_id, mood_id, suitability) VALUES (:g,:m,:s) ON CONFLICT (gift_id, mood_id) DO UPDATE SET suitability = EXCLUDED.suitability', {
|
||||
replacements: { g: giftId, m: moodId, s: link.suitability }
|
||||
});
|
||||
createdCount++;
|
||||
// Fallback erfolgreich (unterdrücktes Detail-Logging)
|
||||
} catch (rawErr) {
|
||||
console.error('[Falukant] MoodLink RAW fail', { giftId, moodId, error: rawErr.message });
|
||||
throw rawErr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (falukantDebug && nullIdIssues > 0) {
|
||||
console.warn(`[Falukant] MoodLinks miss=${nullIdIssues} (bis 10 geloggt)`);
|
||||
if (anomalies.length) console.warn('[Falukant] MoodLinks Beispiele:', anomalies.slice(0, 3));
|
||||
}
|
||||
console.log(`[Falukant] MoodLinks neu=${createdCount} upd=${updatedCount}${nullIdIssues?` miss=${nullIdIssues}`:''}`);
|
||||
};
|
||||
|
||||
export const initializeFalukantHouseTypes = async () => {
|
||||
@@ -801,20 +863,25 @@ export const initializePoliticalOfficeTypes = async () => {
|
||||
};
|
||||
|
||||
export const initializePoliticalOfficePrerequisites = async () => {
|
||||
let created = 0;
|
||||
let existing = 0;
|
||||
let skipped = 0;
|
||||
for (const prereq of politicalOfficePrerequisites) {
|
||||
const office = await PoliticalOfficeType.findOne({
|
||||
where: { name: prereq.officeTr }
|
||||
});
|
||||
if (!office) continue;
|
||||
|
||||
await PoliticalOfficePrerequisite.findOrCreate({
|
||||
where: { office_type_id: office.id },
|
||||
defaults: {
|
||||
office_type_id: office.id,
|
||||
prerequisite: prereq.prerequisite
|
||||
}
|
||||
});
|
||||
const office = await PoliticalOfficeType.findOne({ where: { name: prereq.officeTr } });
|
||||
if (!office) { skipped++; continue; }
|
||||
try {
|
||||
const [record, wasCreated] = await PoliticalOfficePrerequisite.findOrCreate({
|
||||
where: { officeTypeId: office.id },
|
||||
defaults: { officeTypeId: office.id, prerequisite: prereq.prerequisite }
|
||||
});
|
||||
if (wasCreated) created++; else existing++;
|
||||
} catch (e) {
|
||||
if (falukantDebug) console.error('[Falukant] OfficePrereq Fehler', { officeId: office?.id, error: e.message });
|
||||
// Fehler weiterwerfen um Initialisierung nicht still zu verschlucken
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
console.log(`[Falukant] OfficePrereq neu=${created} exist=${existing}${skipped?` skip=${skipped}`:''}`);
|
||||
};
|
||||
|
||||
export const initializeUndergroundTypes = async () => {
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import dotenv from 'dotenv';
|
||||
import { createClient } from 'redis';
|
||||
import User from '../models/community/user.js';
|
||||
|
||||
dotenv.config();
|
||||
// dotenv wird global in server.js/app.js geladen
|
||||
|
||||
const EXPIRATION_TIME = 30 * 60 * 1000;
|
||||
|
||||
const redisHost = process.env.REDIS_HOST || '127.0.0.1';
|
||||
const redisPort = process.env.REDIS_PORT || '6379';
|
||||
if (!process.env.REDIS_HOST || !process.env.REDIS_PORT) {
|
||||
console.warn(`[redis] Verwende Fallback ${redisHost}:${redisPort}`);
|
||||
}
|
||||
const redisClient = createClient({
|
||||
url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
|
||||
url: `redis://${redisHost}:${redisPort}`,
|
||||
password: process.env.REDIS_PASSWORD,
|
||||
legacyMode: false,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Sequelize } from 'sequelize';
|
||||
import { Sequelize, DataTypes } from 'sequelize';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
@@ -8,8 +8,7 @@ const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, proces
|
||||
dialect: 'postgres',
|
||||
define: {
|
||||
timestamps: false,
|
||||
underscored: true, // WICHTIG: Alle Datenbankfelder im snake_case Format
|
||||
freezeTableName: true // Verhindert Pluralisierung der Tabellennamen
|
||||
underscored: true // WICHTIG: Alle Datenbankfelder im snake_case Format
|
||||
},
|
||||
});
|
||||
|
||||
@@ -285,18 +284,23 @@ const findModelForTable = (schemaName, tableName, models) => {
|
||||
|
||||
// Hilfsfunktion: Konvertiert Sequelize-Datentyp zu PostgreSQL-Datentyp
|
||||
const getExpectedDataType = (attribute) => {
|
||||
if (!attribute || !attribute.type) return 'text';
|
||||
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';
|
||||
|
||||
// Direktklassentypen in Sequelize sind Funktionen/Klassen; "instanceof" funktioniert bei Wrappern wie DataTypes.INTEGER() nicht immer.
|
||||
// Deshalb vergleichen wir über den .key wenn verfügbar.
|
||||
const key = type.key || type.constructor?.key;
|
||||
switch (key) {
|
||||
case 'INTEGER': return 'integer';
|
||||
case 'STRING': return 'character varying';
|
||||
case 'TEXT': return 'text';
|
||||
case 'BOOLEAN': return 'boolean';
|
||||
case 'DATE': return 'timestamp without time zone';
|
||||
case 'JSON': return 'json';
|
||||
case 'DECIMAL': return 'numeric';
|
||||
default:
|
||||
return 'text';
|
||||
}
|
||||
};
|
||||
|
||||
// Hilfsfunktion: Konvertiert Sequelize-Default zu PostgreSQL-Default
|
||||
|
||||
Reference in New Issue
Block a user