Remove deprecated relationship change log migration and enhance error handling in FalukantService for partner retrieval

This commit is contained in:
Torsten Schulz (local)
2026-01-26 10:10:22 +01:00
parent bba68da488
commit 80b639b511
3 changed files with 118 additions and 134 deletions

View File

@@ -1,105 +0,0 @@
/* eslint-disable */
'use strict';
/** Log-Tabelle für alle Änderungen an relationship und marriage_proposals (keine Einträge werden gelöscht).
* Hilft zu analysieren, warum z.B. Werbungen um einen Partner über Nacht verschwinden. */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS falukant_log.relationship_change_log (
id serial PRIMARY KEY,
changed_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
table_name character varying(64) NOT NULL,
operation character varying(16) NOT NULL,
record_id integer,
payload_old jsonb,
payload_new jsonb
);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS relationship_change_log_changed_at_idx
ON falukant_log.relationship_change_log (changed_at);
`);
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS relationship_change_log_table_operation_idx
ON falukant_log.relationship_change_log (table_name, operation);
`);
const triggerFunction = `
CREATE OR REPLACE FUNCTION falukant_log.log_relationship_change()
RETURNS TRIGGER AS $$
DECLARE
v_record_id INTEGER;
v_payload_old JSONB;
v_payload_new JSONB;
BEGIN
IF TG_OP = 'INSERT' THEN
v_record_id := NEW.id;
v_payload_old := NULL;
v_payload_new := to_jsonb(NEW);
ELSIF TG_OP = 'UPDATE' THEN
v_record_id := NEW.id;
v_payload_old := to_jsonb(OLD);
v_payload_new := to_jsonb(NEW);
ELSIF TG_OP = 'DELETE' THEN
v_record_id := OLD.id;
v_payload_old := to_jsonb(OLD);
v_payload_new := NULL;
END IF;
INSERT INTO falukant_log.relationship_change_log (
table_name,
operation,
record_id,
payload_old,
payload_new
) VALUES (
TG_TABLE_NAME,
TG_OP,
v_record_id,
v_payload_old,
v_payload_new
);
IF TG_OP = 'DELETE' THEN
RETURN OLD;
ELSE
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
`;
await queryInterface.sequelize.query(triggerFunction);
await queryInterface.sequelize.query(`
DROP TRIGGER IF EXISTS trg_log_relationship_change ON falukant_data.relationship;
CREATE TRIGGER trg_log_relationship_change
AFTER INSERT OR UPDATE OR DELETE ON falukant_data.relationship
FOR EACH ROW
EXECUTE FUNCTION falukant_log.log_relationship_change();
`);
await queryInterface.sequelize.query(`
DROP TRIGGER IF EXISTS trg_log_relationship_change ON falukant_data.marriage_proposals;
CREATE TRIGGER trg_log_relationship_change
AFTER INSERT OR UPDATE OR DELETE ON falukant_data.marriage_proposals
FOR EACH ROW
EXECUTE FUNCTION falukant_log.log_relationship_change();
`);
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query(`
DROP TRIGGER IF EXISTS trg_log_relationship_change ON falukant_data.relationship;
`);
await queryInterface.sequelize.query(`
DROP TRIGGER IF EXISTS trg_log_relationship_change ON falukant_data.marriage_proposals;
`);
await queryInterface.sequelize.query(`
DROP FUNCTION IF EXISTS falukant_log.log_relationship_change();
`);
await queryInterface.sequelize.query(`
DROP TABLE IF EXISTS falukant_log.relationship_change_log;
`);
},
};

View File

