Refactor and reintroduce character name enrichment logic in FalukantService
- Moved the enrichNotificationsWithCharacterNames function back into the FalukantService class, ensuring character names are correctly parsed and attached to notifications. - Implemented a comprehensive ID collection and resolution mechanism for character references, enhancing the accuracy of character name assignments. - Improved error handling during JSON parsing and ID resolution to maintain robustness in notification processing.
This commit is contained in:
@@ -6093,194 +6093,6 @@ class FalukantService extends BaseService {
|
|||||||
all: mapped
|
all: mapped
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export default new FalukantService();
|
|
||||||
|
|
||||||
// Helper: parse notifications for character references and attach characterName
|
|
||||||
async function enrichNotificationsWithCharacterNames(notifications) {
|
|
||||||
if (!Array.isArray(notifications) || notifications.length === 0) return;
|
|
||||||
|
|
||||||
const charIds = new Set();
|
|
||||||
const firstNameIds = new Set();
|
|
||||||
const lastNameIds = new Set();
|
|
||||||
|
|
||||||
// recursive collector that extracts any character id fields and name IDs
|
|
||||||
function collectIds(obj) {
|
|
||||||
if (!obj) return;
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
for (const it of obj) collectIds(it);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof obj !== 'object') return;
|
|
||||||
for (const [k, v] of Object.entries(obj)) {
|
|
||||||
if (!v) continue;
|
|
||||||
if (k === 'character_id' || k === 'characterId') {
|
|
||||||
charIds.add(Number(v));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Sammle first_name und last_name IDs
|
|
||||||
if (k === 'character_first_name' && !isNaN(Number(v))) {
|
|
||||||
firstNameIds.add(Number(v));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (k === 'character_last_name' && !isNaN(Number(v))) {
|
|
||||||
lastNameIds.add(Number(v));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (k === 'character' && typeof v === 'object') {
|
|
||||||
if (v.id) charIds.add(Number(v.id));
|
|
||||||
if (v.character_id) charIds.add(Number(v.character_id));
|
|
||||||
if (v.characterId) charIds.add(Number(v.characterId));
|
|
||||||
collectIds(v);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
collectIds(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// First pass: collect all referenced character ids from notifications
|
|
||||||
for (const n of notifications) {
|
|
||||||
// parse n.tr if it's JSON
|
|
||||||
try {
|
|
||||||
if (typeof n.tr === 'string' && n.tr.trim().startsWith('{')) {
|
|
||||||
const parsed = JSON.parse(n.tr);
|
|
||||||
collectIds(parsed);
|
|
||||||
}
|
|
||||||
} catch (err) { /* ignore */ }
|
|
||||||
|
|
||||||
// parse n.effects if present
|
|
||||||
try {
|
|
||||||
if (n.effects) {
|
|
||||||
let eff = n.effects;
|
|
||||||
if (typeof eff === 'string') {
|
|
||||||
eff = JSON.parse(eff);
|
|
||||||
}
|
|
||||||
collectIds(eff);
|
|
||||||
}
|
|
||||||
} catch (err) { /* ignore */ }
|
|
||||||
|
|
||||||
// top-level fields
|
|
||||||
if (n.character_id) charIds.add(Number(n.character_id));
|
|
||||||
if (n.characterId) charIds.add(Number(n.characterId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Batch load first names and last names
|
|
||||||
const [firstNames, lastNames] = await Promise.all([
|
|
||||||
firstNameIds.size > 0
|
|
||||||
? FalukantPredefineFirstname.findAll({ where: { id: { [Op.in]: Array.from(firstNameIds) } }, attributes: ['id', 'name'] })
|
|
||||||
: [],
|
|
||||||
lastNameIds.size > 0
|
|
||||||
? FalukantPredefineLastname.findAll({ where: { id: { [Op.in]: Array.from(lastNameIds) } }, attributes: ['id', 'name'] })
|
|
||||||
: []
|
|
||||||
]);
|
|
||||||
const firstNameMap = new Map(firstNames.map(fn => [fn.id, fn.name]));
|
|
||||||
const lastNameMap = new Map(lastNames.map(ln => [ln.id, ln.name]));
|
|
||||||
|
|
||||||
const ids = Array.from(charIds).filter(Boolean);
|
|
||||||
|
|
||||||
// Batch load characters and their display names
|
|
||||||
const characters = ids.length > 0 ? await FalukantCharacter.findAll({
|
|
||||||
where: { id: { [Op.in]: ids } },
|
|
||||||
include: [
|
|
||||||
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
|
||||||
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] }
|
|
||||||
],
|
|
||||||
attributes: ['id']
|
|
||||||
}) : [];
|
|
||||||
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper to find first character id in an object
|
|
||||||
function findFirstId(obj) {
|
|
||||||
if (!obj) return null;
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
for (const it of obj) {
|
|
||||||
const r = findFirstId(it);
|
|
||||||
if (r) return r;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (typeof obj !== 'object') return null;
|
|
||||||
for (const [k, v] of Object.entries(obj)) {
|
|
||||||
if (!v) continue;
|
|
||||||
if (k === 'character_id' || k === 'characterId') return Number(v);
|
|
||||||
if (k === 'character' && typeof v === 'object') {
|
|
||||||
if (v.id) return Number(v.id);
|
|
||||||
const r = findFirstId(v);
|
|
||||||
if (r) return r;
|
|
||||||
}
|
|
||||||
const r = findFirstId(v);
|
|
||||||
if (r) return r;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: resolve name IDs in an object and build characterName
|
|
||||||
function resolveNameFromObject(obj) {
|
|
||||||
if (!obj || typeof obj !== 'object') return null;
|
|
||||||
const firstNameId = obj.character_first_name;
|
|
||||||
const lastNameId = obj.character_last_name;
|
|
||||||
if (firstNameId || lastNameId) {
|
|
||||||
const first = firstNameMap.get(Number(firstNameId)) || '';
|
|
||||||
const last = lastNameMap.get(Number(lastNameId)) || '';
|
|
||||||
const name = `${first} ${last}`.trim();
|
|
||||||
if (name) return name;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach resolved name to notifications (set both characterName and character_name)
|
|
||||||
for (const n of notifications) {
|
|
||||||
let foundId = null;
|
|
||||||
let resolvedName = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (typeof n.tr === 'string' && n.tr.trim().startsWith('{')) {
|
|
||||||
const parsed = JSON.parse(n.tr);
|
|
||||||
foundId = findFirstId(parsed) || foundId;
|
|
||||||
// Versuche Namen aus first_name/last_name IDs aufzulösen
|
|
||||||
resolvedName = resolveNameFromObject(parsed.value || parsed) || resolvedName;
|
|
||||||
}
|
|
||||||
} catch (err) { /* ignore */ }
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (n.effects) {
|
|
||||||
let eff = n.effects;
|
|
||||||
if (typeof eff === 'string') {
|
|
||||||
eff = JSON.parse(eff);
|
|
||||||
}
|
|
||||||
foundId = findFirstId(eff) || foundId;
|
|
||||||
// Auch in effects nach Namen suchen
|
|
||||||
if (Array.isArray(eff)) {
|
|
||||||
for (const e of eff) {
|
|
||||||
resolvedName = resolveNameFromObject(e) || resolvedName;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolvedName = resolveNameFromObject(eff) || resolvedName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) { /* ignore */ }
|
|
||||||
|
|
||||||
if (!foundId && n.character_id) foundId = Number(n.character_id);
|
|
||||||
if (!foundId && n.characterId) foundId = Number(n.characterId);
|
|
||||||
|
|
||||||
// Priorität: aufgelöster Name aus IDs > Name aus character_id > Fallback
|
|
||||||
if (resolvedName) {
|
|
||||||
n.characterName = resolvedName;
|
|
||||||
n.character_name = resolvedName;
|
|
||||||
} else if (foundId && nameMap.has(Number(foundId))) {
|
|
||||||
const resolved = nameMap.get(Number(foundId));
|
|
||||||
n.characterName = resolved;
|
|
||||||
n.character_name = resolved;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Church Career Methods ====================
|
// ==================== Church Career Methods ====================
|
||||||
|
|
||||||
@@ -6836,3 +6648,191 @@ async function enrichNotificationsWithCharacterNames(notifications) {
|
|||||||
return !!churchOffice;
|
return !!churchOffice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default new FalukantService();
|
||||||
|
|
||||||
|
// Helper: parse notifications for character references and attach characterName
|
||||||
|
async function enrichNotificationsWithCharacterNames(notifications) {
|
||||||
|
if (!Array.isArray(notifications) || notifications.length === 0) return;
|
||||||
|
|
||||||
|
const charIds = new Set();
|
||||||
|
const firstNameIds = new Set();
|
||||||
|
const lastNameIds = new Set();
|
||||||
|
|
||||||
|
// recursive collector that extracts any character id fields and name IDs
|
||||||
|
function collectIds(obj) {
|
||||||
|
if (!obj) return;
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
for (const it of obj) collectIds(it);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof obj !== 'object') return;
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
if (!v) continue;
|
||||||
|
if (k === 'character_id' || k === 'characterId') {
|
||||||
|
charIds.add(Number(v));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Sammle first_name und last_name IDs
|
||||||
|
if (k === 'character_first_name' && !isNaN(Number(v))) {
|
||||||
|
firstNameIds.add(Number(v));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (k === 'character_last_name' && !isNaN(Number(v))) {
|
||||||
|
lastNameIds.add(Number(v));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (k === 'character' && typeof v === 'object') {
|
||||||
|
if (v.id) charIds.add(Number(v.id));
|
||||||
|
if (v.character_id) charIds.add(Number(v.character_id));
|
||||||
|
if (v.characterId) charIds.add(Number(v.characterId));
|
||||||
|
collectIds(v);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
collectIds(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First pass: collect all referenced character ids from notifications
|
||||||
|
for (const n of notifications) {
|
||||||
|
// parse n.tr if it's JSON
|
||||||
|
try {
|
||||||
|
if (typeof n.tr === 'string' && n.tr.trim().startsWith('{')) {
|
||||||
|
const parsed = JSON.parse(n.tr);
|
||||||
|
collectIds(parsed);
|
||||||
|
}
|
||||||
|
} catch (err) { /* ignore */ }
|
||||||
|
|
||||||
|
// parse n.effects if present
|
||||||
|
try {
|
||||||
|
if (n.effects) {
|
||||||
|
let eff = n.effects;
|
||||||
|
if (typeof eff === 'string') {
|
||||||
|
eff = JSON.parse(eff);
|
||||||
|
}
|
||||||
|
collectIds(eff);
|
||||||
|
}
|
||||||
|
} catch (err) { /* ignore */ }
|
||||||
|
|
||||||
|
// top-level fields
|
||||||
|
if (n.character_id) charIds.add(Number(n.character_id));
|
||||||
|
if (n.characterId) charIds.add(Number(n.characterId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch load first names and last names
|
||||||
|
const [firstNames, lastNames] = await Promise.all([
|
||||||
|
firstNameIds.size > 0
|
||||||
|
? FalukantPredefineFirstname.findAll({ where: { id: { [Op.in]: Array.from(firstNameIds) } }, attributes: ['id', 'name'] })
|
||||||
|
: [],
|
||||||
|
lastNameIds.size > 0
|
||||||
|
? FalukantPredefineLastname.findAll({ where: { id: { [Op.in]: Array.from(lastNameIds) } }, attributes: ['id', 'name'] })
|
||||||
|
: []
|
||||||
|
]);
|
||||||
|
const firstNameMap = new Map(firstNames.map(fn => [fn.id, fn.name]));
|
||||||
|
const lastNameMap = new Map(lastNames.map(ln => [ln.id, ln.name]));
|
||||||
|
|
||||||
|
const ids = Array.from(charIds).filter(Boolean);
|
||||||
|
|
||||||
|
// Batch load characters and their display names
|
||||||
|
const characters = ids.length > 0 ? await FalukantCharacter.findAll({
|
||||||
|
where: { id: { [Op.in]: ids } },
|
||||||
|
include: [
|
||||||
|
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
||||||
|
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] }
|
||||||
|
],
|
||||||
|
attributes: ['id']
|
||||||
|
}) : [];
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper to find first character id in an object
|
||||||
|
function findFirstId(obj) {
|
||||||
|
if (!obj) return null;
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
for (const it of obj) {
|
||||||
|
const r = findFirstId(it);
|
||||||
|
if (r) return r;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof obj !== 'object') return null;
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
if (!v) continue;
|
||||||
|
if (k === 'character_id' || k === 'characterId') return Number(v);
|
||||||
|
if (k === 'character' && typeof v === 'object') {
|
||||||
|
if (v.id) return Number(v.id);
|
||||||
|
const r = findFirstId(v);
|
||||||
|
if (r) return r;
|
||||||
|
}
|
||||||
|
const r = findFirstId(v);
|
||||||
|
if (r) return r;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: resolve name IDs in an object and build characterName
|
||||||
|
function resolveNameFromObject(obj) {
|
||||||
|
if (!obj || typeof obj !== 'object') return null;
|
||||||
|
const firstNameId = obj.character_first_name;
|
||||||
|
const lastNameId = obj.character_last_name;
|
||||||
|
if (firstNameId || lastNameId) {
|
||||||
|
const first = firstNameMap.get(Number(firstNameId)) || '';
|
||||||
|
const last = lastNameMap.get(Number(lastNameId)) || '';
|
||||||
|
const name = `${first} ${last}`.trim();
|
||||||
|
if (name) return name;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach resolved name to notifications (set both characterName and character_name)
|
||||||
|
for (const n of notifications) {
|
||||||
|
let foundId = null;
|
||||||
|
let resolvedName = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof n.tr === 'string' && n.tr.trim().startsWith('{')) {
|
||||||
|
const parsed = JSON.parse(n.tr);
|
||||||
|
foundId = findFirstId(parsed) || foundId;
|
||||||
|
// Versuche Namen aus first_name/last_name IDs aufzulösen
|
||||||
|
resolvedName = resolveNameFromObject(parsed.value || parsed) || resolvedName;
|
||||||
|
}
|
||||||
|
} catch (err) { /* ignore */ }
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (n.effects) {
|
||||||
|
let eff = n.effects;
|
||||||
|
if (typeof eff === 'string') {
|
||||||
|
eff = JSON.parse(eff);
|
||||||
|
}
|
||||||
|
foundId = findFirstId(eff) || foundId;
|
||||||
|
// Auch in effects nach Namen suchen
|
||||||
|
if (Array.isArray(eff)) {
|
||||||
|
for (const e of eff) {
|
||||||
|
resolvedName = resolveNameFromObject(e) || resolvedName;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolvedName = resolveNameFromObject(eff) || resolvedName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) { /* ignore */ }
|
||||||
|
|
||||||
|
if (!foundId && n.character_id) foundId = Number(n.character_id);
|
||||||
|
if (!foundId && n.characterId) foundId = Number(n.characterId);
|
||||||
|
|
||||||
|
// Priorität: aufgelöster Name aus IDs > Name aus character_id > Fallback
|
||||||
|
if (resolvedName) {
|
||||||
|
n.characterName = resolvedName;
|
||||||
|
n.character_name = resolvedName;
|
||||||
|
} else if (foundId && nameMap.has(Number(foundId))) {
|
||||||
|
const resolved = nameMap.get(Number(foundId));
|
||||||
|
n.characterName = resolved;
|
||||||
|
n.character_name = resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user