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:
Torsten Schulz (local)
2025-08-23 22:27:19 +02:00
parent 66818cc728
commit 6da849ca3c
128 changed files with 1054 additions and 1611 deletions

View File

@@ -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 () => {