diff --git a/db/src/models/assignment_changeset.rs b/db/src/models/assignment_changeset.rs index b6167935..c593e5cc 100644 --- a/db/src/models/assignment_changeset.rs +++ b/db/src/models/assignment_changeset.rs @@ -1,44 +1,82 @@ use chrono::NaiveDateTime; -use garde::Validate; +use sqlx::PgPool; -use crate::models::start_date_time_lies_before_end_date_time; - -use super::{ - Assignment, Availability, Event, Function, UserFunction, +use crate::validation::{ + AsyncValidate, AsyncValidateError, start_date_time_lies_before_end_date_time, }; -#[derive(Validate)] -#[garde(allow_unvalidated)] -#[garde(context(AssignmentContext as ctx))] -/// check before: event and availability and must exist and user of availability is in event location +use super::{Assignment, Availability, Event, Function, Role, User}; + pub struct AssignmentChangeset { - #[garde( - custom(user_of_availability_has_function), - custom(event_has_free_slot_for_function) - )] pub function: Function, - #[garde( - custom(available_time_fits), - custom(start_date_time_lies_before_end_date_time), - custom(availability_not_already_assigned) - )] pub time: (NaiveDateTime, NaiveDateTime), } -pub struct AssignmentContext { - pub event: Event, - pub availability: Availability, - pub user_function: UserFunction, - pub assignments_for_event: Vec, - pub assignments_for_availability: Vec, +pub struct AssignmentContext<'a> { + pub pool: &'a PgPool, + pub user: &'a User, + pub event_id: i32, + pub availability_id: i32, +} + +impl<'a> AsyncValidate<'a> for AssignmentChangeset { + type Context = AssignmentContext<'a>; + + async fn validate_with_context( + &self, + context: &'a Self::Context, + ) -> Result<(), crate::validation::AsyncValidateError> { + let Some(availability) = + Availability::read_by_id_including_user(context.pool, context.availability_id).await? + else { + return Err(AsyncValidateError::new( + "Angegebener Verfügbarkeit des Nutzers existiert nicht!", + )); + }; + + let Some(event) = + Event::read_by_id_including_location(context.pool, context.event_id).await? + else { + return Err(AsyncValidateError::new( + "Angegebenes Event existiert nicht!", + )); + }; + + user_is_admin_or_area_manager_of_event_area(context.user, &event)?; + availability_user_inside_event_area(&availability, &event)?; + available_time_fits(&self.time, &availability)?; + start_date_time_lies_before_end_date_time(&self.time.0, &self.time.1)?; + availability_not_already_assigned(&self.time, &availability, &event, context.pool).await?; + user_of_availability_has_function(&self.function, &availability)?; + event_has_free_slot_for_function(&self.function, &availability, &event, context.pool) + .await?; + + Ok(()) + } +} + +fn availability_user_inside_event_area( + availability: &Availability, + event: &Event, +) -> Result<(), AsyncValidateError> { + let user = availability.user.as_ref().unwrap(); + let location = event.location.as_ref().unwrap(); + + if user.area_id != location.area_id { + return Err(AsyncValidateError::new( + "Nutzer der Verfügbarkeit ist nicht im gleichen Bereich wie der Ort der Veranstaltung!", + )); + } + + Ok(()) } fn available_time_fits( value: &(NaiveDateTime, NaiveDateTime), - context: &AssignmentContext, -) -> garde::Result { - if value.0 < context.availability.start || value.1 > context.availability.end { - return Err(garde::Error::new( + availability: &Availability, +) -> Result<(), AsyncValidateError> { + if value.0 < availability.start || value.1 > availability.end { + return Err(AsyncValidateError::new( "time not made available can't be assigned", )); } @@ -48,10 +86,12 @@ fn available_time_fits( fn user_of_availability_has_function( value: &Function, - context: &AssignmentContext, -) -> garde::Result { - if !context.user_function.contains(value) { - return Err(garde::Error::new( + availability: &Availability, +) -> Result<(), AsyncValidateError> { + let user_function = &availability.user.as_ref().unwrap().function; + + if !user_function.contains(value) { + return Err(AsyncValidateError::new( "user has not the required function for this assignment", )); } @@ -59,26 +99,31 @@ fn user_of_availability_has_function( Ok(()) } -fn event_has_free_slot_for_function( +async fn event_has_free_slot_for_function( value: &Function, - context: &AssignmentContext, -) -> garde::Result { - let list: Vec<&Assignment> = context - .assignments_for_event - .iter() - .filter(|a| a.availability_id != context.availability.id && a.event_id != context.event.id) + availability: &Availability, + event: &Event, + pool: &PgPool, +) -> Result<(), AsyncValidateError> { + let assignments_for_event: Vec = Assignment::read_all_by_event(pool, event.id) + .await? + .into_iter() + .filter(|a| a.availability_id != availability.id && a.event_id != event.id) .collect(); - let a = list + let assignments_with_function = assignments_for_event .iter() - .filter(|a| a.function == Function::Posten) + .filter(|a| a.function == *value) .count(); + if match *value { - Function::Posten => a >= context.event.amount_of_posten as usize, - Function::Fuehrungsassistent => context.event.voluntary_fuehrungsassistent && a >= 1, - Function::Wachhabender => context.event.voluntary_wachhabender && a >= 1, + Function::Posten => assignments_with_function >= event.amount_of_posten as usize, + Function::Fuehrungsassistent => { + event.voluntary_fuehrungsassistent && assignments_with_function >= 1 + } + Function::Wachhabender => event.voluntary_wachhabender && assignments_with_function >= 1, } { - return Err(garde::Error::new( + return Err(AsyncValidateError::new( "event already has enough assignments for this function", )); } @@ -86,28 +131,44 @@ fn event_has_free_slot_for_function( Ok(()) } -fn availability_not_already_assigned( - value: &(NaiveDateTime, NaiveDateTime), - context: &AssignmentContext, -) -> garde::Result { - let list: Vec<&Assignment> = context - .assignments_for_availability - .iter() - .filter(|a| a.availability_id != context.availability.id && a.event_id != context.event.id) +async fn availability_not_already_assigned( + time: &(NaiveDateTime, NaiveDateTime), + availability: &Availability, + event: &Event, + pool: &PgPool, +) -> Result<(), AsyncValidateError> { + let list: Vec = Assignment::read_all_by_availability(pool, availability.id) + .await? + .into_iter() + .filter(|a| a.availability_id != availability.id && a.event_id != event.id) .collect(); - let has_start_time_during_assignment = - |a: &Assignment| a.start >= value.0 && a.start <= value.1; - let has_end_time_during_assignment = |a: &Assignment| a.end >= value.0 && a.end <= value.1; + let has_start_time_during_assignment = |a: &Assignment| a.start >= time.0 && a.start <= time.1; + let has_end_time_during_assignment = |a: &Assignment| a.end >= time.0 && a.end <= time.1; if list .iter() .any(|a| has_start_time_during_assignment(a) || has_end_time_during_assignment(a)) { - return Err(garde::Error::new( + return Err(AsyncValidateError::new( "availability is already assigned for that time", )); } Ok(()) } + +fn user_is_admin_or_area_manager_of_event_area( + user: &User, + event: &Event, +) -> Result<(), AsyncValidateError> { + let user_is_admin = user.role == Role::Admin; + let user_is_area_manager_event_area = + user.role == Role::AreaManager && user.area_id == event.location.as_ref().unwrap().area_id; + + if !user_is_admin || !user_is_area_manager_event_area { + return Err(AsyncValidateError::new("")); + } + + Ok(()) +} diff --git a/web/src/endpoints/assignment/post_new.rs b/web/src/endpoints/assignment/post_new.rs index e80f2d36..d6b094c5 100644 --- a/web/src/endpoints/assignment/post_new.rs +++ b/web/src/endpoints/assignment/post_new.rs @@ -1,5 +1,4 @@ use actix_web::{web, HttpResponse, Responder}; -use garde::Validate; use serde::Deserialize; use sqlx::PgPool; @@ -13,8 +12,9 @@ use crate::{ }, }; -use brass_db::models::{ - Assignment, AssignmentChangeset, AssignmentContext, Availability, Event, Function, Role, User, +use brass_db::{ + models::{Assignment, AssignmentChangeset, AssignmentContext, Event, Function, User}, + validation::AsyncValidate, }; #[derive(Deserialize)] @@ -35,28 +35,6 @@ pub async fn post( return Ok(HttpResponse::NotFound().finish()); }; - let user_is_admin_or_area_manager_of_event_area = user.role == Role::Admin - || (user.role == Role::AreaManager - && user.area_id == event.location.as_ref().unwrap().area_id); - - if !user_is_admin_or_area_manager_of_event_area { - return Err(ApplicationError::Unauthorized); - } - - let Some(availability) = - Availability::read_by_id_including_user(pool.get_ref(), query.availability).await? - else { - return Ok(HttpResponse::NotFound().finish()); - }; - - let availability_user_not_in_event_location_area = - availability.user.as_ref().unwrap().area_id != event.location.as_ref().unwrap().area_id; - - if availability_user_not_in_event_location_area { - return Ok(HttpResponse::BadRequest() - .body("availability user is not in the same area as event location")); - } - let function = Function::try_from(query.function)?; let changeset = AssignmentChangeset { @@ -64,22 +42,18 @@ pub async fn post( time: (event.start, event.end), }; - let assignments_for_event = Assignment::read_all_by_event(pool.get_ref(), event.id).await?; - let assignments_for_availability = - Assignment::read_all_by_availability(pool.get_ref(), availability.id).await?; let context = AssignmentContext { - event: event.clone(), - availability: availability.clone(), - user_function: availability.user.as_ref().unwrap().function.clone(), - assignments_for_event, - assignments_for_availability, + user: &user.into_inner(), + event_id: event.id, + availability_id: query.availability, + pool: pool.get_ref(), }; - if let Err(e) = changeset.validate_with(&context) { + if let Err(e) = changeset.validate_with_context(&context).await { return Ok(HttpResponse::BadRequest().body(e.to_string())); }; - Assignment::create(pool.get_ref(), event.id, availability.id, changeset).await?; + Assignment::create(pool.get_ref(), event.id, query.availability, changeset).await?; let availabilities = generate_availability_assignment_list(pool.get_ref(), &event).await?;