refactor: user receive notifications

refs #21
This commit is contained in:
Max Hohlfeld 2025-04-24 21:44:43 +02:00
parent 56f6b8edb4
commit 5446fcb128
8 changed files with 164 additions and 27 deletions

View File

@ -0,0 +1,8 @@
---
source: web/src/endpoints/user/put_receive_notifications.rs
expression: subscribe_body
snapshot_kind: text
---
<input
hx-put="/users/1/unsubscribeNotifications"
type="checkbox" hx-swap="outerHTML" checked>

View File

@ -0,0 +1,8 @@
---
source: web/src/endpoints/user/put_receive_notifications.rs
expression: unsubscribe_body
snapshot_kind: text
---
<input
hx-put="/users/1/subscribeNotifications"
type="checkbox" hx-swap="outerHTML" >

View File

@ -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);

View File

@ -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()
}

View File

@ -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<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
) -> Result<impl Responder, ApplicationError> {
handle_subscription_to_notifications(user, pool, path, false).await
}
#[actix_web::put("/users/{id}/subscribeNotifications")]
pub async fn put_subscribe(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
) -> Result<impl Responder, ApplicationError> {
handle_subscription_to_notifications(user, pool, path, true).await
}
async fn handle_subscription_to_notifications(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
subscription_state: bool,
) -> Result<impl Responder, ApplicationError> {
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());
}
}

View File

@ -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;

View File

@ -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<T, R, F>(
pool: &Pool<Postgres>,
app: &T,
config: &RequestConfig,
form: Option<F>,
) -> ServiceResponse<R>
where
T: Service<Request, Response = ServiceResponse<R>, 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<T, R>(
pool: &Pool<Postgres>,
app: T,

View File

@ -62,7 +62,7 @@
<div class="field-body">
<div class="field">
<div class="control">
{{ user.function|show_tree|safe }}
{{ user.function|show_tree|safe }}
</div>
</div>
</div>
@ -88,19 +88,12 @@
<div class="field">
<div class="control">
<label class="checkbox">
<input hx-post="/users/{{ user.id }}/toggle?field=receiveNotifications" type="checkbox"
hx-on::before-request="document.getElementById('success').classList.remove('fadeout')"
hx-on::after-request="document.getElementById('success').classList.add('fadeout')"
checked="{{ user.receive_notifications }}">
{% block notificationinput %}
<input
hx-put="/users/{{ user.id }}/{%- if user.receive_notifications -%}un{%- endif -%}subscribeNotifications"
type="checkbox" hx-swap="outerHTML" {{ user.receive_notifications|cond_show("checked") }}>
{% endblock %}
Ich möchte E-Mail Benachrichtigungen zu neuen Brasiwa-Einteilungen erhalten.
<span id="success" class="result">
<svg class="icon mr-2">
<use href="/static/feather-sprite.svg#check" />
</svg>
<span>
gespeichert
</span>
</span>
</label>
</div>
</div>