Enhance getFamily method in FalukantService for performance and data retrieval
- Implemented detailed timing metrics to track performance across various steps of the getFamily method. - Refactored data retrieval to load relationships and children in parallel, improving efficiency. - Enhanced mapping of relationship and child data, including additional attributes for characters. - Introduced batch loading for related data to minimize database queries and optimize performance. - Improved error handling and logging for better traceability during execution.
This commit is contained in:
@@ -2880,115 +2880,210 @@ class FalukantService extends BaseService {
|
||||
}
|
||||
|
||||
async getFamily(hashedUserId) {
|
||||
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||
if (!user) throw new Error('User not found');
|
||||
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
||||
if (!character) throw new Error('Character not found for this user');
|
||||
let relationships = await Relationship.findAll({
|
||||
where: { character1Id: character.id },
|
||||
attributes: ['createdAt', 'widowFirstName2', 'nextStepProgress'],
|
||||
include: [
|
||||
{
|
||||
model: FalukantCharacter, as: 'character2',
|
||||
attributes: ['id', 'birthdate', 'gender', 'moodId'],
|
||||
const startTime = Date.now();
|
||||
const timings = {};
|
||||
|
||||
try {
|
||||
// 1. User und Character laden
|
||||
const step1Start = Date.now();
|
||||
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||
if (!user) throw new Error('User not found');
|
||||
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
||||
if (!character) throw new Error('Character not found for this user');
|
||||
timings.step1_user_character = Date.now() - step1Start;
|
||||
|
||||
// 2. Relationships und Children parallel laden
|
||||
const step2Start = Date.now();
|
||||
const [relationshipsRaw, charsWithChildren] = await Promise.all([
|
||||
Relationship.findAll({
|
||||
where: { character1Id: character.id },
|
||||
attributes: ['createdAt', 'widowFirstName2', 'nextStepProgress', 'relationshipTypeId'],
|
||||
include: [
|
||||
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
||||
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] },
|
||||
{ model: CharacterTrait, as: 'traits' },
|
||||
{ model: Mood, as: 'mood' },
|
||||
{
|
||||
model: FalukantCharacter, as: 'character2',
|
||||
attributes: ['id', 'birthdate', 'gender', 'moodId', 'firstName', 'lastName', 'titleOfNobility'],
|
||||
required: false
|
||||
},
|
||||
{ model: RelationshipType, as: 'relationshipType', attributes: ['tr'], required: false }
|
||||
]
|
||||
},
|
||||
{ model: RelationshipType, as: 'relationshipType', attributes: ['tr'] }
|
||||
]
|
||||
});
|
||||
relationships = relationships.map(r => ({
|
||||
createdAt: r.createdAt,
|
||||
widowFirstName2: r.widowFirstName2,
|
||||
progress: r.nextStepProgress,
|
||||
character2: {
|
||||
id: r.character2.id,
|
||||
age: calcAge(r.character2.birthdate),
|
||||
gender: r.character2.gender,
|
||||
firstName: r.character2.definedFirstName?.name || 'Unknown',
|
||||
nobleTitle: r.character2.nobleTitle?.labelTr || '',
|
||||
mood: r.character2.mood,
|
||||
traits: r.character2.traits
|
||||
},
|
||||
relationshipType: r.relationshipType.tr
|
||||
}));
|
||||
const charsWithChildren = await FalukantCharacter.findAll({
|
||||
where: { userId: user.id },
|
||||
include: [
|
||||
{
|
||||
model: ChildRelation,
|
||||
as: 'childrenFather',
|
||||
include: [{
|
||||
model: FalukantCharacter,
|
||||
as: 'child',
|
||||
include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
|
||||
}]
|
||||
},
|
||||
{
|
||||
model: ChildRelation,
|
||||
as: 'childrenMother',
|
||||
include: [{
|
||||
model: FalukantCharacter,
|
||||
as: 'child',
|
||||
include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
|
||||
}]
|
||||
}),
|
||||
FalukantCharacter.findAll({
|
||||
where: { userId: user.id },
|
||||
attributes: ['id'],
|
||||
include: [
|
||||
{
|
||||
model: ChildRelation,
|
||||
as: 'childrenFather',
|
||||
attributes: ['nameSet', 'isHeir', 'createdAt', 'childCharacterId'],
|
||||
include: [{
|
||||
model: FalukantCharacter,
|
||||
as: 'child',
|
||||
attributes: ['id', 'birthdate', 'gender', 'firstName'],
|
||||
required: false
|
||||
}],
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: ChildRelation,
|
||||
as: 'childrenMother',
|
||||
attributes: ['nameSet', 'isHeir', 'createdAt', 'childCharacterId'],
|
||||
include: [{
|
||||
model: FalukantCharacter,
|
||||
as: 'child',
|
||||
attributes: ['id', 'birthdate', 'gender', 'firstName'],
|
||||
required: false
|
||||
}],
|
||||
required: false
|
||||
}
|
||||
]
|
||||
})
|
||||
]);
|
||||
timings.step2_relationships_children = Date.now() - step2Start;
|
||||
|
||||
// 3. Batch-Loading für Relationship-Character-Daten
|
||||
const step3Start = Date.now();
|
||||
const relationshipCharacters = relationshipsRaw
|
||||
.filter(r => r.character2)
|
||||
.map(r => r.character2);
|
||||
const relationshipCharacterIds = relationshipCharacters.map(c => c.id);
|
||||
const childCharacters = charsWithChildren
|
||||
.flatMap(c => [
|
||||
...(c.childrenFather || []).map(r => r.child).filter(Boolean),
|
||||
...(c.childrenMother || []).map(r => r.child).filter(Boolean)
|
||||
]);
|
||||
const childCharacterIds = childCharacters.map(c => c.id);
|
||||
|
||||
// Sammle alle benötigten IDs
|
||||
const relationshipFirstNameIds = [...new Set(relationshipCharacters.map(c => c.firstName).filter(Boolean))];
|
||||
const relationshipLastNameIds = [...new Set(relationshipCharacters.map(c => c.lastName).filter(Boolean))];
|
||||
const relationshipTitleIds = [...new Set(relationshipCharacters.map(c => c.titleOfNobility).filter(Boolean))];
|
||||
const relationshipMoodIds = [...new Set(relationshipCharacters.map(c => c.moodId).filter(Boolean))];
|
||||
const childFirstNameIds = [...new Set(childCharacters.map(c => c.firstName).filter(Boolean))];
|
||||
const allFirstNameIds = [...new Set([...relationshipFirstNameIds, ...childFirstNameIds])];
|
||||
|
||||
// Batch-Load alle benötigten Daten parallel
|
||||
const [firstNames, lastNames, titles, traits, moods] = await Promise.all([
|
||||
allFirstNameIds.length > 0 ? FalukantPredefineFirstname.findAll({
|
||||
where: { id: { [Op.in]: allFirstNameIds } },
|
||||
attributes: ['id', 'name']
|
||||
}) : [],
|
||||
relationshipLastNameIds.length > 0 ? FalukantPredefineLastname.findAll({
|
||||
where: { id: { [Op.in]: relationshipLastNameIds } },
|
||||
attributes: ['id', 'name']
|
||||
}) : [],
|
||||
relationshipTitleIds.length > 0 ? TitleOfNobility.findAll({
|
||||
where: { id: { [Op.in]: relationshipTitleIds } },
|
||||
attributes: ['id', 'labelTr']
|
||||
}) : [],
|
||||
relationshipCharacterIds.length > 0 ? CharacterTrait.findAll({
|
||||
where: { characterId: { [Op.in]: relationshipCharacterIds } },
|
||||
attributes: ['characterId', 'traitId']
|
||||
}) : [],
|
||||
relationshipMoodIds.length > 0 ? Mood.findAll({
|
||||
where: { id: { [Op.in]: relationshipMoodIds } },
|
||||
attributes: ['id', 'labelTr']
|
||||
}) : []
|
||||
]);
|
||||
|
||||
// Erstelle Maps für schnellen Zugriff
|
||||
const firstNameMap = new Map([...firstNames, ...childFirstNames].map(fn => [fn.id, fn.name]));
|
||||
const lastNameMap = new Map(lastNames.map(ln => [ln.id, ln.name]));
|
||||
const titleMap = new Map(titles.map(t => [t.id, t.labelTr]));
|
||||
const moodMap = new Map(moods.map(m => [m.id, m.labelTr]));
|
||||
const traitsMap = new Map();
|
||||
traits.forEach(t => {
|
||||
if (!traitsMap.has(t.characterId)) {
|
||||
traitsMap.set(t.characterId, []);
|
||||
}
|
||||
traitsMap.get(t.characterId).push(t.traitId);
|
||||
});
|
||||
timings.step3_batch_loading = Date.now() - step3Start;
|
||||
|
||||
// 4. Relationships mappen
|
||||
const step4Start = Date.now();
|
||||
const relationships = relationshipsRaw.map(r => {
|
||||
const char2 = r.character2;
|
||||
return {
|
||||
createdAt: r.createdAt,
|
||||
widowFirstName2: r.widowFirstName2,
|
||||
progress: r.nextStepProgress,
|
||||
character2: char2 ? {
|
||||
id: char2.id,
|
||||
age: calcAge(char2.birthdate),
|
||||
gender: char2.gender,
|
||||
firstName: firstNameMap.get(char2.firstName) || 'Unknown',
|
||||
nobleTitle: titleMap.get(char2.titleOfNobility) || '',
|
||||
mood: moodMap.get(char2.moodId) || null,
|
||||
traits: traitsMap.get(char2.id) || []
|
||||
} : null,
|
||||
relationshipType: r.relationshipType?.tr || ''
|
||||
};
|
||||
}).filter(r => r.character2 !== null);
|
||||
timings.step4_map_relationships = Date.now() - step4Start;
|
||||
|
||||
// 5. Children mappen
|
||||
const step5Start = Date.now();
|
||||
const children = [];
|
||||
for (const parentChar of charsWithChildren) {
|
||||
const allRels = [
|
||||
...(parentChar.childrenFather || []),
|
||||
...(parentChar.childrenMother || [])
|
||||
];
|
||||
for (const rel of allRels) {
|
||||
const kid = rel.child;
|
||||
if (!kid) continue;
|
||||
children.push({
|
||||
childCharacterId: kid.id,
|
||||
name: firstNameMap.get(kid.firstName) || 'Unknown',
|
||||
gender: kid.gender,
|
||||
age: calcAge(kid.birthdate),
|
||||
hasName: rel.nameSet,
|
||||
isHeir: rel.isHeir || false,
|
||||
_createdAt: rel.createdAt,
|
||||
});
|
||||
}
|
||||
]
|
||||
});
|
||||
const children = [];
|
||||
for (const parentChar of charsWithChildren) {
|
||||
const allRels = [
|
||||
...(parentChar.childrenFather || []),
|
||||
...(parentChar.childrenMother || [])
|
||||
];
|
||||
for (const rel of allRels) {
|
||||
const kid = rel.child;
|
||||
children.push({
|
||||
childCharacterId: kid.id,
|
||||
name: kid.definedFirstName?.name || 'Unknown',
|
||||
gender: kid.gender,
|
||||
age: calcAge(kid.birthdate),
|
||||
hasName: rel.nameSet,
|
||||
isHeir: rel.isHeir || false,
|
||||
_createdAt: rel.createdAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Sort children globally by relation createdAt ascending (older first)
|
||||
children.sort((a, b) => new Date(a._createdAt) - new Date(b._createdAt));
|
||||
const inProgress = ['wooing', 'engaged', 'married'];
|
||||
const family = {
|
||||
relationships: relationships.filter(r => inProgress.includes(r.relationshipType)),
|
||||
lovers: relationships.filter(r => r.relationshipType === 'lover'),
|
||||
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
|
||||
children: children.map(({ _createdAt, ...rest }) => rest),
|
||||
possiblePartners: []
|
||||
};
|
||||
const ownAge = calcAge(character.birthdate);
|
||||
console.log(`[getFamily] Character age: ${ownAge}, relationships: ${family.relationships.length}`);
|
||||
if (ownAge >= 12 && family.relationships.length === 0) {
|
||||
family.possiblePartners = await this.getPossiblePartners(character.id);
|
||||
console.log(`[getFamily] Found ${family.possiblePartners.length} existing proposals`);
|
||||
if (family.possiblePartners.length === 0) {
|
||||
console.log(`[getFamily] Creating new possible partners...`);
|
||||
await this.createPossiblePartners(
|
||||
character.id,
|
||||
character.gender,
|
||||
character.regionId,
|
||||
character.titleOfNobility,
|
||||
ownAge
|
||||
);
|
||||
children.sort((a, b) => new Date(a._createdAt) - new Date(b._createdAt));
|
||||
timings.step5_map_children = Date.now() - step5Start;
|
||||
|
||||
// 6. Family-Objekt erstellen
|
||||
const step6Start = Date.now();
|
||||
const inProgress = ['wooing', 'engaged', 'married'];
|
||||
const family = {
|
||||
relationships: relationships.filter(r => inProgress.includes(r.relationshipType)),
|
||||
lovers: relationships.filter(r => r.relationshipType === 'lover'),
|
||||
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
|
||||
children: children.map(({ _createdAt, ...rest }) => rest),
|
||||
possiblePartners: []
|
||||
};
|
||||
timings.step6_create_family = Date.now() - step6Start;
|
||||
|
||||
// 7. Possible Partners (nur wenn nötig, asynchron)
|
||||
const step7Start = Date.now();
|
||||
const ownAge = calcAge(character.birthdate);
|
||||
if (ownAge >= 12 && family.relationships.length === 0) {
|
||||
family.possiblePartners = await this.getPossiblePartners(character.id);
|
||||
console.log(`[getFamily] After creation: ${family.possiblePartners.length} proposals`);
|
||||
if (family.possiblePartners.length === 0) {
|
||||
// Asynchron erstellen, nicht blockieren
|
||||
this.createPossiblePartners(
|
||||
character.id,
|
||||
character.gender,
|
||||
character.regionId,
|
||||
character.titleOfNobility,
|
||||
ownAge
|
||||
).catch(err => console.error('[getFamily] Error creating partners (async):', err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`[getFamily] Skipping partner search: age=${ownAge}, relationships=${family.relationships.length}`);
|
||||
timings.step7_possible_partners = Date.now() - step7Start;
|
||||
|
||||
const totalTime = Date.now() - startTime;
|
||||
console.log(`[getFamily] Performance: ${totalTime}ms total`, timings);
|
||||
|
||||
return family;
|
||||
} catch (error) {
|
||||
console.error('[getFamily] Error:', error);
|
||||
throw error;
|
||||
}
|
||||
return family;
|
||||
}
|
||||
|
||||
async setHeir(hashedUserId, childCharacterId) {
|
||||
|
||||
Reference in New Issue
Block a user