Files
yourpart-daemon/src/worker/death_log.rs
Torsten Schulz (local) 8f8fb8418a
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 2m45s
Ändere Dateiberechtigungen auf ausführbar für mehrere Dateien
2026-07-16 09:16:49 +02:00

143 lines
4.8 KiB
Rust
Executable File

//! 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<Row>, key: &str) -> Vec<String> {
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<String> {
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<Value, DbError> {
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::<i32>().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::<i32>().ok());
let region_label = base
.get("region_label")
.cloned()
.unwrap_or_default();
let age_years = base
.get("age_years")
.and_then(|v| v.parse::<i32>().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()
}