feat: toggle cross areal
This commit is contained in:
parent
5063933f33
commit
290e610058
15
db/.sqlx/query-5f9e0e2f5037716e03409d751a32a64a2ae7393109c6cf8d46d992dc2c1f4713.json
generated
Normal file
15
db/.sqlx/query-5f9e0e2f5037716e03409d751a32a64a2ae7393109c6cf8d46d992dc2c1f4713.json
generated
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE availability SET crossAreal = $1 WHERE id = $2;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5f9e0e2f5037716e03409d751a32a64a2ae7393109c6cf8d46d992dc2c1f4713"
|
||||
}
|
@ -331,6 +331,17 @@ impl Availability {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_cross_areal(pool: &PgPool, id: i32, cross_areal: bool) -> Result<()> {
|
||||
query!(
|
||||
"UPDATE availability SET crossAreal = $1 WHERE id = $2;",
|
||||
cross_areal,
|
||||
id
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(pool: &PgPool, id: i32) -> Result<()> {
|
||||
query!("DELETE FROM availability WHERE id = $1", id)
|
||||
.execute(pool)
|
||||
|
@ -16,6 +16,7 @@ pub mod get_overview;
|
||||
pub mod get_update;
|
||||
pub mod post_new;
|
||||
pub mod post_update;
|
||||
pub mod put_cross_areal;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "availability/new_or_edit.html")]
|
||||
@ -30,7 +31,7 @@ struct NewOrEditAvailabilityTemplate<'a> {
|
||||
slot_suggestions: Vec<(NaiveDateTime, NaiveDateTime)>,
|
||||
datetomorrow: NaiveDate,
|
||||
other_user: Option<i32>,
|
||||
other_users: Vec<User>
|
||||
other_users: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -40,5 +41,5 @@ pub struct AvailabilityForm {
|
||||
pub starttime: NaiveTime,
|
||||
pub endtime: NaiveTime,
|
||||
pub comment: Option<String>,
|
||||
pub user: Option<i32>
|
||||
pub user: Option<i32>,
|
||||
}
|
||||
|
122
web/src/endpoints/availability/put_cross_areal.rs
Normal file
122
web/src/endpoints/availability/put_cross_areal.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use askama::Template;
|
||||
use serde_json::json;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
endpoints::IdPath,
|
||||
utils::{ApplicationError, TemplateResponse},
|
||||
};
|
||||
use brass_db::models::{Assignment, Availability, Role, User};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "calendar_cross_areal_button.html")]
|
||||
struct CalendarPartialCrossArealButtonTemplate {
|
||||
availability: Availability,
|
||||
}
|
||||
|
||||
#[actix_web::put("/availability/{id}/makeNonCrossAreal")]
|
||||
pub async fn put_non_cross_areal(
|
||||
user: web::ReqData<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
path: web::Path<IdPath>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
handle_cross_areal(user, pool, path, false).await
|
||||
}
|
||||
|
||||
#[actix_web::put("/availability/{id}/makeCrossAreal")]
|
||||
pub async fn put_cross_areal(
|
||||
user: web::ReqData<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
path: web::Path<IdPath>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
handle_cross_areal(user, pool, path, true).await
|
||||
}
|
||||
|
||||
async fn handle_cross_areal(
|
||||
user: web::ReqData<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
path: web::Path<IdPath>,
|
||||
cross_areal: bool,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
if user.role != Role::Admin && user.role != Role::AreaManager {
|
||||
return Err(ApplicationError::Unauthorized);
|
||||
}
|
||||
|
||||
let Some(mut availability) = Availability::read_including_user(pool.get_ref(), path.id).await?
|
||||
else {
|
||||
return Ok(HttpResponse::NotFound().finish());
|
||||
};
|
||||
|
||||
if user.role == Role::AreaManager && availability.user.as_ref().unwrap().area_id != user.area_id
|
||||
{
|
||||
return Err(ApplicationError::Unauthorized);
|
||||
}
|
||||
|
||||
let assignments_for_availability =
|
||||
Assignment::read_all_by_availability(pool.get_ref(), availability.id).await?;
|
||||
|
||||
if !cross_areal && assignments_for_availability.len() != 0 {
|
||||
let trigger = json!({
|
||||
"showToast": {
|
||||
"type": "danger",
|
||||
"message": "Verfügbarkeit bereits verplant!"
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
return Ok(HttpResponse::UnprocessableEntity()
|
||||
.insert_header(("HX-TRIGGER", trigger))
|
||||
.finish());
|
||||
}
|
||||
|
||||
if availability.cross_areal != cross_areal {
|
||||
Availability::update_cross_areal(pool.get_ref(), availability.id, cross_areal).await?;
|
||||
availability.cross_areal = cross_areal;
|
||||
}
|
||||
|
||||
let template = CalendarPartialCrossArealButtonTemplate { availability };
|
||||
Ok(template.to_response()?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// use crate::utils::test_helper::{
|
||||
// assert_snapshot, create_test_login_user, 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");
|
||||
// create_test_login_user(&context.db_pool, &unsubscribe_config).await;
|
||||
// let unsubscribe_response = test_put::<_, _, String>(&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>(&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");
|
||||
// create_test_login_user(&context.db_pool, &unsubscribe_config).await;
|
||||
// let unsubscribe_response = test_put::<_, _, String>(&app, &unsubscribe_config, None).await;
|
||||
//
|
||||
// assert_eq!(StatusCode::UNAUTHORIZED, unsubscribe_response.status());
|
||||
// }
|
||||
}
|
@ -62,6 +62,8 @@ pub fn init(cfg: &mut ServiceConfig) {
|
||||
cfg.service(availability::post_new::post);
|
||||
cfg.service(availability::post_update::post);
|
||||
cfg.service(availability::get_overview::get);
|
||||
cfg.service(availability::put_cross_areal::put_cross_areal);
|
||||
cfg.service(availability::put_cross_areal::put_non_cross_areal);
|
||||
|
||||
cfg.service(events::put_cancelation::put_cancel);
|
||||
cfg.service(events::put_cancelation::put_uncancel);
|
||||
|
@ -312,7 +312,7 @@
|
||||
{{ availability.end|fmt_datetime(DayMonthYearHourMinute) }} Uhr
|
||||
</td>
|
||||
<td>
|
||||
{{ availability.comment.as_deref().unwrap_or("") }}
|
||||
{{ availability.comment.as_deref().unwrap_or_default() }}
|
||||
</td>
|
||||
<td>
|
||||
{% if availability.user_id == user.id || user.role == Role::Admin || user.role == Role::AreaManager %}
|
||||
@ -324,11 +324,16 @@
|
||||
</svg>
|
||||
</a>
|
||||
<button class="button is-danger is-light" hx-delete="/availability/delete/{{ availability.id }}"
|
||||
hx-target="closest tr" hx-swap="delete" hx-trigger="confirmed" title="Verfügbarkeit löschen">
|
||||
hx-target="closest tr" hx-swap="delete" hx-trigger="confirmed"
|
||||
title="Verfügbarkeit löschen">
|
||||
<svg class="icon">
|
||||
<use href="/static/feather-sprite.svg#x-circle" />
|
||||
</svg>
|
||||
</button>
|
||||
{% if user.role == Role::Admin || (user.role == Role::AreaManager && user.area_id ==
|
||||
availability.user.as_ref().unwrap().area_id) %}
|
||||
{% include "calendar_cross_areal_button.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
8
web/templates/calendar_cross_areal_button.html
Normal file
8
web/templates/calendar_cross_areal_button.html
Normal file
@ -0,0 +1,8 @@
|
||||
<button class="button is-link is-light" hx-swap="outerHTML"
|
||||
hx-put="/availability/{{ availability.id }}/make{% if availability.cross_areal %}Non{% endif %}CrossAreal"
|
||||
title="{% if availability.cross_areal %}nur im Hauptbereich{% else %}Bereichsübergreifend{% endif %} verfügbar machen">
|
||||
<svg class="icon">
|
||||
<use
|
||||
href="/static/feather-sprite.svg#{% if availability.cross_areal %}home{% else %}globe{% endif %}" />
|
||||
</svg>
|
||||
</button>
|
Loading…
x
Reference in New Issue
Block a user