Enhance getGifts method in FalukantService with detailed performance metrics and optimized data retrieval

- Implemented timing metrics to track performance across various steps of the getGifts method, improving traceability.
- Refactored data loading to optimize user, character, and relationship queries, ensuring only necessary fields are fetched.
- Enhanced error handling and logging for better debugging and performance insights during execution.
This commit is contained in:
Torsten Schulz (local)
2026-01-12 11:49:49 +01:00
parent d2ac2bfdd8
commit ec113058d0

View File

@@ -3566,115 +3566,140 @@ class FalukantService extends BaseService {
}
async getGifts(hashedUserId) {
// 1) User & Character optimiert laden (nur benötigte Felder)
const user = await FalukantUser.findOne({
include: [
{ model: User, as: 'user', attributes: ['hashedId'], where: { hashedId: hashedUserId } },
{
model: FalukantCharacter,
as: 'character',
attributes: ['id', 'titleOfNobility'],
required: true
}
]
});
if (!user) throw new Error('User not found');
const myChar = user.character;
if (!myChar) throw new Error('Character not found');
const startTime = Date.now();
const timings = {};
// 2) Beziehung finden und „anderen" Character bestimmen (optimiert: nur benötigte Felder)
const rel = await Relationship.findOne({
where: {
[Op.or]: [
{ character1Id: myChar.id },
{ character2Id: myChar.id }
try {
// 1) User & Character optimiert laden (nur benötigte Felder)
const step1Start = Date.now();
const user = await FalukantUser.findOne({
include: [
{ model: User, as: 'user', attributes: ['hashedId'], where: { hashedId: hashedUserId } },
{
model: FalukantCharacter,
as: 'character',
attributes: ['id', 'titleOfNobility'],
required: true
}
]
},
include: [
{
model: FalukantCharacter,
as: 'character1',
attributes: ['id', 'moodId'],
include: [{
model: CharacterTrait,
as: 'traits',
attributes: ['id'],
through: { attributes: [] },
});
if (!user) throw new Error('User not found');
const myChar = user.character;
if (!myChar) throw new Error('Character not found');
timings.step1_user_character = Date.now() - step1Start;
// 2) Beziehung finden und „anderen" Character bestimmen (optimiert: nur benötigte Felder)
const step2Start = Date.now();
const rel = await Relationship.findOne({
where: {
[Op.or]: [
{ character1Id: myChar.id },
{ character2Id: myChar.id }
]
},
include: [
{
model: FalukantCharacter,
as: 'character1',
attributes: ['id', 'moodId'],
include: [{
model: CharacterTrait,
as: 'traits',
attributes: ['id'],
through: { attributes: [] },
required: false
}],
required: false
}],
},
{
model: FalukantCharacter,
as: 'character2',
attributes: ['id', 'moodId'],
include: [{
model: CharacterTrait,
as: 'traits',
attributes: ['id'],
through: { attributes: [] },
required: false
}],
required: false
}
]
});
timings.step2_relationship = Date.now() - step2Start;
// 3) Wenn keine Beziehung gefunden, alle Gifts ohne Filter zurückgeben
const step3Start = Date.now();
let relatedTraitIds = [];
let relatedMoodId = null;
if (rel) {
const relatedChar = rel.character1.id === myChar.id ? rel.character2 : rel.character1;
// Trait-IDs und Mood des relatedChar
relatedTraitIds = relatedChar.traits ? relatedChar.traits.map(t => t.id) : [];
relatedMoodId = relatedChar.moodId;
}
timings.step3_process_relationship = Date.now() - step3Start;
// 4) Gifts laden mit Mood/Trait-Filter nur wenn Beziehung existiert
const step4Start = Date.now();
const giftIncludes = [
{
model: PromotionalGiftMood,
as: 'promotionalgiftmoods',
attributes: ['mood_id', 'suitability'],
required: false
},
{
model: FalukantCharacter,
as: 'character2',
attributes: ['id', 'moodId'],
include: [{
model: CharacterTrait,
as: 'traits',
attributes: ['id'],
through: { attributes: [] },
required: false
}],
model: PromotionalGiftCharacterTrait,
as: 'characterTraits',
attributes: ['trait_id', 'suitability'],
required: false
}
]
});
];
// 3) Wenn keine Beziehung gefunden, alle Gifts ohne Filter zurückgeben
let relatedTraitIds = [];
let relatedMoodId = null;
if (rel) {
const relatedChar = rel.character1.id === myChar.id ? rel.character2 : rel.character1;
// Trait-IDs und Mood des relatedChar
relatedTraitIds = relatedChar.traits ? relatedChar.traits.map(t => t.id) : [];
relatedMoodId = relatedChar.moodId;
}
// 4) Gifts laden mit Mood/Trait-Filter nur wenn Beziehung existiert
const giftIncludes = [
{
model: PromotionalGiftMood,
as: 'promotionalgiftmoods',
attributes: ['mood_id', 'suitability'],
required: false
},
{
model: PromotionalGiftCharacterTrait,
as: 'characterTraits',
attributes: ['trait_id', 'suitability'],
required: false
// Wenn Beziehung existiert, Filter anwenden
if (rel && relatedMoodId) {
giftIncludes[0].where = { mood_id: relatedMoodId };
}
];
if (rel && relatedTraitIds.length > 0) {
giftIncludes[1].where = { trait_id: { [Op.in]: relatedTraitIds } };
}
timings.step4_prepare_gift_includes = Date.now() - step4Start;
// Wenn Beziehung existiert, Filter anwenden
if (rel && relatedMoodId) {
giftIncludes[0].where = { mood_id: relatedMoodId };
// 5) Parallel: Gifts und lowestTitleOfNobility laden
const step5Start = Date.now();
const [gifts, lowestTitleOfNobility] = await Promise.all([
PromotionalGift.findAll({
include: giftIncludes
}),
TitleOfNobility.findOne({ order: [['id', 'ASC']] })
]);
timings.step5_load_gifts_and_title = Date.now() - step5Start;
// 6) Kosten berechnen (getGiftCost ist synchron, kein await nötig)
const step6Start = Date.now();
const result = gifts.map(gift => ({
id: gift.id,
name: gift.name,
cost: this.getGiftCost(
gift.value,
myChar.titleOfNobility,
lowestTitleOfNobility.id
),
moodsAffects: gift.promotionalgiftmoods, // nur Einträge mit relatedMoodId
charactersAffects: gift.characterTraits // nur Einträge mit relatedTraitIds
}));
timings.step6_calculate_costs = Date.now() - step6Start;
const totalTime = Date.now() - startTime;
console.log(`[getGifts] Performance: ${totalTime}ms total`, timings);
return result;
} catch (error) {
console.error('[getGifts] Error:', error);
throw error;
}
if (rel && relatedTraitIds.length > 0) {
giftIncludes[1].where = { trait_id: { [Op.in]: relatedTraitIds } };
}
// 5) Parallel: Gifts und lowestTitleOfNobility laden
const [gifts, lowestTitleOfNobility] = await Promise.all([
PromotionalGift.findAll({
include: giftIncludes
}),
TitleOfNobility.findOne({ order: [['id', 'ASC']] })
]);
// 6) Kosten berechnen (getGiftCost ist synchron, kein await nötig)
return gifts.map(gift => ({
id: gift.id,
name: gift.name,
cost: this.getGiftCost(
gift.value,
myChar.titleOfNobility,
lowestTitleOfNobility.id
),
moodsAffects: gift.promotionalgiftmoods, // nur Einträge mit relatedMoodId
charactersAffects: gift.characterTraits // nur Einträge mit relatedTraitIds
}));
}
async getChildren(hashedUserId) {