Skip to content

Commit b72b836

Browse files
committed
Repository method to deactivate a user.
1 parent 6519d6c commit b72b836

File tree

4 files changed

+100
-2
lines changed

4 files changed

+100
-2
lines changed

crates/storage-pg/.sqlx/query-2f7aba76cd7df75d6a9a6d91d5ddebaedf37437f3bd4f796f5581fab997587d7.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/storage-pg/src/user/mod.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,42 @@ impl UserRepository for PgUserRepository<'_> {
330330
Ok(user)
331331
}
332332

333+
#[tracing::instrument(
334+
name = "db.user.deactivate",
335+
skip_all,
336+
fields(
337+
db.query.text,
338+
%user.id,
339+
),
340+
err,
341+
)]
342+
async fn deactivate(&mut self, clock: &dyn Clock, mut user: User) -> Result<User, Self::Error> {
343+
if user.deactivated_at.is_some() {
344+
return Ok(user);
345+
}
346+
347+
let deactivated_at = clock.now();
348+
let res = sqlx::query!(
349+
r#"
350+
UPDATE users
351+
SET deactivated_at = $2
352+
WHERE user_id = $1
353+
AND deactivated_at IS NULL
354+
"#,
355+
Uuid::from(user.id),
356+
deactivated_at,
357+
)
358+
.traced()
359+
.execute(&mut *self.conn)
360+
.await?;
361+
362+
DatabaseError::ensure_affected_rows(&res, 1)?;
363+
364+
user.deactivated_at = Some(user.created_at);
365+
366+
Ok(user)
367+
}
368+
333369
#[tracing::instrument(
334370
name = "db.user.set_can_request_admin",
335371
skip_all,

crates/storage-pg/src/user/tests.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ async fn test_user_repo(pool: PgPool) {
3333
let non_admin = all.cannot_request_admin_only();
3434
let active = all.active_only();
3535
let locked = all.locked_only();
36+
let deactivated = all.deactivated_only();
3637

3738
// Initially, the user shouldn't exist
3839
assert!(!repo.user().exists(USERNAME).await.unwrap());
@@ -49,6 +50,7 @@ async fn test_user_repo(pool: PgPool) {
4950
assert_eq!(repo.user().count(non_admin).await.unwrap(), 0);
5051
assert_eq!(repo.user().count(active).await.unwrap(), 0);
5152
assert_eq!(repo.user().count(locked).await.unwrap(), 0);
53+
assert_eq!(repo.user().count(deactivated).await.unwrap(), 0);
5254

5355
// Adding the user should work
5456
let user = repo
@@ -73,6 +75,7 @@ async fn test_user_repo(pool: PgPool) {
7375
assert_eq!(repo.user().count(non_admin).await.unwrap(), 1);
7476
assert_eq!(repo.user().count(active).await.unwrap(), 1);
7577
assert_eq!(repo.user().count(locked).await.unwrap(), 0);
78+
assert_eq!(repo.user().count(deactivated).await.unwrap(), 0);
7679

7780
// Adding a second time should give a conflict
7881
// It should not poison the transaction though
@@ -93,6 +96,7 @@ async fn test_user_repo(pool: PgPool) {
9396
assert_eq!(repo.user().count(non_admin).await.unwrap(), 1);
9497
assert_eq!(repo.user().count(active).await.unwrap(), 0);
9598
assert_eq!(repo.user().count(locked).await.unwrap(), 1);
99+
assert_eq!(repo.user().count(deactivated).await.unwrap(), 0);
96100

97101
// Check that the property is retrieved on lookup
98102
let user = repo.user().lookup(user.id).await.unwrap().unwrap();
@@ -123,6 +127,7 @@ async fn test_user_repo(pool: PgPool) {
123127
assert_eq!(repo.user().count(non_admin).await.unwrap(), 0);
124128
assert_eq!(repo.user().count(active).await.unwrap(), 1);
125129
assert_eq!(repo.user().count(locked).await.unwrap(), 0);
130+
assert_eq!(repo.user().count(deactivated).await.unwrap(), 0);
126131

127132
// Check that the property is retrieved on lookup
128133
let user = repo.user().lookup(user.id).await.unwrap().unwrap();
@@ -145,6 +150,26 @@ async fn test_user_repo(pool: PgPool) {
145150
assert_eq!(repo.user().count(non_admin).await.unwrap(), 1);
146151
assert_eq!(repo.user().count(active).await.unwrap(), 1);
147152
assert_eq!(repo.user().count(locked).await.unwrap(), 0);
153+
assert_eq!(repo.user().count(deactivated).await.unwrap(), 0);
154+
155+
// Deactivating the user should work
156+
let user = repo.user().deactivate(&clock, user).await.unwrap();
157+
assert!(user.deactivated_at.is_some());
158+
159+
// Check that the property is retrieved on lookup
160+
let user = repo.user().lookup(user.id).await.unwrap().unwrap();
161+
assert!(user.deactivated_at.is_some());
162+
163+
// Deactivating a second time should not fail
164+
let user = repo.user().deactivate(&clock, user).await.unwrap();
165+
assert!(user.deactivated_at.is_some());
166+
167+
assert_eq!(repo.user().count(all).await.unwrap(), 1);
168+
assert_eq!(repo.user().count(admin).await.unwrap(), 0);
169+
assert_eq!(repo.user().count(non_admin).await.unwrap(), 1);
170+
assert_eq!(repo.user().count(active).await.unwrap(), 0);
171+
assert_eq!(repo.user().count(locked).await.unwrap(), 0);
172+
assert_eq!(repo.user().count(deactivated).await.unwrap(), 1);
148173

149174
// Check the list method
150175
let list = repo.user().list(all, Pagination::first(10)).await.unwrap();
@@ -171,8 +196,7 @@ async fn test_user_repo(pool: PgPool) {
171196
.list(active, Pagination::first(10))
172197
.await
173198
.unwrap();
174-
assert_eq!(list.edges.len(), 1);
175-
assert_eq!(list.edges[0].id, user.id);
199+
assert_eq!(list.edges.len(), 0);
176200

177201
let list = repo
178202
.user()
@@ -181,6 +205,14 @@ async fn test_user_repo(pool: PgPool) {
181205
.unwrap();
182206
assert_eq!(list.edges.len(), 0);
183207

208+
let list = repo
209+
.user()
210+
.list(deactivated, Pagination::first(10))
211+
.await
212+
.unwrap();
213+
assert_eq!(list.edges.len(), 1);
214+
assert_eq!(list.edges[0].id, user.id);
215+
184216
repo.save().await.unwrap();
185217
}
186218

crates/storage/src/user/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,20 @@ pub trait UserRepository: Send + Sync {
228228
/// Returns [`Self::Error`] if the underlying repository fails
229229
async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
230230

231+
/// Deactivate a [`User`]
232+
///
233+
/// Returns the deactivated [`User`]
234+
///
235+
/// # Parameters
236+
///
237+
/// * `clock`: The clock used to generate timestamps
238+
/// * `user`: The [`User`] to deactivate
239+
///
240+
/// # Errors
241+
///
242+
/// Returns [`Self::Error`] if the underlying repository fails
243+
async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
244+
231245
/// Set whether a [`User`] can request admin
232246
///
233247
/// Returns the [`User`] with the new `can_request_admin` value
@@ -298,6 +312,7 @@ repository_impl!(UserRepository:
298312
async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
299313
async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
300314
async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
315+
async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
301316
async fn set_can_request_admin(
302317
&mut self,
303318
user: User,

0 commit comments

Comments
 (0)