Enhance FalukantService with character caching and optimized city retrieval: Introduce caching for cities with branch types to reduce database queries, and streamline character retrieval logic. Update product and knowledge fetching to improve performance and maintainability.

This commit is contained in:
Torsten Schulz (local)
2026-01-29 16:04:43 +01:00
parent f1717920b6
commit f7a977df33

View File

@@ -4541,36 +4541,47 @@ class FalukantService extends BaseService {
const priceByProduct = new Map(items.map(i => [i.productId, i.currentPrice])); const priceByProduct = new Map(items.map(i => [i.productId, i.currentPrice]));
const user = await this.getFalukantUserByHashedId(hashedUserId); const user = await this.getFalukantUserByHashedId(hashedUserId);
const character = await FalukantCharacter.findOne({ where: { userId: user.id } }); const character = user.character || await FalukantCharacter.findOne({ where: { userId: user.id }, attributes: ['id'] });
if (!character) { if (!character) {
throw new Error(`No FalukantCharacter found for user with id ${user.id}`); throw new Error(`No FalukantCharacter found for user with id ${user.id}`);
} }
const characterId = character.id;
const [products, knowledges, cities, townWorths] = await Promise.all([ let citiesWithBranchType = FalukantService._citiesBatchCache?.get(user.id);
const now = Date.now();
if (citiesWithBranchType && citiesWithBranchType.expires > now) {
citiesWithBranchType = citiesWithBranchType.data;
} else {
const cityRows = await sequelize.query(
`SELECT r.id, r.name,
MAX(CASE WHEN bt.label_tr IN ('store','fullstack') THEN 2 WHEN bt.label_tr = 'production' THEN 1 ELSE 0 END) AS branch_type_sort
FROM falukant_data.region r
INNER JOIN falukant_type.region rt ON r.region_type_id = rt.id AND rt.label_tr = 'city'
LEFT JOIN falukant_data.branch b ON b.region_id = r.id AND b.falukant_user_id = :userId
LEFT JOIN falukant_type.branch bt ON b.branch_type_id = bt.id
GROUP BY r.id, r.name
ORDER BY r.id`,
{ replacements: { userId: user.id }, type: sequelize.QueryTypes.SELECT }
);
const branchTypeByCityId = new Map();
const cities = [];
for (const row of cityRows) {
cities.push({ id: row.id, name: row.name });
const sort = row.branch_type_sort ?? 0;
branchTypeByCityId.set(row.id, sort === 2 ? 'store' : sort === 1 ? 'production' : null);
}
citiesWithBranchType = { cities, branchTypeByCityId };
if (!FalukantService._citiesBatchCache) FalukantService._citiesBatchCache = new Map();
FalukantService._citiesBatchCache.set(user.id, { data: citiesWithBranchType, expires: now + 60000 });
}
const { cities, branchTypeByCityId } = citiesWithBranchType;
const [products, knowledges, townWorths] = await Promise.all([
ProductType.findAll({ where: { id: { [Op.in]: productIds } }, attributes: ['id', 'sellCost'] }), ProductType.findAll({ where: { id: { [Op.in]: productIds } }, attributes: ['id', 'sellCost'] }),
Knowledge.findAll({ Knowledge.findAll({
where: { characterId: character.id, productId: { [Op.in]: productIds } }, where: { characterId: characterId, productId: { [Op.in]: productIds } },
attributes: ['productId', 'knowledge'] attributes: ['productId', 'knowledge']
}), }),
RegionData.findAll({
attributes: ['id', 'name'],
include: [
{
model: RegionType,
as: 'regionType',
where: { labelTr: 'city' },
attributes: ['labelTr']
},
{
model: Branch,
as: 'branches',
where: { falukantUserId: user.id },
include: [{ model: BranchType, as: 'branchType', attributes: ['labelTr'] }],
attributes: ['branchTypeId'],
required: false
}
]
}),
TownProductWorth.findAll({ TownProductWorth.findAll({
where: { productId: { [Op.in]: productIds } }, where: { productId: { [Op.in]: productIds } },
attributes: ['productId', 'regionId', 'worthPercent'] attributes: ['productId', 'regionId', 'worthPercent']
@@ -4578,12 +4589,7 @@ class FalukantService extends BaseService {
]); ]);
const knowledgeByProduct = new Map(knowledges.map(k => [k.productId, k.knowledge || 0])); const knowledgeByProduct = new Map(knowledges.map(k => [k.productId, k.knowledge || 0]));
const worthByProductRegion = new Map(); const worthByProductRegion = new Map(townWorths.map(tw => [`${tw.productId}-${tw.regionId}`, tw.worthPercent]));
for (const tw of townWorths) {
const key = `${tw.productId}-${tw.regionId}`;
worthByProductRegion.set(key, tw.worthPercent);
}
const productById = new Map(products.map(p => [p.id, p]));
const PRICE_TOLERANCE = 0.01; const PRICE_TOLERANCE = 0.01;
const out = {}; const out = {};
@@ -4604,18 +4610,11 @@ class FalukantService extends BaseService {
const priceInCity = calcRegionalSellPriceSync(product, knowledgeFactor, worthPercent); const priceInCity = calcRegionalSellPriceSync(product, knowledgeFactor, worthPercent);
if (priceInCity == null) continue; if (priceInCity == null) continue;
if (priceInCity <= currentRegionalPrice - PRICE_TOLERANCE) continue; if (priceInCity <= currentRegionalPrice - PRICE_TOLERANCE) continue;
let branchType = null;
if (city.branches && city.branches.length > 0) {
const branchTypes = city.branches.map(b => b.branchType?.labelTr).filter(Boolean);
if (branchTypes.includes('store') || branchTypes.includes('fullstack')) branchType = 'store';
else if (branchTypes.includes('production')) branchType = 'production';
}
results.push({ results.push({
regionId: city.id, regionId: city.id,
regionName: city.name, regionName: city.name,
price: priceInCity, price: priceInCity,
branchType branchType: branchTypeByCityId.get(city.id) ?? null
}); });
} }
results.sort((a, b) => b.price - a.price); results.sort((a, b) => b.price - a.price);