@@ -3158,6 +3158,7 @@ class FalukantService extends BaseService {
const step7Start = Date.now(); const step7Start = Date.now();
const ownAge = calcAge(character.birthdate); const ownAge = calcAge(character.birthdate);
if (ownAge >= 12 && family.relationships.length === 0) { if (ownAge >= 12 && family.relationships.length === 0) {
try {
family.possiblePartners = await this.getPossiblePartners(character.id); family.possiblePartners = await this.getPossiblePartners(character.id);
if (family.possiblePartners.length === 0) { if (family.possiblePartners.length === 0) {
// Asynchron erstellen, nicht blockieren // Asynchron erstellen, nicht blockieren
@@ -3169,6 +3170,12 @@ class FalukantService extends BaseService {
ownAge ownAge
).catch(err => console.error('[getFamily] Error creating partners (async):', err)); ).catch(err => console.error('[getFamily] Error creating partners (async):', err));
} }
} catch (err) {
// Fehler beim Laden nicht an Frontend durchreichen Seite bleibt nutzbar, Log für Analyse
console.error('[getFamily] getPossiblePartners failed (characterId=%s):', character.id, err?.message || err);
if (err?.stack) console.error('[getFamily] getPossiblePartners stack:', err.stack);
family.possiblePartners = [];
}
} }
timings.step7_possible_partners = Date.now() - step7Start; timings.step7_possible_partners = Date.now() - step7Start;
@@ -3399,33 +3406,44 @@ class FalukantService extends BaseService {
{ {
model: FalukantCharacter, model: FalukantCharacter,
as: 'proposedCharacter', as: 'proposedCharacter',
required: false,
attributes: ['id', 'firstName', 'lastName', 'gender', 'regionId', 'birthdate'], attributes: ['id', 'firstName', 'lastName', 'gender', 'regionId', 'birthdate'],
include: [ include: [
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }, { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'], required: false },
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] }, { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'], required: false },
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }, { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'], required: false },
], ],
}, },
], ],
}); });
return proposals const valid = [];
.filter(proposal => proposal.proposedCharacter !== null) for (const proposal of proposals) {
.map(proposal => { if (proposal.proposedCharacter == null) {
const birthdate = new Date(proposal.proposedCharacter.birthdate); console.warn('[getPossiblePartners] Proposal id=%s filtered out (proposedCharacterId=%s missing)', proposal.id, proposal.proposedCharacterId);
const age = calcAge(birthdate); continue;
return { }
const pc = proposal.proposedCharacter;
const birthdate = pc.birthdate ? new Date(pc.birthdate) : null;
const age = birthdate ? calcAge(birthdate) : 0;
const name = `${pc.definedFirstName?.name ?? ''} ${pc.definedLastName?.name ?? ''}`.trim() || null;
if (!name) {
console.warn('[getPossiblePartners] Proposal id=%s filtered out (proposedCharacterId=%s has no name)', proposal.id, pc.id);
continue;
}
valid.push({
id: proposal.id, id: proposal.id,
requesterCharacterId: proposal.requesterCharacterId, requesterCharacterId: proposal.requesterCharacterId,
proposedCharacterId: proposal.proposedCharacter.id, proposedCharacterId: pc.id,
proposedCharacterName: `${proposal.proposedCharacter.definedFirstName?.name} ${proposal.proposedCharacter.definedLastName?.name}`, proposedCharacterName: name,
proposedCharacterGender: proposal.proposedCharacter.gender, proposedCharacterGender: pc.gender,
proposedCharacterRegionId: proposal.proposedCharacter.regionId, proposedCharacterRegionId: pc.regionId,
proposedCharacterAge: age, proposedCharacterAge: age,
proposedCharacterNobleTitle: proposal.proposedCharacter.nobleTitle?.labelTr || null, proposedCharacterNobleTitle: pc.nobleTitle?.labelTr ?? null,
cost: proposal.cost, cost: proposal.cost,
};
}); });
} }
return valid;
}
async createPossiblePartners(requestingCharacterId, requestingCharacterGender, requestingRegionId, requestingCharacterTitleOfNobility, ownAge) { async createPossiblePartners(requestingCharacterId, requestingCharacterGender, requestingRegionId, requestingCharacterTitleOfNobility, ownAge) {
try { try {

View File

@@ -387,6 +387,77 @@ const syncDatabase = async () => {
console.warn('⚠️ Konnte Vocab-Trainer Tabellen nicht sicherstellen:', e?.message || e); console.warn('⚠️ Konnte Vocab-Trainer Tabellen nicht sicherstellen:', e?.message || e);
} }
// Relationship-/Marriage-Proposal-Änderungen loggen (keine Einträge löschen; ohne db:migrate)
console.log("Ensuring relationship change log (falukant) exists...");
try {
await sequelize.query(`
CREATE TABLE IF NOT EXISTS falukant_log.relationship_change_log (
id serial PRIMARY KEY,
changed_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
table_name character varying(64) NOT NULL,
operation character varying(16) NOT NULL,
record_id integer,
payload_old jsonb,
payload_new jsonb
);
`);
await sequelize.query(`
CREATE INDEX IF NOT EXISTS relationship_change_log_changed_at_idx
ON falukant_log.relationship_change_log (changed_at);
`);
await sequelize.query(`
CREATE INDEX IF NOT EXISTS relationship_change_log_table_operation_idx
ON falukant_log.relationship_change_log (table_name, operation);
`);
await sequelize.query(`
CREATE OR REPLACE FUNCTION falukant_log.log_relationship_change()
RETURNS TRIGGER AS $$
DECLARE
v_record_id INTEGER;
v_payload_old JSONB;
v_payload_new JSONB;
BEGIN
IF TG_OP = 'INSERT' THEN
v_record_id := NEW.id;
v_payload_old := NULL;
v_payload_new := to_jsonb(NEW);
ELSIF TG_OP = 'UPDATE' THEN
v_record_id := NEW.id;
v_payload_old := to_jsonb(OLD);
v_payload_new := to_jsonb(NEW);
ELSIF TG_OP = 'DELETE' THEN
v_record_id := OLD.id;
v_payload_old := to_jsonb(OLD);
v_payload_new := NULL;
END IF;
INSERT INTO falukant_log.relationship_change_log (
table_name, operation, record_id, payload_old, payload_new
) VALUES (
TG_TABLE_NAME, TG_OP, v_record_id, v_payload_old, v_payload_new
);
IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
END;
$$ LANGUAGE plpgsql;
`);
await sequelize.query(`
DROP TRIGGER IF EXISTS trg_log_relationship_change ON falukant_data.relationship;
CREATE TRIGGER trg_log_relationship_change
AFTER INSERT OR UPDATE OR DELETE ON falukant_data.relationship
FOR EACH ROW
EXECUTE FUNCTION falukant_log.log_relationship_change();
`);
await sequelize.query(`
DROP TRIGGER IF EXISTS trg_log_relationship_change ON falukant_data.marriage_proposals;
CREATE TRIGGER trg_log_relationship_change
AFTER INSERT OR UPDATE OR DELETE ON falukant_data.marriage_proposals
FOR EACH ROW
EXECUTE FUNCTION falukant_log.log_relationship_change();
`);
console.log("✅ relationship_change_log und Trigger sind vorhanden.");
} catch (e) {
console.warn('⚠️ relationship_change_log/Trigger konnten nicht sichergestellt werden:', e?.message || e);
}
// Vorab: Stelle kritische Spalten sicher, damit Index-Erstellung nicht fehlschlägt // Vorab: Stelle kritische Spalten sicher, damit Index-Erstellung nicht fehlschlägt
console.log("Pre-ensure Taxi columns (traffic_light) ..."); console.log("Pre-ensure Taxi columns (traffic_light) ...");
try { try {