All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 2m45s
143 lines
4.8 KiB
Rust
Executable File
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()
|
|
}
|