use actix_web::{web, HttpResponse, Responder}; use askama::Template; use sqlx::PgPool; use crate::{ endpoints::IdPath, filters, models::{Role, User}, utils::ApplicationError, }; #[derive(Template, Debug)] #[template(path = "user/overview_locked_td.html")] struct OverviewPartialLockedTemplate { u: User, is_oob: bool, } #[derive(Template, Debug)] #[template(path = "user/overview_buttons_td.html")] struct OverviewPartialButtonsTemplate { u: User, } #[actix_web::put("/users/{id}/lock")] pub async fn put_lock_user( user: web::ReqData, pool: web::Data, path: web::Path, ) -> Result { handle_lock_state_for_user(user, pool, path, true).await } #[actix_web::put("/users/{id}/unlock")] pub async fn put_unlock_user( user: web::ReqData, pool: web::Data, path: web::Path, ) -> Result { handle_lock_state_for_user(user, pool, path, false).await } async fn handle_lock_state_for_user( user: web::ReqData, pool: web::Data, path: web::Path, lock_state: bool, ) -> Result { if user.role != Role::AreaManager && user.role != Role::Admin { return Err(ApplicationError::Unauthorized); } if user.id == path.id { return Ok(HttpResponse::BadRequest().finish()); } let Some(mut user_in_db) = User::read_by_id(pool.get_ref(), path.id).await? else { return Ok(HttpResponse::NotFound().finish()); }; if user.role == Role::AreaManager && (user.area_id != user_in_db.area_id || user_in_db.role == Role::Admin) { return Err(ApplicationError::Unauthorized); } if user_in_db.locked != lock_state { User::update_locked(pool.get_ref(), user_in_db.id, lock_state).await?; user_in_db.locked = lock_state; } let buttons = OverviewPartialButtonsTemplate { u: user_in_db.clone(), }; let locked_oob = OverviewPartialLockedTemplate { u: user_in_db, is_oob: true, }; let mut body = buttons.render()?; body.push_str(&locked_oob.render()?); Ok(HttpResponse::Ok().body(body)) } #[cfg(test)] mod tests { use crate::{ models::{Area, Function, Role, User}, utils::test_helper::{ assert_snapshot, read_body, test_put, DbTestContext, RequestConfig, StatusCode, }, }; use brass_macros::db_test; use fake::{Fake, Faker}; #[db_test] async fn admin_can_lock_and_unlock_user(context: &DbTestContext) { let app = context.app().await; User::create(&context.db_pool, Faker.fake()).await.unwrap(); let lock_config = RequestConfig { uri: "/users/1/lock".to_string(), role: Role::Admin, function: vec![Function::Posten], user_area: 1, }; let lock_response = test_put::<_, _, String>(&context.db_pool, &app, &lock_config, None).await; assert_eq!(StatusCode::OK, lock_response.status()); let lock_body = read_body(lock_response).await; assert_snapshot!(lock_body); let unlock_config = RequestConfig { uri: "/users/1/unlock".to_string(), role: Role::Admin, function: vec![Function::Posten], user_area: 1, }; let unlock_response = test_put::<_, _, String>(&context.db_pool, &app, &unlock_config, None).await; assert_eq!(StatusCode::OK, unlock_response.status()); let unlock_body = read_body(unlock_response).await; assert_snapshot!(unlock_body); } #[db_test] async fn area_manager_cant_lock_outside_of_his_area(context: &DbTestContext) { let app = context.app().await; Area::create(&context.db_pool, "Bereich 2").await.unwrap(); User::create(&context.db_pool, Faker.fake()).await.unwrap(); let config = RequestConfig { uri: "/users/1/lock".to_string(), role: Role::AreaManager, function: vec![Function::Posten], user_area: 2, }; let response = test_put::<_, _, String>(&context.db_pool, &app, &config, None).await; assert_eq!(StatusCode::UNAUTHORIZED, response.status()) } #[db_test] async fn one_cant_lock_oneself(context: &DbTestContext) { let app = context.app().await; let config = RequestConfig { uri: "/users/1/lock".to_string(), role: Role::Admin, function: vec![Function::Posten], user_area: 1, }; let response = test_put::<_, _, String>(&context.db_pool, &app, &config, None).await; assert_eq!(StatusCode::BAD_REQUEST, response.status()) } #[db_test] async fn one_cant_lock_non_existing_user(context: &DbTestContext) { let app = context.app().await; let config = RequestConfig { uri: "/users/30/lock".to_string(), role: Role::Admin, function: vec![Function::Posten], user_area: 1, }; let response = test_put::<_, _, String>(&context.db_pool, &app, &config, None).await; assert_eq!(StatusCode::NOT_FOUND, response.status()) } }