Compare commits
No commits in common. "507bb13a6e9212ae5b81a4b34ef2044eada72be0" and "e5df98a515ad9cef6595fb50f3c5cc9024c67600" have entirely different histories.
507bb13a6e
...
e5df98a515
@ -1,82 +1,44 @@
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use sqlx::PgPool;
|
use garde::Validate;
|
||||||
|
|
||||||
use crate::validation::{
|
use crate::models::start_date_time_lies_before_end_date_time;
|
||||||
AsyncValidate, AsyncValidateError, start_date_time_lies_before_end_date_time,
|
|
||||||
|
use super::{
|
||||||
|
Assignment, Availability, Event, Function, UserFunction,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Assignment, Availability, Event, Function, Role, User};
|
#[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
|
||||||
pub struct AssignmentChangeset {
|
pub struct AssignmentChangeset {
|
||||||
|
#[garde(
|
||||||
|
custom(user_of_availability_has_function),
|
||||||
|
custom(event_has_free_slot_for_function)
|
||||||
|
)]
|
||||||
pub function: 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 time: (NaiveDateTime, NaiveDateTime),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AssignmentContext<'a> {
|
pub struct AssignmentContext {
|
||||||
pub pool: &'a PgPool,
|
pub event: Event,
|
||||||
pub user: &'a User,
|
pub availability: Availability,
|
||||||
pub event_id: i32,
|
pub user_function: UserFunction,
|
||||||
pub availability_id: i32,
|
pub assignments_for_event: Vec<Assignment>,
|
||||||
}
|
pub assignments_for_availability: Vec<Assignment>,
|
||||||
|
|
||||||
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(
|
fn available_time_fits(
|
||||||
value: &(NaiveDateTime, NaiveDateTime),
|
value: &(NaiveDateTime, NaiveDateTime),
|
||||||
availability: &Availability,
|
context: &AssignmentContext,
|
||||||
) -> Result<(), AsyncValidateError> {
|
) -> garde::Result {
|
||||||
if value.0 < availability.start || value.1 > availability.end {
|
if value.0 < context.availability.start || value.1 > context.availability.end {
|
||||||
return Err(AsyncValidateError::new(
|
return Err(garde::Error::new(
|
||||||
"time not made available can't be assigned",
|
"time not made available can't be assigned",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -86,12 +48,10 @@ fn available_time_fits(
|
|||||||
|
|
||||||
fn user_of_availability_has_function(
|
fn user_of_availability_has_function(
|
||||||
value: &Function,
|
value: &Function,
|
||||||
availability: &Availability,
|
context: &AssignmentContext,
|
||||||
) -> Result<(), AsyncValidateError> {
|
) -> garde::Result {
|
||||||
let user_function = &availability.user.as_ref().unwrap().function;
|
if !context.user_function.contains(value) {
|
||||||
|
return Err(garde::Error::new(
|
||||||
if !user_function.contains(value) {
|
|
||||||
return Err(AsyncValidateError::new(
|
|
||||||
"user has not the required function for this assignment",
|
"user has not the required function for this assignment",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -99,31 +59,26 @@ fn user_of_availability_has_function(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn event_has_free_slot_for_function(
|
fn event_has_free_slot_for_function(
|
||||||
value: &Function,
|
value: &Function,
|
||||||
availability: &Availability,
|
context: &AssignmentContext,
|
||||||
event: &Event,
|
) -> garde::Result {
|
||||||
pool: &PgPool,
|
let list: Vec<&Assignment> = context
|
||||||
) -> Result<(), AsyncValidateError> {
|
.assignments_for_event
|
||||||
let assignments_for_event: Vec<Assignment> = Assignment::read_all_by_event(pool, event.id)
|
.iter()
|
||||||
.await?
|
.filter(|a| a.availability_id != context.availability.id && a.event_id != context.event.id)
|
||||||
.into_iter()
|
|
||||||
.filter(|a| a.availability_id != availability.id && a.event_id != event.id)
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let assignments_with_function = assignments_for_event
|
let a = list
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|a| a.function == *value)
|
.filter(|a| a.function == Function::Posten)
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
if match *value {
|
if match *value {
|
||||||
Function::Posten => assignments_with_function >= event.amount_of_posten as usize,
|
Function::Posten => a >= context.event.amount_of_posten as usize,
|
||||||
Function::Fuehrungsassistent => {
|
Function::Fuehrungsassistent => context.event.voluntary_fuehrungsassistent && a >= 1,
|
||||||
event.voluntary_fuehrungsassistent && assignments_with_function >= 1
|
Function::Wachhabender => context.event.voluntary_wachhabender && a >= 1,
|
||||||
}
|
|
||||||
Function::Wachhabender => event.voluntary_wachhabender && assignments_with_function >= 1,
|
|
||||||
} {
|
} {
|
||||||
return Err(AsyncValidateError::new(
|
return Err(garde::Error::new(
|
||||||
"event already has enough assignments for this function",
|
"event already has enough assignments for this function",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -131,44 +86,28 @@ async fn event_has_free_slot_for_function(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn availability_not_already_assigned(
|
fn availability_not_already_assigned(
|
||||||
time: &(NaiveDateTime, NaiveDateTime),
|
value: &(NaiveDateTime, NaiveDateTime),
|
||||||
availability: &Availability,
|
context: &AssignmentContext,
|
||||||
event: &Event,
|
) -> garde::Result {
|
||||||
pool: &PgPool,
|
let list: Vec<&Assignment> = context
|
||||||
) -> Result<(), AsyncValidateError> {
|
.assignments_for_availability
|
||||||
let list: Vec<Assignment> = Assignment::read_all_by_availability(pool, availability.id)
|
.iter()
|
||||||
.await?
|
.filter(|a| a.availability_id != context.availability.id && a.event_id != context.event.id)
|
||||||
.into_iter()
|
|
||||||
.filter(|a| a.availability_id != availability.id && a.event_id != event.id)
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let has_start_time_during_assignment = |a: &Assignment| a.start >= time.0 && a.start <= time.1;
|
let has_start_time_during_assignment =
|
||||||
let has_end_time_during_assignment = |a: &Assignment| a.end >= time.0 && a.end <= time.1;
|
|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;
|
||||||
|
|
||||||
if list
|
if list
|
||||||
.iter()
|
.iter()
|
||||||
.any(|a| has_start_time_during_assignment(a) || has_end_time_during_assignment(a))
|
.any(|a| has_start_time_during_assignment(a) || has_end_time_during_assignment(a))
|
||||||
{
|
{
|
||||||
return Err(AsyncValidateError::new(
|
return Err(garde::Error::new(
|
||||||
"availability is already assigned for that time",
|
"availability is already assigned for that time",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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(())
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
use garde::Validate;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
@ -12,9 +13,8 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use brass_db::{
|
use brass_db::models::{
|
||||||
models::{Assignment, AssignmentChangeset, AssignmentContext, Event, Function, User},
|
Assignment, AssignmentChangeset, AssignmentContext, Availability, Event, Function, Role, User,
|
||||||
validation::AsyncValidate,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -35,6 +35,28 @@ pub async fn post(
|
|||||||
return Ok(HttpResponse::NotFound().finish());
|
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 function = Function::try_from(query.function)?;
|
||||||
|
|
||||||
let changeset = AssignmentChangeset {
|
let changeset = AssignmentChangeset {
|
||||||
@ -42,18 +64,22 @@ pub async fn post(
|
|||||||
time: (event.start, event.end),
|
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 {
|
let context = AssignmentContext {
|
||||||
user: &user.into_inner(),
|
event: event.clone(),
|
||||||
event_id: event.id,
|
availability: availability.clone(),
|
||||||
availability_id: query.availability,
|
user_function: availability.user.as_ref().unwrap().function.clone(),
|
||||||
pool: pool.get_ref(),
|
assignments_for_event,
|
||||||
|
assignments_for_availability,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = changeset.validate_with_context(&context).await {
|
if let Err(e) = changeset.validate_with(&context) {
|
||||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
||||||
};
|
};
|
||||||
|
|
||||||
Assignment::create(pool.get_ref(), event.id, query.availability, changeset).await?;
|
Assignment::create(pool.get_ref(), event.id, availability.id, changeset).await?;
|
||||||
|
|
||||||
let availabilities = generate_availability_assignment_list(pool.get_ref(), &event).await?;
|
let availabilities = generate_availability_assignment_list(pool.get_ref(), &event).await?;
|
||||||
|
|
||||||
@ -73,84 +99,3 @@ pub async fn post(
|
|||||||
|
|
||||||
Ok(template.to_response()?)
|
Ok(template.to_response()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use brass_db::models::{
|
|
||||||
Availability, AvailabilityChangeset, Event, EventChangeset, Location, User,
|
|
||||||
};
|
|
||||||
use brass_macros::db_test;
|
|
||||||
use chrono::{NaiveDate, NaiveDateTime};
|
|
||||||
use fake::{Fake, Faker};
|
|
||||||
|
|
||||||
use crate::utils::test_helper::{DbTestContext, NaiveDateTimeExt};
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn response_produces_updated_template(context: &DbTestContext) {
|
|
||||||
let app = context.app().await;
|
|
||||||
Location::create(&context.db_pool, &Faker.fake::<String>(), 1)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let start = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 10, 0, 0).unwrap();
|
|
||||||
let end = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 20, 0, 0).unwrap();
|
|
||||||
|
|
||||||
Event::create(
|
|
||||||
&context.db_pool,
|
|
||||||
EventChangeset::create_for_test(start, end),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
User::create(&context.db_pool, Faker.fake()).await.unwrap();
|
|
||||||
|
|
||||||
// Availability::create(pool, 1, AvailabilityChangeset { time () })
|
|
||||||
|
|
||||||
assert!(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_availability_does_not_exist(context: &DbTestContext) {
|
|
||||||
assert!(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_event_does_not_exist(context: &DbTestContext) {
|
|
||||||
assert!(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_area_manager_is_different_area_from_event(context: &DbTestContext) {
|
|
||||||
assert!(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_availability_user_not_in_event_area(context: &DbTestContext) {
|
|
||||||
assert!(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_assignment_time_doesnt_fit_into_availability_time(context: &DbTestContext) {
|
|
||||||
assert!(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_end_time_lies_before_start_time(context: &DbTestContext) {
|
|
||||||
assert!(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_availability_time_already_assigned(context: &DbTestContext) {
|
|
||||||
assert!(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_availability_user_does_not_have_function(context: &DbTestContext) {
|
|
||||||
assert!(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_event_already_has_enough_assignments_for_function(context: &DbTestContext) {
|
|
||||||
assert!(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
mod test_context;
|
mod test_context;
|
||||||
mod test_requests;
|
mod test_requests;
|
||||||
use chrono::{NaiveDate, NaiveDateTime};
|
|
||||||
pub use test_context::{setup, teardown, DbTestContext};
|
pub use test_context::{setup, teardown, DbTestContext};
|
||||||
pub use test_requests::RequestConfig;
|
pub use test_requests::RequestConfig;
|
||||||
pub use test_requests::{read_body, test_delete, test_get, test_post, test_put};
|
pub use test_requests::{read_body, test_delete, test_get, test_post, test_put};
|
||||||
@ -28,27 +27,3 @@ macro_rules! assert_mail_snapshot {
|
|||||||
|
|
||||||
pub(crate) use assert_mail_snapshot;
|
pub(crate) use assert_mail_snapshot;
|
||||||
pub(crate) use assert_snapshot;
|
pub(crate) use assert_snapshot;
|
||||||
|
|
||||||
pub trait NaiveDateTimeExt {
|
|
||||||
fn from_ymd_and_hms(
|
|
||||||
year: i32,
|
|
||||||
month: u32,
|
|
||||||
day: u32,
|
|
||||||
hour: u32,
|
|
||||||
minute: u32,
|
|
||||||
second: u32,
|
|
||||||
) -> Option<NaiveDateTime>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NaiveDateTimeExt for NaiveDateTime {
|
|
||||||
fn from_ymd_and_hms(
|
|
||||||
year: i32,
|
|
||||||
month: u32,
|
|
||||||
day: u32,
|
|
||||||
hour: u32,
|
|
||||||
minute: u32,
|
|
||||||
second: u32,
|
|
||||||
) -> Option<NaiveDateTime> {
|
|
||||||
NaiveDate::from_ymd_opt(year, month, day)?.and_hms_opt(hour, minute, second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user