From 5446fcb128b2a3710631df493815f1d7b04e1c43 Mon Sep 17 00:00:00 2001 From: Max Hohlfeld Date: Thu, 24 Apr 2025 21:44:43 +0200 Subject: [PATCH] refactor: user receive notifications refs #21 --- ...can_toggle_subscription_for_himself-2.snap | 8 ++ ...r_can_toggle_subscription_for_himself.snap | 8 ++ web/src/endpoints/mod.rs | 2 + web/src/endpoints/user/post_toggle.rs | 11 -- .../user/put_receive_notifications.rs | 115 ++++++++++++++++++ web/src/utils/test_helper/mod.rs | 4 +- web/src/utils/test_helper/test_requests.rs | 24 +++- web/templates/user/profile.html | 19 +-- 8 files changed, 164 insertions(+), 27 deletions(-) create mode 100644 web/snapshots/brass_web__endpoints__user__put_receive_notifications__tests__inner_user_can_toggle_subscription_for_himself-2.snap create mode 100644 web/snapshots/brass_web__endpoints__user__put_receive_notifications__tests__inner_user_can_toggle_subscription_for_himself.snap create mode 100644 web/src/endpoints/user/put_receive_notifications.rs diff --git a/web/snapshots/brass_web__endpoints__user__put_receive_notifications__tests__inner_user_can_toggle_subscription_for_himself-2.snap b/web/snapshots/brass_web__endpoints__user__put_receive_notifications__tests__inner_user_can_toggle_subscription_for_himself-2.snap new file mode 100644 index 00000000..ad89282d --- /dev/null +++ b/web/snapshots/brass_web__endpoints__user__put_receive_notifications__tests__inner_user_can_toggle_subscription_for_himself-2.snap @@ -0,0 +1,8 @@ +--- +source: web/src/endpoints/user/put_receive_notifications.rs +expression: subscribe_body +snapshot_kind: text +--- + diff --git a/web/snapshots/brass_web__endpoints__user__put_receive_notifications__tests__inner_user_can_toggle_subscription_for_himself.snap b/web/snapshots/brass_web__endpoints__user__put_receive_notifications__tests__inner_user_can_toggle_subscription_for_himself.snap new file mode 100644 index 00000000..deedb6f7 --- /dev/null +++ b/web/snapshots/brass_web__endpoints__user__put_receive_notifications__tests__inner_user_can_toggle_subscription_for_himself.snap @@ -0,0 +1,8 @@ +--- +source: web/src/endpoints/user/put_receive_notifications.rs +expression: unsubscribe_body +snapshot_kind: text +--- + diff --git a/web/src/endpoints/mod.rs b/web/src/endpoints/mod.rs index afe8c33a..802811a2 100644 --- a/web/src/endpoints/mod.rs +++ b/web/src/endpoints/mod.rs @@ -49,6 +49,8 @@ pub fn init(cfg: &mut ServiceConfig) { cfg.service(user::get_register::get); cfg.service(user::post_register::post); cfg.service(user::post_resend_registration::post); + cfg.service(user::put_receive_notifications::put_subscribe); + cfg.service(user::put_receive_notifications::put_unsubscribe); cfg.service(availability::delete::delete); cfg.service(availability::get_new::get); diff --git a/web/src/endpoints/user/post_toggle.rs b/web/src/endpoints/user/post_toggle.rs index d0262f75..c3496cfb 100644 --- a/web/src/endpoints/user/post_toggle.rs +++ b/web/src/endpoints/user/post_toggle.rs @@ -69,17 +69,6 @@ pub async fn post( id = user.id)); } } - "receiveNotifications" => { - User::update_receive_notifications( - pool.get_ref(), - user.id, - !user.receive_notifications, - ) - .await - .unwrap(); - } _ => return HttpResponse::BadRequest().body("Other PATCH paths are not supported!"), }; - - HttpResponse::Ok().finish() } diff --git a/web/src/endpoints/user/put_receive_notifications.rs b/web/src/endpoints/user/put_receive_notifications.rs new file mode 100644 index 00000000..bd442241 --- /dev/null +++ b/web/src/endpoints/user/put_receive_notifications.rs @@ -0,0 +1,115 @@ +use actix_web::{web, HttpResponse, Responder}; +use askama::Template; +use serde_json::json; +use sqlx::PgPool; +use tracing::error; + +use crate::{ + endpoints::IdPath, + filters, + models::User, + utils::{ApplicationError, TemplateResponse}, +}; + +#[derive(Template)] +#[template(path = "user/profile.html", block = "notificationinput")] +struct ProfilePartialNotificationInputTemplate { + user: User, +} + +#[actix_web::put("/users/{id}/unsubscribeNotifications")] +pub async fn put_unsubscribe( + user: web::ReqData, + pool: web::Data, + path: web::Path, +) -> Result { + handle_subscription_to_notifications(user, pool, path, false).await +} + +#[actix_web::put("/users/{id}/subscribeNotifications")] +pub async fn put_subscribe( + user: web::ReqData, + pool: web::Data, + path: web::Path, +) -> Result { + handle_subscription_to_notifications(user, pool, path, true).await +} + +async fn handle_subscription_to_notifications( + user: web::ReqData, + pool: web::Data, + path: web::Path, + subscription_state: bool, +) -> Result { + if user.id != path.id { + return Err(ApplicationError::Unauthorized); + } + + let Some(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await? else { + error!(user = user.id, "Logged in user does not exist in database!"); + return Ok(HttpResponse::NotFound().finish()); + }; + + let mut user = user.into_inner(); + if user_in_db.receive_notifications != subscription_state { + User::update_receive_notifications(pool.get_ref(), user_in_db.id, subscription_state) + .await?; + user.receive_notifications = subscription_state; + } + + let trigger = json!({ + "showToast": { + "type": "success", + "message": "Benachrichtigungseinstellung gespeichert!" + } + }) + .to_string(); + + let template = ProfilePartialNotificationInputTemplate { user }; + + Ok(HttpResponse::Ok() + .insert_header(("HX-TRIGGER", trigger)) + .body(template.to_response()?.into_body())) +} + +#[cfg(test)] +mod tests { + use crate::utils::test_helper::{ + assert_snapshot, read_body, test_put, DbTestContext, RequestConfig, StatusCode, + }; + use brass_macros::db_test; + + #[db_test] + async fn user_can_toggle_subscription_for_himself(context: &DbTestContext) { + let app = context.app().await; + + let unsubscribe_config = RequestConfig::new("/users/1/unsubscribeNotifications"); + let unsubscribe_response = + test_put::<_, _, String>(&context.db_pool, &app, &unsubscribe_config, None).await; + + assert_eq!(StatusCode::OK, unsubscribe_response.status()); + + let unsubscribe_body = read_body(unsubscribe_response).await; + assert_snapshot!(unsubscribe_body); + + let subscribe_config = RequestConfig::new("/users/1/subscribeNotifications"); + let subscribe_response = + test_put::<_, _, String>(&context.db_pool, &app, &subscribe_config, None).await; + + assert_eq!(StatusCode::OK, subscribe_response.status()); + + let subscribe_body = read_body(subscribe_response).await; + assert_snapshot!(subscribe_body); + } + + #[db_test] + async fn user_cant_toggle_subscription_for_someone_else(context: &DbTestContext) { + let app = context.app().await; + + let unsubscribe_config = RequestConfig::new("/users/3/unsubscribeNotifications"); + let unsubscribe_response = + test_put::<_, _, String>(&context.db_pool, &app, &unsubscribe_config, None).await; + + assert_eq!(StatusCode::UNAUTHORIZED, unsubscribe_response.status()); + } +} diff --git a/web/src/utils/test_helper/mod.rs b/web/src/utils/test_helper/mod.rs index 7d703e2a..27b44174 100644 --- a/web/src/utils/test_helper/mod.rs +++ b/web/src/utils/test_helper/mod.rs @@ -2,7 +2,7 @@ mod test_context; mod test_requests; pub use test_context::{setup, teardown, DbTestContext}; pub use test_requests::RequestConfig; -pub use test_requests::{read_body, test_delete, test_get, test_post}; +pub use test_requests::{read_body, test_delete, test_get, test_post, test_put}; pub use actix_http::StatusCode; @@ -25,5 +25,5 @@ macro_rules! assert_mail_snapshot { }; } -pub(crate) use assert_snapshot; pub(crate) use assert_mail_snapshot; +pub(crate) use assert_snapshot; diff --git a/web/src/utils/test_helper/test_requests.rs b/web/src/utils/test_helper/test_requests.rs index edcab4a9..860d75e8 100644 --- a/web/src/utils/test_helper/test_requests.rs +++ b/web/src/utils/test_helper/test_requests.rs @@ -59,7 +59,7 @@ where let login_form = LoginForm { email: "abc".to_string(), password: "abc".to_string(), - next: None + next: None, }; let login_req = test::TestRequest::post() @@ -119,6 +119,28 @@ where test::call_service(&app, post_request).await } +pub async fn test_put( + pool: &Pool, + app: &T, + config: &RequestConfig, + form: Option, +) -> ServiceResponse +where + T: Service, Error = Error>, + R: MessageBody, + F: Serialize, +{ + let cookie = create_user_and_get_login_cookie(pool, app, config).await; + + let put_request = test::TestRequest::put() + .uri(&config.uri) + .cookie(cookie) + .set_form(form) + .to_request(); + + test::call_service(app, put_request).await +} + pub async fn test_delete( pool: &Pool, app: T, diff --git a/web/templates/user/profile.html b/web/templates/user/profile.html index 4d382dff..86c25082 100644 --- a/web/templates/user/profile.html +++ b/web/templates/user/profile.html @@ -62,7 +62,7 @@
- {{ user.function|show_tree|safe }} + {{ user.function|show_tree|safe }}
@@ -88,19 +88,12 @@