cleanup at death
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 4m44s
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 4m44s
This commit is contained in:
@@ -15,6 +15,7 @@ Verhindern, dass nach Tod/Erbe ein Mischzustand entsteht
|
||||
## Voraussetzungen
|
||||
|
||||
- Daemon läuft mit aktuellen Änderungen.
|
||||
- Migration `020_falukant_character_relation_cleanup.sql` ist eingespielt.
|
||||
- Zugriff auf DB (psql).
|
||||
- Zugriff auf WebSocket-Eventstream oder Daemon-Log.
|
||||
- Ein Test-User mit:
|
||||
@@ -63,6 +64,15 @@ WHERE c.user_id = <USER_ID>;
|
||||
|
||||
```sql
|
||||
-- Todes-Cleanup (sollte leer sein)
|
||||
SELECT COUNT(*) FROM falukant_data.relationship
|
||||
WHERE character1_id = <CHARACTER_ID> OR character2_id = <CHARACTER_ID>;
|
||||
SELECT COUNT(*) FROM falukant_data.child_relation
|
||||
WHERE child_character_id = <CHARACTER_ID>
|
||||
OR father_character_id = <CHARACTER_ID>
|
||||
OR mother_character_id = <CHARACTER_ID>;
|
||||
-- Es dürfen auch keine Zustände gelöschter Beziehungen zurückbleiben.
|
||||
SELECT COUNT(*) FROM falukant_data.relationship_state rs
|
||||
WHERE NOT EXISTS (SELECT 1 FROM falukant_data.relationship r WHERE r.id = rs.relationship_id);
|
||||
SELECT COUNT(*) FROM falukant_data.knowledge WHERE character_id = <CHARACTER_ID>;
|
||||
SELECT COUNT(*) FROM falukant_data.debtors_prism WHERE character_id = <CHARACTER_ID>;
|
||||
SELECT COUNT(*) FROM falukant_data.political_office WHERE character_id = <CHARACTER_ID>;
|
||||
@@ -106,5 +116,8 @@ Nach Eintreffen von `falukantUpdateStatus`:
|
||||
- [ ] `CharacterDeath` kommt genau für den verstorbenen Charakter.
|
||||
- [ ] `falukantUpdateStatus` kommt nach Erbwechsel für den betroffenen User.
|
||||
- [ ] `knowledge`/`debtors_prism`/`political_office`/`election_candidate` sind bereinigt.
|
||||
- [ ] `relationship`/`relationship_state`/`child_relation` sind bereinigt (beide Partner, alle drei Kind-/Elternrollen).
|
||||
- [ ] Kindstod benachrichtigt auch einen einzelnen bekannten Elternteil (`NULL` beim anderen Elternteil).
|
||||
- [ ] Ein Benachrichtigungsfehler unterbricht den NPC-Todes-Cleanup nicht.
|
||||
- [ ] `political_office_history` enthält entfernte Ämter (falls vorhanden).
|
||||
- [ ] UI bleibt konsistent (Bild + Status gehören zum selben Charakter).
|
||||
|
||||
77
migrations/020_falukant_character_relation_cleanup.sql
Normal file
77
migrations/020_falukant_character_relation_cleanup.sql
Normal file
@@ -0,0 +1,77 @@
|
||||
-- Charakter-Verknüpfungen auch bei DELETEs außerhalb der Worker bereinigen.
|
||||
-- Löscht bestehende verwaiste Beziehungen; vorher auf der Ziel-DB sichern.
|
||||
BEGIN;
|
||||
|
||||
LOCK TABLE falukant_data.character, falukant_data.relationship,
|
||||
falukant_data.relationship_state, falukant_data.child_relation
|
||||
IN SHARE ROW EXCLUSIVE MODE;
|
||||
|
||||
DELETE FROM falukant_data.relationship_state rs
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM falukant_data.relationship r WHERE r.id = rs.relationship_id
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM falukant_data.relationship r
|
||||
WHERE r.id = rs.relationship_id
|
||||
AND ((r.character1_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1 FROM falukant_data.character c WHERE c.id = r.character1_id))
|
||||
OR (r.character2_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1 FROM falukant_data.character c WHERE c.id = r.character2_id)))
|
||||
);
|
||||
|
||||
DELETE FROM falukant_data.relationship r
|
||||
WHERE (r.character1_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1 FROM falukant_data.character c WHERE c.id = r.character1_id
|
||||
)) OR (r.character2_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1 FROM falukant_data.character c WHERE c.id = r.character2_id
|
||||
));
|
||||
|
||||
DELETE FROM falukant_data.child_relation cr
|
||||
WHERE (cr.child_character_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1 FROM falukant_data.character c WHERE c.id = cr.child_character_id
|
||||
)) OR (cr.father_character_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1 FROM falukant_data.character c WHERE c.id = cr.father_character_id
|
||||
)) OR (cr.mother_character_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1 FROM falukant_data.character c WHERE c.id = cr.mother_character_id
|
||||
));
|
||||
|
||||
-- Vorhandene einspaltige FKs ersetzen (deren Namen sind je nach Backend verschieden).
|
||||
-- Auch relationship_state absichern, damit es die Kaskade nicht blockieren kann.
|
||||
DO $$
|
||||
DECLARE
|
||||
target record;
|
||||
existing_fk record;
|
||||
BEGIN
|
||||
FOR target IN
|
||||
SELECT * FROM (VALUES
|
||||
('relationship', 'character1_id', 'character'),
|
||||
('relationship', 'character2_id', 'character'),
|
||||
('child_relation', 'child_character_id', 'character'),
|
||||
('child_relation', 'father_character_id', 'character'),
|
||||
('child_relation', 'mother_character_id', 'character'),
|
||||
('relationship_state', 'relationship_id', 'relationship')
|
||||
) AS targets(table_name, column_name, referenced_table)
|
||||
LOOP
|
||||
FOR existing_fk IN
|
||||
SELECT con.conname
|
||||
FROM pg_constraint con
|
||||
JOIN pg_attribute att ON att.attrelid = con.conrelid
|
||||
AND con.conkey = ARRAY[att.attnum]
|
||||
WHERE con.contype = 'f'
|
||||
AND con.conrelid = format('falukant_data.%I', target.table_name)::regclass
|
||||
AND con.confrelid = format('falukant_data.%I', target.referenced_table)::regclass
|
||||
AND att.attname = target.column_name
|
||||
LOOP
|
||||
EXECUTE format('ALTER TABLE falukant_data.%I DROP CONSTRAINT %I',
|
||||
target.table_name, existing_fk.conname);
|
||||
END LOOP;
|
||||
|
||||
EXECUTE format(
|
||||
'ALTER TABLE falukant_data.%I ADD CONSTRAINT %I FOREIGN KEY (%I) REFERENCES falukant_data.%I (id) ON DELETE CASCADE',
|
||||
target.table_name, target.table_name || '_' || target.column_name || '_fkey',
|
||||
target.column_name, target.referenced_table);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
@@ -65,3 +65,20 @@ negativen Ruf- und Eheeffekte für niedrige und mittlere Stände).
|
||||
Der Backend-Auftrag verwendet `performer_id`, `victim_id` und optional in
|
||||
`parameters` `goal` (`expose`, Standard; oder `blackmail`) sowie
|
||||
`relationship_id`. Details: `docs/FALUKANT_UNDERGROUND_INVESTIGATE_AFFAIR.md`.
|
||||
|
||||
## `020_falukant_character_relation_cleanup.sql`
|
||||
|
||||
Entfernt bestehende verwaiste Einträge aus `relationship`, `relationship_state`
|
||||
und `child_relation`. Sichert anschließend beide Beziehungspartner sowie Kind,
|
||||
Vater und Mutter mit Fremdschlüsseln und `ON DELETE CASCADE` ab. Damit werden
|
||||
die Verknüpfungen bei jeder Charakter-Löschung entfernt, auch außerhalb des
|
||||
Daemons. `relationship_state` wird zusammen mit der Beziehung gelöscht.
|
||||
Zeilen mit unbekanntem Elternteil (`NULL`) bleiben gültig.
|
||||
|
||||
Voraussetzung: Migration `001` ist eingespielt. Die Migration läuft atomar,
|
||||
ist wiederholbar und sperrt Schreibzugriffe auf die betroffenen Tabellen für
|
||||
ihre Laufzeit. Nach Backup manuell auf der Ziel-DB ausführen:
|
||||
|
||||
```bash
|
||||
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f migrations/020_falukant_character_relation_cleanup.sql
|
||||
```
|
||||
|
||||
@@ -420,12 +420,14 @@ impl CharacterCreationWorker {
|
||||
let tr = death_ctx.as_ref().map(|c| {
|
||||
death_log::wrap_death_notification(c, "director_death", user_id)
|
||||
});
|
||||
Self::notify_user(
|
||||
if let Err(e) = Self::notify_user(
|
||||
pool,
|
||||
broker,
|
||||
user_id,
|
||||
tr.as_deref().unwrap_or("director_death"),
|
||||
)?;
|
||||
) {
|
||||
eprintln!("[CharacterCreationWorker] Todesbenachrichtigung: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Relationships löschen und betroffene User benachrichtigen
|
||||
@@ -439,12 +441,14 @@ impl CharacterCreationWorker {
|
||||
let tr = death_ctx.as_ref().map(|c| {
|
||||
death_log::wrap_death_notification(c, "relationship_death", related_user_id)
|
||||
});
|
||||
Self::notify_user(
|
||||
if let Err(e) = Self::notify_user(
|
||||
pool,
|
||||
broker,
|
||||
related_user_id,
|
||||
tr.as_deref().unwrap_or("relationship_death"),
|
||||
)?;
|
||||
) {
|
||||
eprintln!("[CharacterCreationWorker] Todesbenachrichtigung: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,12 +465,14 @@ impl CharacterCreationWorker {
|
||||
let tr = death_ctx.as_ref().map(|c| {
|
||||
death_log::wrap_death_notification(c, "child_death", father_user_id)
|
||||
});
|
||||
Self::notify_user(
|
||||
if let Err(e) = Self::notify_user(
|
||||
pool,
|
||||
broker,
|
||||
father_user_id,
|
||||
tr.as_deref().unwrap_or("child_death"),
|
||||
)?;
|
||||
) {
|
||||
eprintln!("[CharacterCreationWorker] Todesbenachrichtigung: {e}");
|
||||
}
|
||||
}
|
||||
if let Some(mother_user_id) = row
|
||||
.get("mother_user_id")
|
||||
@@ -475,12 +481,14 @@ impl CharacterCreationWorker {
|
||||
let tr = death_ctx.as_ref().map(|c| {
|
||||
death_log::wrap_death_notification(c, "child_death", mother_user_id)
|
||||
});
|
||||
Self::notify_user(
|
||||
if let Err(e) = Self::notify_user(
|
||||
pool,
|
||||
broker,
|
||||
mother_user_id,
|
||||
tr.as_deref().unwrap_or("child_death"),
|
||||
)?;
|
||||
) {
|
||||
eprintln!("[CharacterCreationWorker] Todesbenachrichtigung: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -983,7 +983,7 @@ pub const QUERY_DELETE_CHILD_RELATION: &str = r#"
|
||||
WITH deleted AS (
|
||||
DELETE FROM falukant_data.child_relation WHERE child_character_id = $1 RETURNING father_character_id, mother_character_id
|
||||
)
|
||||
SELECT cf.user_id AS father_user_id, cm.user_id AS mother_user_id FROM deleted d JOIN falukant_data.character cf ON cf.id = d.father_character_id JOIN falukant_data.character cm ON cm.id = d.mother_character_id;
|
||||
SELECT cf.user_id AS father_user_id, cm.user_id AS mother_user_id FROM deleted d LEFT JOIN falukant_data.character cf ON cf.id = d.father_character_id LEFT JOIN falukant_data.character cm ON cm.id = d.mother_character_id;
|
||||
"#;
|
||||
|
||||
/// Entfernt alle child_relation-Zeilen, in denen der Charakter Vater oder Mutter ist (nötig vor Charakter-Löschung).
|
||||
|
||||
Reference in New Issue
Block a user