@@ -12,6 +12,8 @@ use mas_storage::{
1212 BoxRng ,
1313 queue:: { DeactivateUserJob , QueueJobRepositoryExt as _} ,
1414} ;
15+ use schemars:: JsonSchema ;
16+ use serde:: Deserialize ;
1517use tracing:: info;
1618use ulid:: Ulid ;
1719
@@ -49,7 +51,26 @@ impl IntoResponse for RouteError {
4951 }
5052}
5153
52- pub fn doc ( operation : TransformOperation ) -> TransformOperation {
54+ /// # JSON payload for the `POST /api/admin/v1/users/:id/deactivate` endpoint
55+ #[ derive( Default , Deserialize , JsonSchema ) ]
56+ #[ serde( rename = "DeactivateUserRequest" ) ]
57+ pub struct Request {
58+ /// Whether to skip requesting the homeserver to GDPR-erase the user upon
59+ /// deactivation.
60+ #[ serde( default ) ]
61+ skip_erase : bool ,
62+ }
63+
64+ pub fn doc ( mut operation : TransformOperation ) -> TransformOperation {
65+ operation
66+ . inner_mut ( )
67+ . request_body
68+ . as_mut ( )
69+ . unwrap ( )
70+ . as_item_mut ( )
71+ . unwrap ( )
72+ . required = false ;
73+
5374 operation
5475 . id ( "deactivateUser" )
5576 . summary ( "Deactivate a user" )
@@ -79,7 +100,9 @@ pub async fn handler(
79100 } : CallContext ,
80101 NoApi ( mut rng) : NoApi < BoxRng > ,
81102 id : UlidPathParam ,
103+ body : Option < Json < Request > > ,
82104) -> Result < Json < SingleResponse < User > > , RouteError > {
105+ let Json ( params) = body. unwrap_or_default ( ) ;
83106 let id = * id;
84107 let user = repo
85108 . user ( )
@@ -91,7 +114,11 @@ pub async fn handler(
91114
92115 info ! ( %user. id, "Scheduling deactivation of user" ) ;
93116 repo. queue_job ( )
94- . schedule_job ( & mut rng, & clock, DeactivateUserJob :: new ( & user, true ) )
117+ . schedule_job (
118+ & mut rng,
119+ & clock,
120+ DeactivateUserJob :: new ( & user, !params. skip_erase ) ,
121+ )
95122 . await ?;
96123
97124 repo. save ( ) . await ?;
@@ -106,14 +133,13 @@ pub async fn handler(
106133mod tests {
107134 use chrono:: Duration ;
108135 use hyper:: { Request , StatusCode } ;
109- use insta:: assert_json_snapshot;
136+ use insta:: { allow_duplicates , assert_json_snapshot} ;
110137 use mas_storage:: { Clock , RepositoryAccess , user:: UserRepository } ;
111- use sqlx:: PgPool ;
138+ use sqlx:: { PgPool , types :: Json } ;
112139
113140 use crate :: test_utils:: { RequestBuilderExt , ResponseExt , TestState , setup} ;
114141
115- #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
116- async fn test_deactivate_user ( pool : PgPool ) {
142+ async fn test_deactivate_user_helper ( pool : PgPool , skip_erase : Option < bool > ) {
117143 setup ( ) ;
118144 let mut state = TestState :: from_pool ( pool. clone ( ) ) . await . unwrap ( ) ;
119145 let token = state. token_with_scope ( "urn:mas:admin" ) . await ;
@@ -126,9 +152,14 @@ mod tests {
126152 . unwrap ( ) ;
127153 repo. save ( ) . await . unwrap ( ) ;
128154
129- let request = Request :: post ( format ! ( "/api/admin/v1/users/{}/deactivate" , user. id) )
130- . bearer ( & token)
131- . empty ( ) ;
155+ let request =
156+ Request :: post ( format ! ( "/api/admin/v1/users/{}/deactivate" , user. id) ) . bearer ( & token) ;
157+ let request = match skip_erase {
158+ None => request. empty ( ) ,
159+ Some ( skip_erase) => request. json ( serde_json:: json!( {
160+ "skip_erase" : skip_erase,
161+ } ) ) ,
162+ } ;
132163 let response = state. request ( request) . await ;
133164 response. assert_status ( StatusCode :: OK ) ;
134165 let body: serde_json:: Value = response. json ( ) ;
@@ -145,6 +176,20 @@ mod tests {
145176 serde_json:: Value :: Null
146177 ) ;
147178
179+ // It should have scheduled a deactivation job for the user
180+ // XXX: we don't have a good way to look for the deactivation job
181+ let job: Json < serde_json:: Value > = sqlx:: query_scalar (
182+ "SELECT payload FROM queue_jobs WHERE queue_name = 'deactivate-user'" ,
183+ )
184+ . fetch_one ( & pool)
185+ . await
186+ . expect ( "Deactivation job to be scheduled" ) ;
187+ assert_eq ! ( job[ "user_id" ] , serde_json:: json!( user. id) ) ;
188+ assert_eq ! (
189+ job[ "hs_erase" ] ,
190+ serde_json:: json!( !skip_erase. unwrap_or( false ) )
191+ ) ;
192+
148193 // Make sure to run the jobs in the queue
149194 state. run_jobs_in_queue ( ) . await ;
150195
@@ -155,7 +200,7 @@ mod tests {
155200 response. assert_status ( StatusCode :: OK ) ;
156201 let body: serde_json:: Value = response. json ( ) ;
157202
158- assert_json_snapshot ! ( body, @r#"
203+ allow_duplicates ! ( assert_json_snapshot!( body, @r#"
159204 {
160205 "data": {
161206 "type": "user",
@@ -175,7 +220,17 @@ mod tests {
175220 "self": "/api/admin/v1/users/01FSHN9AG0MZAA6S4AF7CTV32E"
176221 }
177222 }
178- "# ) ;
223+ "# ) ) ;
224+ }
225+
226+ #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
227+ async fn test_deactivate_user ( pool : PgPool ) {
228+ test_deactivate_user_helper ( pool, Option :: None ) . await ;
229+ }
230+
231+ #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
232+ async fn test_deactivate_user_skip_erase ( pool : PgPool ) {
233+ test_deactivate_user_helper ( pool, Option :: Some ( true ) ) . await ;
179234 }
180235
181236 #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
0 commit comments