diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 0e7dcd3..d6e1619 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -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'); - - // 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 } + const startTime = Date.now(); + const timings = {}; + + 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) {