feat(falukant): enhance notification handling and localization updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s

- Updated the `enrichNotificationsWithCharacterNames` method in FalukantService to include region name enrichment and handle additional character IDs.
- Introduced a new `serializeNotificationForClient` function to format notifications for the client, ensuring all relevant data is included.
- Enhanced the MessagesDialog component to merge notification payloads and extract parameters more effectively, improving the clarity of displayed messages.
- Added new localization entries for director resignation risk and regional festival effects in multiple languages, ensuring comprehensive user notifications.
This commit is contained in:
Torsten Schulz (local)
2026-04-14 08:06:56 +02:00
parent 9deda3147e
commit 26daf5fed5
7 changed files with 230 additions and 43 deletions

View File

@@ -7328,7 +7328,7 @@ ORDER BY r.id`,
// Enrich notifications: parse JSON payloads and resolve character names
await enrichNotificationsWithCharacterNames(notifications);
return notifications;
return notifications.map(serializeNotificationForClient);
}
async getAllNotifications(hashedUserId, page = 1, size = 10) {
@@ -7344,7 +7344,12 @@ ORDER BY r.id`,
await enrichNotificationsWithCharacterNames(rows);
return { items: rows, total: count, page: Number(page) || 1, size: limit };
return {
items: rows.map(serializeNotificationForClient),
total: count,
page: Number(page) || 1,
size: limit,
};
}
async markNotificationsShown(hashedUserId) {
@@ -8536,6 +8541,14 @@ ORDER BY r.id`,
export default new FalukantService();
/** Stellt sicher, dass Anreicherungen (z. B. region_name ohne DB-Spalte) im API-JSON landen. */
function serializeNotificationForClient(row) {
const j = typeof row.toJSON === 'function' ? row.toJSON() : { ...row };
const dv = row.dataValues || {};
if (dv.region_name != null && j.region_name == null) j.region_name = dv.region_name;
return j;
}
// Helper: parse notifications for character references and attach characterName
async function enrichNotificationsWithCharacterNames(notifications) {
if (!Array.isArray(notifications) || notifications.length === 0) return;
@@ -8552,7 +8565,7 @@ async function enrichNotificationsWithCharacterNames(notifications) {
if (typeof obj !== 'object') return;
for (const [k, v] of Object.entries(obj)) {
if (!v) continue;
if (k === 'character_id' || k === 'characterId') {
if (k === 'character_id' || k === 'characterId' || k === 'director_character_id') {
charIds.add(Number(v));
continue;
}
@@ -8567,15 +8580,25 @@ async function enrichNotificationsWithCharacterNames(notifications) {
}
}
// First pass: collect all referenced character ids from notifications
const regionIds = new Set();
// First pass: collect all referenced character ids and region ids from notifications
for (const n of notifications) {
// parse n.tr if it's JSON
let parsed = null;
try {
if (typeof n.tr === 'string' && n.tr.trim().startsWith('{')) {
const parsed = JSON.parse(n.tr);
parsed = JSON.parse(n.tr);
collectIds(parsed);
} else if (n.tr && typeof n.tr === 'object') {
parsed = n.tr;
collectIds(parsed);
}
} catch (err) { /* ignore */ }
} catch (err) {
parsed = null;
}
if (parsed?.region_id != null) {
regionIds.add(Number(parsed.region_id));
}
// parse n.effects if present
try {
@@ -8591,24 +8614,34 @@ async function enrichNotificationsWithCharacterNames(notifications) {
}
const ids = Array.from(charIds).filter(Boolean);
if (!ids.length) return;
// Batch load characters and their display names
const characters = await FalukantCharacter.findAll({
where: { id: { [Op.in]: ids } },
include: [
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] }
],
attributes: ['id']
});
const regionIdList = Array.from(regionIds).filter((id) => Number.isFinite(id));
if (!ids.length && !regionIdList.length) return;
const nameMap = new Map();
for (const c of characters) {
const first = c.definedFirstName?.name || '';
const last = c.definedLastName?.name || '';
const display = `${first} ${last}`.trim() || null;
nameMap.set(Number(c.id), display || `#${c.id}`);
if (ids.length) {
const characters = await FalukantCharacter.findAll({
where: { id: { [Op.in]: ids } },
include: [
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] }
],
attributes: ['id']
});
for (const c of characters) {
const first = c.definedFirstName?.name || '';
const last = c.definedLastName?.name || '';
const display = `${first} ${last}`.trim() || null;
nameMap.set(Number(c.id), display || `#${c.id}`);
}
}
let regionNameById = new Map();
if (regionIdList.length) {
const regions = await RegionData.findAll({
where: { id: { [Op.in]: regionIdList } },
attributes: ['id', 'name'],
});
regionNameById = new Map(regions.map((r) => [Number(r.id), r.name]));
}
// helper to find first character id in an object
@@ -8625,6 +8658,7 @@ async function enrichNotificationsWithCharacterNames(notifications) {
for (const [k, v] of Object.entries(obj)) {
if (!v) continue;
if (k === 'character_id' || k === 'characterId') return Number(v);
if (k === 'director_character_id') return Number(v);
if (k === 'character' && typeof v === 'object') {
if (v.id) return Number(v.id);
const r = findFirstId(v);
@@ -8638,10 +8672,23 @@ async function enrichNotificationsWithCharacterNames(notifications) {
// Attach resolved name to notifications (set character_name; characterName is a getter that reads from it)
for (const n of notifications) {
let foundId = null;
let parsed = null;
try {
if (typeof n.tr === 'string' && n.tr.trim().startsWith('{')) {
const parsed = JSON.parse(n.tr);
parsed = JSON.parse(n.tr);
} else if (n.tr && typeof n.tr === 'object') {
parsed = n.tr;
}
} catch (err) {
parsed = null;
}
let foundId = null;
if (parsed?.director_character_id != null) {
foundId = Number(parsed.director_character_id);
}
try {
if (!foundId && parsed) {
foundId = findFirstId(parsed) || foundId;
}
} catch (err) { /* ignore */ }
@@ -8661,5 +8708,11 @@ async function enrichNotificationsWithCharacterNames(notifications) {
// Set character_name directly (characterName is a getter that reads from character_name)
n.character_name = resolved;
}
if (parsed?.region_id != null && regionNameById.has(Number(parsed.region_id))) {
const rn = regionNameById.get(Number(parsed.region_id));
// Kein DB-Feld: explizit in dataValues setzen, damit toJSON() es mitschickt
if (n.dataValues) n.dataValues.region_name = rn;
}
}
}