feat: account deletion request and active-user check

This commit is contained in:
2026-10-26 02:11:44 +01:00
parent 83bb5d9847
commit 0b5d18c4ee
27 changed files with 577 additions and 151 deletions

View File

@@ -19,6 +19,9 @@ pub enum DomainError {
#[error("Forbidden: {0}")]
Forbidden(String),
#[error("Conflict: {0}")]
Conflict(String),
}
impl DomainError {

View File

@@ -78,13 +78,18 @@ impl UserRepository for InMemoryStore {
events: &[crate::events::DomainEvent],
) -> Result<(), DomainError> {
let id = user_id.value();
if let Some(user) = self.users.read().unwrap().get(&id) {
self.retired_usernames
.write()
.unwrap()
.insert(user.username().value().to_lowercase());
self.tombstoned.write().unwrap().insert(id);
}
let users = self.users.read().unwrap();
let mut gone = self.tombstoned.write().unwrap();
let Some(user) = users.get(&id).filter(|_| !gone.contains(&id)) else {
return Err(DomainError::NotFound("User not found".into()));
};
self.retired_usernames
.write()
.unwrap()
.insert(user.username().value().to_lowercase());
gone.insert(id);
drop(gone);
drop(users);
self.refresh_sessions
.write()
.unwrap()

View File

@@ -628,3 +628,33 @@ async fn fake_mark_deleted_mirrors_the_sqlite_tombstone() {
assert_eq!(store.refresh_session_count(), 0);
assert_eq!(store.recorded_events().len(), 1);
}
#[tokio::test]
async fn fake_mark_deleted_refuses_unknown_and_already_deleted_users() {
let store = InMemoryStore::new();
let user = test_user("ghost");
UserRepository::save(&*store, &user).await.unwrap();
let event = domain::events::DomainEvent::UserDeleted {
user_id: user.id().clone(),
};
let unknown = store
.mark_deleted(&UserId::generate(), std::slice::from_ref(&event))
.await;
assert!(matches!(unknown, Err(DomainError::NotFound(_))));
assert!(store.recorded_events().is_empty());
store
.mark_deleted(user.id(), std::slice::from_ref(&event))
.await
.unwrap();
let again = store
.mark_deleted(user.id(), std::slice::from_ref(&event))
.await;
assert!(matches!(again, Err(DomainError::NotFound(_))));
assert_eq!(
store.recorded_events().len(),
1,
"second call records nothing"
);
}