@@ -12,6 +12,8 @@ use mas_storage::{
12
12
BoxRng ,
13
13
queue:: { DeactivateUserJob , QueueJobRepositoryExt as _} ,
14
14
} ;
15
+ use schemars:: JsonSchema ;
16
+ use serde:: Deserialize ;
15
17
use tracing:: info;
16
18
use ulid:: Ulid ;
17
19
@@ -49,7 +51,26 @@ impl IntoResponse for RouteError {
49
51
}
50
52
}
51
53
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
+
53
74
operation
54
75
. id ( "deactivateUser" )
55
76
. summary ( "Deactivate a user" )
@@ -79,7 +100,9 @@ pub async fn handler(
79
100
} : CallContext ,
80
101
NoApi ( mut rng) : NoApi < BoxRng > ,
81
102
id : UlidPathParam ,
103
+ body : Option < Json < Request > > ,
82
104
) -> Result < Json < SingleResponse < User > > , RouteError > {
105
+ let Json ( params) = body. unwrap_or_default ( ) ;
83
106
let id = * id;
84
107
let user = repo
85
108
. user ( )
@@ -91,7 +114,11 @@ pub async fn handler(
91
114
92
115
info ! ( %user. id, "Scheduling deactivation of user" ) ;
93
116
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
+ )
95
122
. await ?;
96
123
97
124
repo. save ( ) . await ?;
@@ -106,14 +133,13 @@ pub async fn handler(
106
133
mod tests {
107
134
use chrono:: Duration ;
108
135
use hyper:: { Request , StatusCode } ;
109
- use insta:: assert_json_snapshot;
136
+ use insta:: { allow_duplicates , assert_json_snapshot} ;
110
137
use mas_storage:: { Clock , RepositoryAccess , user:: UserRepository } ;
111
- use sqlx:: PgPool ;
138
+ use sqlx:: { PgPool , types :: Json } ;
112
139
113
140
use crate :: test_utils:: { RequestBuilderExt , ResponseExt , TestState , setup} ;
114
141
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 > ) {
117
143
setup ( ) ;
118
144
let mut state = TestState :: from_pool ( pool. clone ( ) ) . await . unwrap ( ) ;
119
145
let token = state. token_with_scope ( "urn:mas:admin" ) . await ;
@@ -126,9 +152,14 @@ mod tests {
126
152
. unwrap ( ) ;
127
153
repo. save ( ) . await . unwrap ( ) ;
128
154
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
+ } ;
132
163
let response = state. request ( request) . await ;
133
164
response. assert_status ( StatusCode :: OK ) ;
134
165
let body: serde_json:: Value = response. json ( ) ;
@@ -145,6 +176,20 @@ mod tests {
145
176
serde_json:: Value :: Null
146
177
) ;
147
178
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
+
148
193
// Make sure to run the jobs in the queue
149
194
state. run_jobs_in_queue ( ) . await ;
150
195
@@ -155,7 +200,7 @@ mod tests {
155
200
response. assert_status ( StatusCode :: OK ) ;
156
201
let body: serde_json:: Value = response. json ( ) ;
157
202
158
- assert_json_snapshot ! ( body, @r#"
203
+ allow_duplicates ! ( assert_json_snapshot!( body, @r#"
159
204
{
160
205
"data": {
161
206
"type": "user",
@@ -175,7 +220,17 @@ mod tests {
175
220
"self": "/api/admin/v1/users/01FSHN9AG0MZAA6S4AF7CTV32E"
176
221
}
177
222
}
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 ;
179
234
}
180
235
181
236
#[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
0 commit comments