feat: account deletion request and active-user check
This commit is contained in:
@@ -26,6 +26,8 @@ pub fn create_image_ref(pool: SqlitePool) -> (Arc<dyn ImageRefCommand>, Arc<dyn
|
||||
|
||||
#[async_trait]
|
||||
impl ImageRefCommand for SqliteImageRefAdapter {
|
||||
// Tombstoned users (`deleted_at` set) are intentionally included: their
|
||||
// avatar stays referenced, and so protected from GC, until the purge.
|
||||
async fn swap(&self, old_key: &str, new_key: &str) -> Result<(), DomainError> {
|
||||
let mut tx = self
|
||||
.pool
|
||||
@@ -52,6 +54,8 @@ impl ImageRefCommand for SqliteImageRefAdapter {
|
||||
|
||||
#[async_trait]
|
||||
impl ImageRefQuery for SqliteImageRefAdapter {
|
||||
// Tombstoned users are intentionally visible here until the purge removes
|
||||
// the row, so their avatar is not garbage-collected early.
|
||||
async fn list_keys(&self) -> Result<Vec<String>, DomainError> {
|
||||
let rows: Vec<(String,)> = sqlx::query_as(
|
||||
"SELECT avatar_path FROM users WHERE avatar_path IS NOT NULL
|
||||
|
||||
@@ -171,7 +171,7 @@ impl UserRepository for SqliteUserRepository {
|
||||
.map_err(crate::shared::map_sqlx_error)?;
|
||||
let stamped =
|
||||
sqlx::query("UPDATE users SET deleted_at = ? WHERE id = ? AND deleted_at IS NULL")
|
||||
.bind(Utc::now().to_rfc3339())
|
||||
.bind(Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string())
|
||||
.bind(&id_str)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
|
||||
@@ -288,3 +288,40 @@ async fn follow_lists_omit_deleted_local_users() {
|
||||
|a| a.identity == SocialIdentity::Local(domain::value_objects::UserId::from_uuid(gone))
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn follower_lists_omit_deleted_local_users() {
|
||||
let pool = test_pool().await;
|
||||
let viewer = uuid::Uuid::new_v4();
|
||||
let gone = uuid::Uuid::new_v4();
|
||||
let live = uuid::Uuid::new_v4();
|
||||
sqlx::query(
|
||||
"INSERT INTO users (id, username, deleted_at) VALUES (?1, 'gone', '2026-01-01T00:00:00Z'), (?2, 'live', NULL)",
|
||||
)
|
||||
.bind(gone.to_string())
|
||||
.bind(live.to_string())
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"INSERT INTO ap_followers (local_user_id, remote_actor_url, follow_activity_id, status)
|
||||
VALUES (?1, 'https://md.example/users/' || ?2, '', 'accepted'),
|
||||
(?1, 'https://md.example/users/' || ?3, '', 'accepted'),
|
||||
(?1, 'https://other.example/users/remote', '', 'accepted')",
|
||||
)
|
||||
.bind(viewer.to_string())
|
||||
.bind(gone.to_string())
|
||||
.bind(live.to_string())
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let actors = FollowQuery::get_followers(&repo(pool), viewer)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(actors.len(), 2, "deleted local follower must not be listed");
|
||||
assert!(!actors.iter().any(
|
||||
|a| a.identity == SocialIdentity::Local(domain::value_objects::UserId::from_uuid(gone))
|
||||
));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user