[Fix] User deletion & migration

This commit is contained in:
Alex 2026-08-09 02:51:47 +02:00
commit 7dc98ef29b
Signed by: alex
SSH key fingerprint: SHA256:D1+Ub8o0v4K5y1JNivW8IxEOelqLSvPmUzBbDIoZkRQ
20 changed files with 742 additions and 129 deletions

View file

@ -251,6 +251,21 @@ fn run_migrations_on_connection(conn: &Connection) -> Result<(), StorageError> {
)?;
}
if current_version < 7 {
conn.execute_batch(
r#"
CREATE TABLE IF NOT EXISTS user_residency (
user_id INTEGER PRIMARY KEY,
username TEXT NOT NULL,
lifecycle_state TEXT NOT NULL CHECK (lifecycle_state IN ('managed', 'released')),
data_state TEXT NOT NULL CHECK (data_state IN ('present', 'empty')),
updated_at INTEGER NOT NULL
);
PRAGMA user_version = 7;
"#,
)?;
}
Ok(())
}
@ -320,7 +335,7 @@ mod tests {
run_migrations_on_connection(&conn)?;
let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
assert_eq!(version, 6);
assert_eq!(version, 7);
for column in ["height", "reply_to", "edited_count", "deleted_by_external"] {
let mut statement =
conn.prepare("SELECT 1 FROM pragma_table_info('messages') WHERE name = ?1")?;
@ -337,8 +352,8 @@ mod tests {
run_migrations_on_connection(&conn)?;
run_migrations_on_connection(&conn)?;
let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
assert_eq!(version, 6);
for table in ["sync_heads", "sync_events", "client_sync_state"] {
assert_eq!(version, 7);
for table in ["sync_heads", "sync_events", "client_sync_state", "user_residency"] {
let exists: i64 = conn.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
[table],

View file

@ -188,6 +188,23 @@ pub fn delete_pending_chat_secret_forward(
})
}
/// Erase every E2EE record owned by, or queued for, a user. The operation is
/// intentionally idempotent so it can be retried after an interrupted remote
/// erasure request.
pub fn purge_user(user_id: i64) -> Result<(), StorageError> {
let user_id = user_id.to_string();
db::with_conn(&E2EE_DB, |conn| {
let tx = conn.unchecked_transaction()?;
tx.execute("DELETE FROM chat_secrets WHERE user_id = ?1", params![user_id])?;
tx.execute(
"DELETE FROM pending_chat_secret_forwards WHERE recipient_user_id = ?1 OR sender_user_id = ?1",
params![user_id],
)?;
tx.commit()?;
Ok(())
})
}
pub fn get_chat_secret(query: ChatSecretQuery) -> Result<Option<StoredChatSecret>, StorageError> {
if query.user_id.is_empty() || query.chat_id.is_empty() {
return Ok(None);