From c2cd1f9c85a7947946589d13b80cf02b54fefc04 Mon Sep 17 00:00:00 2001 From: Max Hohlfeld Date: Sat, 19 Jul 2025 22:07:23 +0200 Subject: [PATCH] feat: add, edit, delete foreign availabilities as admin or areamanger --- web/src/endpoints/availability/delete.rs | 5 +- web/src/endpoints/availability/get_new.rs | 14 ++-- .../endpoints/availability/get_new_other.rs | 64 +++++++++++++++++++ web/src/endpoints/availability/get_update.rs | 30 +++++++-- web/src/endpoints/availability/mod.rs | 4 ++ web/src/endpoints/availability/post_new.rs | 16 +++-- web/src/endpoints/availability/post_update.rs | 10 +-- web/src/endpoints/mod.rs | 3 +- web/templates/availability/new_or_edit.html | 25 ++++++++ web/templates/calendar.html | 8 ++- 10 files changed, 152 insertions(+), 27 deletions(-) create mode 100644 web/src/endpoints/availability/get_new_other.rs diff --git a/web/src/endpoints/availability/delete.rs b/web/src/endpoints/availability/delete.rs index 6fb5a7e4..3cdad41b 100644 --- a/web/src/endpoints/availability/delete.rs +++ b/web/src/endpoints/availability/delete.rs @@ -2,7 +2,7 @@ use actix_web::{web, HttpResponse, Responder}; use sqlx::PgPool; use crate::{endpoints::IdPath, utils::ApplicationError}; -use brass_db::models::{Availability, User}; +use brass_db::models::{Availability, Role, User}; #[actix_web::delete("/availability/delete/{id}")] pub async fn delete( @@ -14,7 +14,8 @@ pub async fn delete( return Ok(HttpResponse::NotFound().finish()); }; - if availability.user_id != user.id { + if user.role != Role::Admin && user.role != Role::AreaManager && availability.user_id != user.id + { return Err(ApplicationError::Unauthorized); } diff --git a/web/src/endpoints/availability/get_new.rs b/web/src/endpoints/availability/get_new.rs index dfa552ef..6f137332 100644 --- a/web/src/endpoints/availability/get_new.rs +++ b/web/src/endpoints/availability/get_new.rs @@ -1,24 +1,18 @@ use actix_web::{web, HttpResponse, Responder}; -use chrono::{Days, NaiveDate, NaiveTime}; -use serde::Deserialize; +use chrono::{Days, NaiveTime}; use sqlx::PgPool; use crate::{ - endpoints::availability::NewOrEditAvailabilityTemplate, + endpoints::{availability::NewOrEditAvailabilityTemplate, NaiveDateQuery}, utils::{ApplicationError, TemplateResponse}, }; use brass_db::models::{find_free_date_time_slots, Availability, User}; -#[derive(Deserialize)] -struct AvailabilityNewQuery { - date: NaiveDate, -} - #[actix_web::get("/availability/new")] pub async fn get( user: web::ReqData, pool: web::Data, - query: web::Query, + query: web::Query, ) -> Result { let availabilities_from_user = Availability::read_all_by_user_and_date(pool.get_ref(), user.id, &query.date).await?; @@ -41,6 +35,8 @@ pub async fn get( comment: None, slot_suggestions, datetomorrow: query.date.checked_add_days(Days::new(1)).unwrap(), + other_user: None, + other_users: Vec::default(), }; Ok(template.to_response()?) diff --git a/web/src/endpoints/availability/get_new_other.rs b/web/src/endpoints/availability/get_new_other.rs new file mode 100644 index 00000000..8dac9c88 --- /dev/null +++ b/web/src/endpoints/availability/get_new_other.rs @@ -0,0 +1,64 @@ +use actix_web::{web, Responder}; +use brass_db::models::{find_free_date_time_slots, Availability, Role, User}; +use chrono::{Days, NaiveDate, NaiveTime}; +use serde::Deserialize; +use sqlx::PgPool; + +use crate::{ + endpoints::availability::NewOrEditAvailabilityTemplate, + utils::{ApplicationError, TemplateResponse}, +}; + +#[derive(Deserialize)] +struct AvailabilityNewOtherQuery { + date: NaiveDate, + user: Option, +} + +#[actix_web::get("/availability/new-other")] +pub async fn get( + user: web::ReqData, + pool: web::Data, + query: web::Query, +) -> Result { + if user.role != Role::Admin && user.role != Role::AreaManager { + return Err(ApplicationError::Unauthorized); + } + + let other_user = if let Some(id) = query.user { + User::read_by_id(pool.get_ref(), id).await?.map(|u| u.id) + } else { + None + }; + + let slot_suggestions = if let Some(id) = other_user { + let availabilities = + Availability::read_all_by_user_and_date(pool.get_ref(), id, &query.date).await?; + find_free_date_time_slots(&availabilities) + } else { + Vec::default() + }; + + let mut other_users = if user.role == Role::AreaManager { + User::read_all_by_area(pool.get_ref(), user.area_id).await? + } else { + User::read_all_including_area(pool.get_ref()).await? + }; + other_users.retain(|u| u.id != user.id); + + let template = NewOrEditAvailabilityTemplate { + user: user.into_inner(), + date: query.date, + enddate: None, + id: None, + start: Some(NaiveTime::from_hms_opt(10, 0, 0).unwrap()), + end: Some(NaiveTime::from_hms_opt(20, 0, 0).unwrap()), + comment: None, + slot_suggestions, + datetomorrow: query.date.checked_add_days(Days::new(1)).unwrap(), + other_user, + other_users, + }; + + Ok(template.to_response()?) +} diff --git a/web/src/endpoints/availability/get_update.rs b/web/src/endpoints/availability/get_update.rs index e25fd92b..26d65d42 100644 --- a/web/src/endpoints/availability/get_update.rs +++ b/web/src/endpoints/availability/get_update.rs @@ -6,7 +6,7 @@ use crate::{ endpoints::{availability::NewOrEditAvailabilityTemplate, IdPath}, utils::{ApplicationError, TemplateResponse}, }; -use brass_db::models::{find_free_date_time_slots, Availability, User}; +use brass_db::models::{find_free_date_time_slots, Availability, Role, User}; #[actix_web::get("/availability/edit/{id}")] pub async fn get( @@ -18,19 +18,37 @@ pub async fn get( return Ok(HttpResponse::NotFound().finish()); }; - if availability.user_id != user.id { + if user.role != Role::Admin && user.role != Role::AreaManager && availability.user_id != user.id + { return Err(ApplicationError::Unauthorized); } - let availabilities = - Availability::read_all_by_user_and_date(pool.get_ref(), user.id, &availability.start.date()) - .await?; + let availabilities = Availability::read_all_by_user_and_date( + pool.get_ref(), + availability.user_id, + &availability.start.date(), + ) + .await?; let slot_suggestions = find_free_date_time_slots(&availabilities) .into_iter() .filter(|(a, b)| *b == availability.start || *a == availability.end) .collect(); + let other_user = if availability.user_id != user.id { + Some(availability.user_id) + } else { + None + }; + + let other_users = if availability.user_id != user.id { + vec![User::read_by_id(pool.get_ref(), availability.user_id) + .await? + .unwrap()] + } else { + Vec::default() + }; + let template = NewOrEditAvailabilityTemplate { user: user.into_inner(), date: availability.start.date(), @@ -45,6 +63,8 @@ pub async fn get( .date() .checked_add_days(Days::new(1)) .unwrap(), + other_user, + other_users, }; Ok(template.to_response()?) diff --git a/web/src/endpoints/availability/mod.rs b/web/src/endpoints/availability/mod.rs index e633d2fd..75d4bea7 100644 --- a/web/src/endpoints/availability/mod.rs +++ b/web/src/endpoints/availability/mod.rs @@ -11,6 +11,7 @@ use brass_db::models::{Role, User}; pub mod delete; pub mod get_calendar; pub mod get_new; +pub mod get_new_other; pub mod get_overview; pub mod get_update; pub mod post_new; @@ -28,6 +29,8 @@ struct NewOrEditAvailabilityTemplate<'a> { comment: Option<&'a str>, slot_suggestions: Vec<(NaiveDateTime, NaiveDateTime)>, datetomorrow: NaiveDate, + other_user: Option, + other_users: Vec } #[derive(Deserialize)] @@ -37,4 +40,5 @@ pub struct AvailabilityForm { pub starttime: NaiveTime, pub endtime: NaiveTime, pub comment: Option, + pub user: Option } diff --git a/web/src/endpoints/availability/post_new.rs b/web/src/endpoints/availability/post_new.rs index 9e2f46df..a346a101 100644 --- a/web/src/endpoints/availability/post_new.rs +++ b/web/src/endpoints/availability/post_new.rs @@ -19,10 +19,11 @@ pub async fn post( ) -> Result { let start = form.startdate.and_time(form.starttime); let end = form.enddate.and_time(form.endtime); + let user_for_availability = form.user.unwrap_or(user.id); let context = AvailabilityContext { pool: pool.get_ref(), - user_id: user.id, + user_id: user_for_availability, availability: None, }; @@ -46,9 +47,14 @@ pub async fn post( return Ok(HttpResponse::UnprocessableEntity().body(error_message.into_string())); }; - if let Some(a) = - Availability::find_adjacent_by_time_for_user(pool.get_ref(), &start, &end, user.id, None) - .await? + if let Some(a) = Availability::find_adjacent_by_time_for_user( + pool.get_ref(), + &start, + &end, + user_for_availability, + None, + ) + .await? { if a.end == start { changeset.time.0 = a.start; @@ -60,7 +66,7 @@ pub async fn post( Availability::update(pool.get_ref(), a.id, changeset).await?; } else { - Availability::create(pool.get_ref(), user.id, changeset).await?; + Availability::create(pool.get_ref(), user_for_availability, changeset).await?; } let url = utils::get_return_url_for_date(&form.startdate); diff --git a/web/src/endpoints/availability/post_update.rs b/web/src/endpoints/availability/post_update.rs index 443f4864..cddc5e69 100644 --- a/web/src/endpoints/availability/post_update.rs +++ b/web/src/endpoints/availability/post_update.rs @@ -7,7 +7,7 @@ use crate::{ utils::{self, ApplicationError}, }; use brass_db::{ - models::{Availability, AvailabilityChangeset, AvailabilityContext, User}, + models::{Availability, AvailabilityChangeset, AvailabilityContext, Role, User}, validation::AsyncValidate, }; @@ -22,12 +22,14 @@ pub async fn post( return Ok(HttpResponse::NotFound().finish()); }; - if availability.user_id != user.id { + if user.role != Role::Admin && user.role != Role::AreaManager && availability.user_id != user.id + { return Err(ApplicationError::Unauthorized); } let start = form.startdate.and_time(form.starttime); let end = form.enddate.and_time(form.endtime); + let user_for_availability = form.user.unwrap_or(user.id); let comment = form .comment @@ -36,7 +38,7 @@ pub async fn post( let context = AvailabilityContext { pool: pool.get_ref(), - user_id: user.id, + user_id: user_for_availability, availability: Some(availability.id), }; @@ -59,7 +61,7 @@ pub async fn post( pool.get_ref(), &start, &end, - user.id, + user_for_availability, Some(availability.id), ) .await? diff --git a/web/src/endpoints/mod.rs b/web/src/endpoints/mod.rs index dcf58842..28878aa2 100644 --- a/web/src/endpoints/mod.rs +++ b/web/src/endpoints/mod.rs @@ -10,7 +10,7 @@ mod events; mod export; mod imprint; mod location; -pub mod user; // TODO: why pub? +pub mod user; mod vehicle; mod vehicle_assignment; @@ -56,6 +56,7 @@ pub fn init(cfg: &mut ServiceConfig) { cfg.service(availability::delete::delete); cfg.service(availability::get_new::get); + cfg.service(availability::get_new_other::get); cfg.service(availability::get_calendar::get); cfg.service(availability::get_update::get); cfg.service(availability::post_new::post); diff --git a/web/templates/availability/new_or_edit.html b/web/templates/availability/new_or_edit.html index 63497842..1c8a8484 100644 --- a/web/templates/availability/new_or_edit.html +++ b/web/templates/availability/new_or_edit.html @@ -12,6 +12,31 @@ + {% if other_users.len() != 0 %} +
+
+ +
+
+
+
+
+ +
+
+
+
+
+ {% endif %} +
diff --git a/web/templates/calendar.html b/web/templates/calendar.html index c2431f6d..26567092 100644 --- a/web/templates/calendar.html +++ b/web/templates/calendar.html @@ -236,6 +236,12 @@ {% if selected_area.is_none() || selected_area.unwrap() == user.area_id %}
{% let btn_disabled = !user_can_create_availability %} + + + + + Neue Verfügbarkeit für anderen Nutzer +