refactor/tighten-architecture #26
@@ -45,8 +45,12 @@ pub const URL_KEYED_USER_COLUMNS: &[(&str, &str)] = &[
|
||||
];
|
||||
|
||||
/// Columns holding serialized events and activities that carry the user's id
|
||||
/// (the outbox log keeps finished rows, including authored activity JSON).
|
||||
pub const PAYLOAD_KEYED_USER_COLUMNS: &[(&str, &str)] = &[("event_queue", "payload")];
|
||||
/// (the outbox log keeps finished rows, including authored activity JSON) and
|
||||
/// the global wrap-up reports, whose `most_active_user` names a member.
|
||||
pub const PAYLOAD_KEYED_USER_COLUMNS: &[(&str, &str)] = &[
|
||||
("event_queue", "payload"),
|
||||
("wrap_up_records", "report_json"),
|
||||
];
|
||||
|
||||
pub struct SqliteAccountPurger {
|
||||
pool: SqlitePool,
|
||||
@@ -142,6 +146,19 @@ impl AccountPurger for SqliteAccountPurger {
|
||||
.await
|
||||
.map_err(map_sqlx_error)?;
|
||||
|
||||
// Global wrap-up reports name their most active member; redact it
|
||||
// (the type allows none) and keep the rest of the report.
|
||||
sqlx::query(
|
||||
"UPDATE wrap_up_records
|
||||
SET report_json = json_set(report_json, '$.most_active_user', NULL)
|
||||
WHERE user_id IS NULL AND report_json IS NOT NULL
|
||||
AND instr(report_json, ?) > 0",
|
||||
)
|
||||
.bind(&id)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(map_sqlx_error)?;
|
||||
|
||||
// Other accounts' follows of this one.
|
||||
for table in ["ap_followers", "ap_following"] {
|
||||
sqlx::query(&format!("DELETE FROM {table} WHERE remote_actor_url = ?"))
|
||||
|
||||
@@ -728,3 +728,48 @@ async fn tombstone_cleanup_removes_rows_older_than_retention_and_keeps_younger_o
|
||||
.await;
|
||||
assert_eq!(retired, 1, "the username stays retired for good");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn purge_redacts_the_user_from_global_wrap_up_reports_only() {
|
||||
let pool = migrated_pool().await;
|
||||
let parts = sqlite::wire_pool(pool.clone()).await.unwrap();
|
||||
let s = seed(&pool, &parts).await;
|
||||
let (a, b) = (s.user.value().to_string(), s.other.value().to_string());
|
||||
let report = |id: &str, name: &str| {
|
||||
format!(
|
||||
r#"{{"total_watched":3,"most_active_user":{{"user_id":"{id}","display_name":"{name}"}},"total_users_active":2}}"#
|
||||
)
|
||||
};
|
||||
for (rid, id, name) in [("g-alice", &a, "Zelda Quux"), ("g-bob", &b, "Bobby Tables")] {
|
||||
exec(
|
||||
&pool,
|
||||
"INSERT INTO wrap_up_records (id, user_id, start_date, end_date, status, report_json)
|
||||
VALUES (?, NULL, '2024-01-01', '2024-12-31', 'completed', ?)",
|
||||
&[rid, &report(id, name)],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
tombstone(&pool, &s.user).await;
|
||||
|
||||
purger(&pool).purge(&s.user).await.unwrap();
|
||||
|
||||
let all: Vec<(String, String)> =
|
||||
sqlx::query_as("SELECT id, report_json FROM wrap_up_records WHERE user_id IS NULL")
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
for (_, json) in &all {
|
||||
assert!(!json.contains(&a), "user id left in {json}");
|
||||
assert!(!json.contains("Zelda Quux"), "display name left in {json}");
|
||||
}
|
||||
let alice_row = all.iter().find(|(id, _)| id == "g-alice").unwrap();
|
||||
let v: serde_json::Value = serde_json::from_str(&alice_row.1).unwrap();
|
||||
assert!(v["most_active_user"].is_null(), "{v}");
|
||||
assert_eq!(v["total_watched"], 3, "rest of the report is kept");
|
||||
let bob_row = all.iter().find(|(id, _)| id == "g-bob").unwrap();
|
||||
assert_eq!(
|
||||
bob_row.1,
|
||||
report(&b, "Bobby Tables"),
|
||||
"other user untouched"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user