//! Kontext für `falukant_log.notification` bei Charaktertod (Direktor, Partner, Eltern). //! `tr` enthält JSON mit Verstorbenem, Ansässigkeit, Alter, Ehe/Kind/Liebschaft. //! //! **Aufruf vor** `DELETE` auf `relationship` / `child_relation`, sonst sind Partner/Kinder/Geliebte leer. //! //! **Geliebte** sind keine eigene Tabelle: `falukant_data.relationship` + Typ `falukant_type.relationship.tr = 'lover'`, //! Zusatzdaten in `relationship_state`. Zählt nur, wenn `relationship_state.active` nicht `false` ist (wie Backend-UI). //! `lover_role` (`secret_affair` / `lover` / `mistress_or_favorite`) wird für die Liste **nicht** gefiltert. //! //! Kinder aus Liebschaft sind in `child_relation.birth_context = 'lover'` markiert; zusätzlich unter `linked.lover_birth_children`. use crate::db::{DbConnection, DbError, Row}; use serde_json::{json, Value}; use crate::worker::sql::{ QUERY_DEATH_LOG_CHARACTER_BASE, QUERY_DEATH_LOG_CHILD_DISPLAY_NAMES, QUERY_DEATH_LOG_CHILD_LOVER_BIRTH_DISPLAY_NAMES, QUERY_DEATH_LOG_LOVER_DISPLAY_NAMES, QUERY_DEATH_LOG_SPOUSE_DISPLAY_NAMES, }; fn collect_display_names(rows: Vec, key: &str) -> Vec { let mut out = Vec::new(); for r in rows { if let Some(s) = r.get(key) { let t = s.trim(); if !t.is_empty() { out.push(t.to_string()); } } } out } /// Verknüpfte Namen (Ehe, Kinder, Geliebte): optional — bei Schema-/SQL-Fehlern leere Liste, /// damit der **Kern** (`deceased`) trotzdem als JSON in `notification.tr` landet. fn optional_linked_names( conn: &mut DbConnection, log_label: &'static str, stmt: &'static str, query: &str, deceased_character_id: i32, ) -> Vec { if let Err(e) = conn.prepare(stmt, query) { eprintln!("[death_log] {log_label} prepare: {e}"); return Vec::new(); } match conn.execute(stmt, &[&deceased_character_id]) { Ok(rows) => collect_display_names(rows, "display_name"), Err(e) => { eprintln!("[death_log] {log_label} execute: {e}"); Vec::new() } } } /// Lädt Verstorbenen + Ehepartner, Kinder, **aktive Geliebte** (`tr = 'lover'`, `active IS NOT FALSE`). /// Muss ausgeführt werden, bevor Beziehungen aus der DB gelöscht werden. pub fn build_deceased_context(conn: &mut DbConnection, deceased_character_id: i32) -> Result { conn.prepare("death_base", QUERY_DEATH_LOG_CHARACTER_BASE)?; let base_rows = conn.execute("death_base", &[&deceased_character_id])?; let base = base_rows.first().ok_or_else(|| { DbError::new(format!( "Todes-Log: Charakter {deceased_character_id} nicht gefunden" )) })?; let character_id = base .get("character_id") .and_then(|v| v.parse::().ok()) .unwrap_or(deceased_character_id); let display_name = base .get("display_name") .cloned() .unwrap_or_else(|| character_id.to_string()); let region_id = base .get("region_id") .and_then(|v| v.parse::().ok()); let region_label = base .get("region_label") .cloned() .unwrap_or_default(); let age_years = base .get("age_years") .and_then(|v| v.parse::().ok()) .unwrap_or(0); let spouses = optional_linked_names( conn, "spouse", "death_spouse", QUERY_DEATH_LOG_SPOUSE_DISPLAY_NAMES, deceased_character_id, ); let children = optional_linked_names( conn, "child", "death_child", QUERY_DEATH_LOG_CHILD_DISPLAY_NAMES, deceased_character_id, ); let lover_birth_children = optional_linked_names( conn, "child_lover_birth", "death_child_lover_birth", QUERY_DEATH_LOG_CHILD_LOVER_BIRTH_DISPLAY_NAMES, deceased_character_id, ); let lovers = optional_linked_names( conn, "lover", "death_lover", QUERY_DEATH_LOG_LOVER_DISPLAY_NAMES, deceased_character_id, ); Ok(json!({ "deceased": { "character_id": character_id, "display_name": display_name, "region_id": region_id, "region_label": region_label, "age_years": age_years, }, "linked": { "spouses": spouses, "children": children, "lover_birth_children": lover_birth_children, "lovers": lovers, }, })) } /// Vollständiges `tr`-/WebSocket-JSON mit `event` und `user_id`. pub fn wrap_death_notification(context: &Value, event: &str, user_id: i32) -> String { json!({ "event": event, "user_id": user_id, "deceased": context.get("deceased"), "linked": context.get("linked"), }) .to_string() }