refactor: clothing and event changeset
This commit is contained in:
parent
bdaf8ff20e
commit
428f46b853
56
Cargo.lock
generated
56
Cargo.lock
generated
@ -794,7 +794,6 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"fake",
|
"fake",
|
||||||
"garde",
|
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
@ -831,7 +830,6 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"fake",
|
"fake",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"garde",
|
|
||||||
"insta",
|
"insta",
|
||||||
"lettre",
|
"lettre",
|
||||||
"maud",
|
"maud",
|
||||||
@ -906,15 +904,6 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "castaway"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
|
|
||||||
dependencies = [
|
|
||||||
"rustversion",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.22"
|
version = "1.2.22"
|
||||||
@ -1023,20 +1012,6 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "compact_str"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
|
|
||||||
dependencies = [
|
|
||||||
"castaway",
|
|
||||||
"cfg-if",
|
|
||||||
"itoa",
|
|
||||||
"rustversion",
|
|
||||||
"ryu",
|
|
||||||
"static_assertions",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -1659,31 +1634,6 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "garde"
|
|
||||||
version = "0.22.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6a989bd2fd12136080f7825ff410d9239ce84a2a639487fc9d924ee42e2fb84f"
|
|
||||||
dependencies = [
|
|
||||||
"compact_str",
|
|
||||||
"garde_derive",
|
|
||||||
"once_cell",
|
|
||||||
"regex",
|
|
||||||
"smallvec",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "garde_derive"
|
|
||||||
version = "0.22.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1f7f0545bbbba0a37d4d445890fa5759814e0716f02417b39f6fab292193df68"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"regex",
|
|
||||||
"syn 2.0.101",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
@ -3399,12 +3349,6 @@ dependencies = [
|
|||||||
"path-slash",
|
"path-slash",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "static_assertions"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stringprep"
|
name = "stringprep"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -9,7 +9,6 @@ publish = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
sqlx = { version = "^0.8", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
|
sqlx = { version = "^0.8", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
|
||||||
chrono = { version = "0.4.33", features = ["serde", "now"] }
|
chrono = { version = "0.4.33", features = ["serde", "now"] }
|
||||||
garde = { version = "0.22.0", features = ["derive", "email"] } # refactor out
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
rand = { version = "0.9", features = ["os_rng"] }
|
rand = { version = "0.9", features = ["os_rng"] }
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
|
@ -167,6 +167,7 @@ async fn availability_not_already_assigned(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: maybe merge with event changeset
|
||||||
fn user_is_admin_or_area_manager_of_event_area(
|
fn user_is_admin_or_area_manager_of_event_area(
|
||||||
user: &User,
|
user: &User,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
|
@ -1,34 +1,234 @@
|
|||||||
|
use chrono::Days;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
#[cfg(feature = "test-helpers")]
|
#[cfg(feature = "test-helpers")]
|
||||||
use fake::{Fake, Faker};
|
use fake::{Fake, Faker};
|
||||||
use garde::Validate;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use super::start_date_time_lies_before_end_date_time;
|
use crate::END_OF_DAY;
|
||||||
|
use crate::START_OF_DAY;
|
||||||
|
use crate::models::Assignment;
|
||||||
|
use crate::models::Availability;
|
||||||
|
use crate::models::Event;
|
||||||
|
use crate::models::Function;
|
||||||
|
use crate::models::Location;
|
||||||
|
use crate::models::Role;
|
||||||
|
use crate::models::User;
|
||||||
|
use crate::validation::AsyncValidate;
|
||||||
|
use crate::validation::AsyncValidateError;
|
||||||
|
use crate::validation::start_date_time_lies_before_end_date_time;
|
||||||
|
|
||||||
#[derive(Validate)]
|
|
||||||
#[garde(allow_unvalidated)]
|
|
||||||
#[garde(context(Option<EventContext> as ctx))]
|
|
||||||
pub struct EventChangeset {
|
pub struct EventChangeset {
|
||||||
#[garde(
|
|
||||||
custom(start_date_time_lies_before_end_date_time),
|
|
||||||
custom(time_can_be_extended_if_edit),
|
|
||||||
custom(date_unchanged_if_edit)
|
|
||||||
)]
|
|
||||||
pub time: (NaiveDateTime, NaiveDateTime),
|
pub time: (NaiveDateTime, NaiveDateTime),
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// check before: must exist and user can create event for this location
|
|
||||||
pub location_id: i32,
|
pub location_id: i32,
|
||||||
#[garde(custom(can_unset_wachhabender))]
|
|
||||||
pub voluntary_wachhabender: bool,
|
pub voluntary_wachhabender: bool,
|
||||||
#[garde(custom(can_unset_fuehrungsassistent))]
|
|
||||||
pub voluntary_fuehrungsassistent: bool,
|
pub voluntary_fuehrungsassistent: bool,
|
||||||
#[garde(range(min = ctx.as_ref().map(|c: &EventContext| c.amount_of_assigned_posten).unwrap_or(0), max = 100))]
|
|
||||||
pub amount_of_posten: i16,
|
pub amount_of_posten: i16,
|
||||||
pub clothing: i32,
|
pub clothing: i32,
|
||||||
pub note: Option<String>,
|
pub note: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct EventContext<'a> {
|
||||||
|
pub pool: &'a PgPool,
|
||||||
|
pub event: Option<i32>,
|
||||||
|
pub user: &'a User,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AsyncValidate<'a> for EventChangeset {
|
||||||
|
type Context = EventContext<'a>;
|
||||||
|
|
||||||
|
async fn validate_with_context(
|
||||||
|
&self,
|
||||||
|
context: &'a Self::Context,
|
||||||
|
) -> Result<(), AsyncValidateError> {
|
||||||
|
let Some(location) = Location::read_by_id(context.pool, self.location_id).await? else {
|
||||||
|
return Err(AsyncValidateError::new(
|
||||||
|
"Der angegebene Veranstaltungsort existiert nicht.",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
user_is_admin_or_area_manager_of_event_location(context.user, &location)?;
|
||||||
|
|
||||||
|
start_date_time_lies_before_end_date_time(&self.time.0, &self.time.1)?;
|
||||||
|
|
||||||
|
let mut minimum_amount_of_posten = 0_i16;
|
||||||
|
if let Some(id) = context.event {
|
||||||
|
let event = Event::read_by_id_including_location(context.pool, id)
|
||||||
|
.await?
|
||||||
|
.unwrap();
|
||||||
|
let assignments_for_event =
|
||||||
|
Assignment::read_all_by_event(context.pool, event.id).await?;
|
||||||
|
minimum_amount_of_posten = assignments_for_event
|
||||||
|
.iter()
|
||||||
|
.filter(|a| a.function == Function::Posten)
|
||||||
|
.count() as i16;
|
||||||
|
|
||||||
|
time_can_be_extended_if_edit(&self.time, &event, &assignments_for_event, context.pool)
|
||||||
|
.await?;
|
||||||
|
date_unchanged_if_edit(&self.time, &event.start.date())?;
|
||||||
|
can_unset_wachhabender(&self.voluntary_wachhabender, &assignments_for_event)?;
|
||||||
|
can_unset_fuehrungsassistent(
|
||||||
|
&self.voluntary_fuehrungsassistent,
|
||||||
|
&assignments_for_event,
|
||||||
|
)?;
|
||||||
|
if location.area_id != event.location.unwrap().area_id {
|
||||||
|
return Err(AsyncValidateError::new(
|
||||||
|
"Veranstaltungsort kann nicht zu einem Ort außerhalb des initialen Bereichs geändert werden.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(minimum_amount_of_posten..=100).contains(&self.amount_of_posten) {
|
||||||
|
return Err(AsyncValidateError::new(
|
||||||
|
"Die Anzahl der Posten darf nicht kleiner als die Anzahl der bereits geplanten Posten und maximal 100 sein.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_is_admin_or_area_manager_of_event_location(
|
||||||
|
user: &User,
|
||||||
|
location: &Location,
|
||||||
|
) -> Result<(), AsyncValidateError> {
|
||||||
|
if user.role != Role::Admin
|
||||||
|
&& !(user.role == Role::AreaManager && user.area_id == location.area_id)
|
||||||
|
{
|
||||||
|
return Err(AsyncValidateError::new(
|
||||||
|
"Du verfügst nicht über die Berechtigung, diese Veranstaltung zu erstellen bzw. zu bearbeiten.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn date_unchanged_if_edit(
|
||||||
|
time: &(NaiveDateTime, NaiveDateTime),
|
||||||
|
date_in_db: &NaiveDate,
|
||||||
|
) -> Result<(), AsyncValidateError> {
|
||||||
|
if time.0.date() != *date_in_db {
|
||||||
|
return Err(AsyncValidateError::new("event date can't be changed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn time_can_be_extended_if_edit(
|
||||||
|
time: &(NaiveDateTime, NaiveDateTime),
|
||||||
|
event: &Event,
|
||||||
|
assignments_for_event: &Vec<Assignment>,
|
||||||
|
pool: &PgPool,
|
||||||
|
) -> Result<(), AsyncValidateError> {
|
||||||
|
let start = event.start.date();
|
||||||
|
let end = event.start.date().checked_add_days(Days::new(1)).unwrap();
|
||||||
|
let mut common_time = (start.and_time(START_OF_DAY), end.and_time(END_OF_DAY));
|
||||||
|
|
||||||
|
for assignment in assignments_for_event {
|
||||||
|
let availability = Availability::read_by_id(pool, assignment.availability_id)
|
||||||
|
.await?
|
||||||
|
.unwrap();
|
||||||
|
let all_assignments =
|
||||||
|
Assignment::read_all_by_availability(pool, assignment.availability_id).await?;
|
||||||
|
|
||||||
|
if all_assignments.len() == 1 {
|
||||||
|
if availability.start > common_time.0 {
|
||||||
|
common_time.0 = availability.start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if availability.end < common_time.1 {
|
||||||
|
common_time.1 = availability.end;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut slots = vec![(availability.start, availability.end)];
|
||||||
|
for a in all_assignments
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.event_id != assignment.event_id)
|
||||||
|
{
|
||||||
|
let (fit, rest) = slots
|
||||||
|
.into_iter()
|
||||||
|
.partition(|s| s.0 >= a.start && s.1 <= a.end);
|
||||||
|
slots = rest;
|
||||||
|
let fit = fit.first().unwrap();
|
||||||
|
|
||||||
|
if fit.0 != a.start {
|
||||||
|
slots.push((fit.0, a.start));
|
||||||
|
}
|
||||||
|
|
||||||
|
if fit.1 != a.end {
|
||||||
|
slots.push((a.end, fit.1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let slot = slots
|
||||||
|
.into_iter()
|
||||||
|
.find(|s| s.0 >= assignment.start && s.1 <= assignment.end)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if slot.0 > common_time.0 {
|
||||||
|
common_time.0 = slot.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if slot.1 < common_time.1 {
|
||||||
|
common_time.1 = slot.1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_start_time = common_time.0;
|
||||||
|
let new_start_time = time.0;
|
||||||
|
let old_end_time = common_time.1;
|
||||||
|
let new_end_time = time.1;
|
||||||
|
|
||||||
|
if new_start_time < old_start_time {
|
||||||
|
return Err(AsyncValidateError::new(
|
||||||
|
"starttime lies outside of available time for assigned people",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_end_time > old_end_time {
|
||||||
|
return Err(AsyncValidateError::new(
|
||||||
|
"endtime lies ouside of available time for assigned people",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_unset_fuehrungsassistent(
|
||||||
|
fuehrungsassistent_required: &bool,
|
||||||
|
assignments_for_event: &Vec<Assignment>,
|
||||||
|
) -> Result<(), AsyncValidateError> {
|
||||||
|
if !*fuehrungsassistent_required
|
||||||
|
&& assignments_for_event
|
||||||
|
.iter()
|
||||||
|
.any(|a| a.function == Function::Fuehrungsassistent)
|
||||||
|
{
|
||||||
|
return Err(AsyncValidateError::new(
|
||||||
|
"fuehrungsassistent can't be set to not by ff, because a person is already assigned",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_unset_wachhabender(
|
||||||
|
voluntary_wachhabender: &bool,
|
||||||
|
assignments_for_event: &Vec<Assignment>,
|
||||||
|
) -> Result<(), AsyncValidateError> {
|
||||||
|
if !*voluntary_wachhabender
|
||||||
|
&& assignments_for_event
|
||||||
|
.iter()
|
||||||
|
.any(|a| a.function == Function::Wachhabender)
|
||||||
|
{
|
||||||
|
return Err(AsyncValidateError::new(
|
||||||
|
"wachhabender can't be set to not by ff, because a person is already assigned",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "test-helpers")]
|
#[cfg(feature = "test-helpers")]
|
||||||
impl EventChangeset {
|
impl EventChangeset {
|
||||||
pub fn create_for_test(start: NaiveDateTime, end: NaiveDateTime) -> EventChangeset {
|
pub fn create_for_test(start: NaiveDateTime, end: NaiveDateTime) -> EventChangeset {
|
||||||
@ -46,77 +246,3 @@ impl EventChangeset {
|
|||||||
changeset
|
changeset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventContext {
|
|
||||||
pub date_in_db: NaiveDate,
|
|
||||||
pub common_min_max_available_time: (NaiveDateTime, NaiveDateTime),
|
|
||||||
pub wachhabender_assigned: bool,
|
|
||||||
pub fuehrungsassistent_assigned: bool,
|
|
||||||
pub amount_of_assigned_posten: i16,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn date_unchanged_if_edit(
|
|
||||||
value: &(NaiveDateTime, NaiveDateTime),
|
|
||||||
context: &Option<EventContext>,
|
|
||||||
) -> garde::Result {
|
|
||||||
if context
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|c| c.date_in_db != value.0.date())
|
|
||||||
{
|
|
||||||
return Err(garde::Error::new("event date can't be changed"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn time_can_be_extended_if_edit(
|
|
||||||
value: &(NaiveDateTime, NaiveDateTime),
|
|
||||||
context: &Option<EventContext>,
|
|
||||||
) -> garde::Result {
|
|
||||||
if let Some(context) = context {
|
|
||||||
let old_start_time = context.common_min_max_available_time.0;
|
|
||||||
let new_start_time = value.0;
|
|
||||||
let old_end_time = context.common_min_max_available_time.1;
|
|
||||||
let new_end_time = value.1;
|
|
||||||
|
|
||||||
if new_start_time < old_start_time {
|
|
||||||
return Err(garde::Error::new(
|
|
||||||
"starttime lies outside of available time for assigned people",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_end_time > old_end_time {
|
|
||||||
return Err(garde::Error::new(
|
|
||||||
"endtime lies ouside of available time for assigned people",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_unset_fuehrungsassistent(value: &bool, context: &Option<EventContext>) -> garde::Result {
|
|
||||||
if context
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|c| !*value && c.fuehrungsassistent_assigned)
|
|
||||||
{
|
|
||||||
return Err(garde::Error::new(
|
|
||||||
"fuehrungsassistent can't be set to not by ff, because a person is already assigned",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_unset_wachhabender(value: &bool, context: &Option<EventContext>) -> garde::Result {
|
|
||||||
if context
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|c| !*value && c.wachhabender_assigned)
|
|
||||||
{
|
|
||||||
return Err(garde::Error::new(
|
|
||||||
"wachhabender can't be set to not by ff, because a person is already assigned",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
@ -42,17 +42,4 @@ pub use user_funtion::UserFunction;
|
|||||||
pub use vehicle::Vehicle;
|
pub use vehicle::Vehicle;
|
||||||
pub use vehicle_assignment::VehicleAssignment;
|
pub use vehicle_assignment::VehicleAssignment;
|
||||||
|
|
||||||
use chrono::NaiveDateTime;
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, sqlx::Error>;
|
type Result<T> = std::result::Result<T, sqlx::Error>;
|
||||||
|
|
||||||
fn start_date_time_lies_before_end_date_time<T>(
|
|
||||||
value: &(NaiveDateTime, NaiveDateTime),
|
|
||||||
_context: &T,
|
|
||||||
) -> garde::Result {
|
|
||||||
if value.0 >= value.1 {
|
|
||||||
return Err(garde::Error::new("endtime can't lie before starttime"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
@ -31,7 +31,6 @@ brass-config = { path = "../config" }
|
|||||||
brass-db = { path = "../db" }
|
brass-db = { path = "../db" }
|
||||||
actix-http = "3.9.0"
|
actix-http = "3.9.0"
|
||||||
askama = "0.13.0"
|
askama = "0.13.0"
|
||||||
garde = { version = "0.22.0", features = ["derive", "email"] }
|
|
||||||
maud = "0.27.0"
|
maud = "0.27.0"
|
||||||
tracing-actix-web = "0.7.18"
|
tracing-actix-web = "0.7.18"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
|
@ -13,7 +13,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use brass_db::{
|
use brass_db::{
|
||||||
models::{Assignment, AssignmentChangeset, AssignmentContext, Event, Function, User},
|
models::{Assignment, AssignmentChangeset, AssignmentContext, Event, Function, Role, User},
|
||||||
validation::AsyncValidate,
|
validation::AsyncValidate,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,6 +30,10 @@ pub async fn post(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
query: web::Query<AssignmentQuery>,
|
query: web::Query<AssignmentQuery>,
|
||||||
) -> Result<impl Responder, ApplicationError> {
|
) -> Result<impl Responder, ApplicationError> {
|
||||||
|
if user.role != Role::Admin && user.role != Role::AreaManager {
|
||||||
|
return Err(ApplicationError::Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
let Some(event) = Event::read_by_id_including_location(pool.get_ref(), query.event).await?
|
let Some(event) = Event::read_by_id_including_location(pool.get_ref(), query.event).await?
|
||||||
else {
|
else {
|
||||||
return Ok(HttpResponse::NotFound().finish());
|
return Ok(HttpResponse::NotFound().finish());
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use garde::Validate;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::filters;
|
use crate::filters;
|
||||||
@ -26,8 +25,7 @@ struct ReadClothingPartialTemplate {
|
|||||||
c: Clothing,
|
c: Clothing,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Validate)]
|
#[derive(Deserialize)]
|
||||||
struct NewOrEditClothingForm {
|
struct NewOrEditClothingForm {
|
||||||
#[garde(length(min = 3))]
|
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
use garde::Validate;
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -26,8 +25,8 @@ pub async fn post(
|
|||||||
return Ok(HttpResponse::NotFound().finish());
|
return Ok(HttpResponse::NotFound().finish());
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = form.validate() {
|
if form.name.chars().count() < 3 {
|
||||||
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
return Ok(HttpResponse::UnprocessableEntity().body("Name der Bekleidung ist zu kurz."));
|
||||||
};
|
};
|
||||||
|
|
||||||
clothing.name = form.name.to_string();
|
clothing.name = form.name.to_string();
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
use garde::Validate;
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -18,8 +17,8 @@ pub async fn post(
|
|||||||
return Err(ApplicationError::Unauthorized);
|
return Err(ApplicationError::Unauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = form.validate() {
|
if form.name.chars().count() < 3 {
|
||||||
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
return Ok(HttpResponse::UnprocessableEntity().body("Name der Bekleidung ist zu kurz."));
|
||||||
};
|
};
|
||||||
|
|
||||||
let clothing = Clothing::create(pool.get_ref(), &form.name).await?;
|
let clothing = Clothing::create(pool.get_ref(), &form.name).await?;
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||||
use chrono::Days;
|
|
||||||
use garde::Validate;
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{events::NewOrEditEventForm, IdPath},
|
endpoints::{events::NewOrEditEventForm, IdPath},
|
||||||
utils::{self, ApplicationError},
|
utils::{self, ApplicationError},
|
||||||
END_OF_DAY, START_OF_DAY,
|
|
||||||
};
|
};
|
||||||
use brass_db::models::{
|
use brass_db::{
|
||||||
Assignment, AssignmentChangeset, Availability, Event, EventChangeset, EventContext, Function,
|
models::{Assignment, AssignmentChangeset, Event, EventChangeset, EventContext, Role, User},
|
||||||
Location, Role, User,
|
validation::AsyncValidate,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[actix_web::post("/events/{id}/edit")]
|
#[actix_web::post("/events/{id}/edit")]
|
||||||
@ -28,21 +25,6 @@ pub async fn post(
|
|||||||
return Ok(HttpResponse::NotFound().finish());
|
return Ok(HttpResponse::NotFound().finish());
|
||||||
};
|
};
|
||||||
|
|
||||||
if event.location_id != form.location {
|
|
||||||
let Some(location) = Location::read_by_id(pool.get_ref(), form.location).await? else {
|
|
||||||
return Ok(HttpResponse::BadRequest().body("Location does not exist"));
|
|
||||||
};
|
|
||||||
|
|
||||||
if user.role != Role::Admin && user.area_id != location.area_id {
|
|
||||||
return Ok(HttpResponse::BadRequest().body("Can't use location outside of your area"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.location.as_ref().unwrap().area_id != location.area_id {
|
|
||||||
return Ok(HttpResponse::BadRequest()
|
|
||||||
.body("Can't change to a location outside of previous location area"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let changeset = EventChangeset {
|
let changeset = EventChangeset {
|
||||||
amount_of_posten: form.amount,
|
amount_of_posten: form.amount,
|
||||||
clothing: form.clothing,
|
clothing: form.clothing,
|
||||||
@ -57,85 +39,18 @@ pub async fn post(
|
|||||||
voluntary_fuehrungsassistent: form.voluntaryfuehrungsassistent,
|
voluntary_fuehrungsassistent: form.voluntaryfuehrungsassistent,
|
||||||
};
|
};
|
||||||
|
|
||||||
let assignments_for_event = Assignment::read_all_by_event(pool.get_ref(), event.id).await?;
|
let context = EventContext {
|
||||||
|
event: Some(event.id),
|
||||||
|
pool: pool.get_ref(),
|
||||||
|
user: &user.into_inner(),
|
||||||
|
};
|
||||||
|
|
||||||
let start = event.start.date();
|
if let Err(e) = changeset.validate_with_context(&context).await {
|
||||||
let end = event.start.date().checked_add_days(Days::new(1)).unwrap();
|
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
||||||
let mut common_time = (start.and_time(START_OF_DAY), end.and_time(END_OF_DAY));
|
|
||||||
|
|
||||||
for assignment in &assignments_for_event {
|
|
||||||
let availability = Availability::read_by_id(pool.get_ref(), assignment.availability_id)
|
|
||||||
.await?
|
|
||||||
.unwrap();
|
|
||||||
let all_assignments =
|
|
||||||
Assignment::read_all_by_availability(pool.get_ref(), assignment.availability_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if all_assignments.len() == 1 {
|
|
||||||
if availability.start > common_time.0 {
|
|
||||||
common_time.0 = availability.start;
|
|
||||||
}
|
|
||||||
|
|
||||||
if availability.end < common_time.1 {
|
|
||||||
common_time.1 = availability.end;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut slots = vec![(availability.start, availability.end)];
|
|
||||||
for a in all_assignments
|
|
||||||
.iter()
|
|
||||||
.filter(|x| x.event_id != assignment.event_id)
|
|
||||||
{
|
|
||||||
let (fit, rest) = slots
|
|
||||||
.into_iter()
|
|
||||||
.partition(|s| s.0 >= a.start && s.1 <= a.end);
|
|
||||||
slots = rest;
|
|
||||||
let fit = fit.first().unwrap();
|
|
||||||
|
|
||||||
if fit.0 != a.start {
|
|
||||||
slots.push((fit.0, a.start));
|
|
||||||
}
|
|
||||||
|
|
||||||
if fit.1 != a.end {
|
|
||||||
slots.push((a.end, fit.1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let slot = slots
|
|
||||||
.into_iter()
|
|
||||||
.find(|s| s.0 >= assignment.start && s.1 <= assignment.end)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if slot.0 > common_time.0 {
|
|
||||||
common_time.0 = slot.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if slot.1 < common_time.1 {
|
|
||||||
common_time.1 = slot.1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let context = Some(EventContext {
|
|
||||||
date_in_db: event.start.date(),
|
|
||||||
common_min_max_available_time: common_time,
|
|
||||||
// safe as max amount of posten can be only 100
|
|
||||||
amount_of_assigned_posten: assignments_for_event
|
|
||||||
.iter()
|
|
||||||
.filter(|a| a.function == Function::Posten)
|
|
||||||
.count() as i16,
|
|
||||||
wachhabender_assigned: assignments_for_event
|
|
||||||
.iter()
|
|
||||||
.any(|a| a.function == Function::Wachhabender),
|
|
||||||
fuehrungsassistent_assigned: assignments_for_event
|
|
||||||
.iter()
|
|
||||||
.any(|a| a.function == Function::Fuehrungsassistent),
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Err(e) = changeset.validate_with(&context) {
|
|
||||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if event.start != changeset.time.0 || event.end != changeset.time.1 {
|
if event.start != changeset.time.0 || event.end != changeset.time.1 {
|
||||||
|
let assignments_for_event = Assignment::read_all_by_event(pool.get_ref(), event.id).await?;
|
||||||
for a in assignments_for_event {
|
for a in assignments_for_event {
|
||||||
let c = AssignmentChangeset {
|
let c = AssignmentChangeset {
|
||||||
function: a.function,
|
function: a.function,
|
||||||
@ -148,7 +63,7 @@ pub async fn post(
|
|||||||
Event::update(pool.get_ref(), event.id, changeset).await?;
|
Event::update(pool.get_ref(), event.id, changeset).await?;
|
||||||
|
|
||||||
let url = utils::get_return_url_for_date(&form.date);
|
let url = utils::get_return_url_for_date(&form.date);
|
||||||
//println!("redirecto to {url}");
|
|
||||||
Ok(HttpResponse::Found()
|
Ok(HttpResponse::Found()
|
||||||
.insert_header((LOCATION, url.clone()))
|
.insert_header((LOCATION, url.clone()))
|
||||||
.insert_header(("HX-LOCATION", url))
|
.insert_header(("HX-LOCATION", url))
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||||
use garde::Validate;
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::events::NewOrEditEventForm,
|
endpoints::events::NewOrEditEventForm,
|
||||||
utils::{self, ApplicationError},
|
utils::{self, ApplicationError},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Event, EventChangeset, Location, Role, User};
|
use brass_db::{
|
||||||
|
models::{Event, EventChangeset, EventContext, Role, User},
|
||||||
|
validation::AsyncValidate,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_web::post("/events/new")]
|
#[actix_web::post("/events/new")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
@ -18,14 +20,6 @@ pub async fn post(
|
|||||||
return Err(ApplicationError::Unauthorized);
|
return Err(ApplicationError::Unauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(location) = Location::read_by_id(pool.get_ref(), form.location).await? else {
|
|
||||||
return Ok(HttpResponse::BadRequest().body("Location does not exist"));
|
|
||||||
};
|
|
||||||
|
|
||||||
if user.role != Role::Admin && user.area_id != location.area_id {
|
|
||||||
return Ok(HttpResponse::BadRequest().body("Can't use location outside of your area"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let changeset = EventChangeset {
|
let changeset = EventChangeset {
|
||||||
amount_of_posten: form.amount,
|
amount_of_posten: form.amount,
|
||||||
clothing: form.clothing,
|
clothing: form.clothing,
|
||||||
@ -40,14 +34,20 @@ pub async fn post(
|
|||||||
voluntary_fuehrungsassistent: form.voluntaryfuehrungsassistent,
|
voluntary_fuehrungsassistent: form.voluntaryfuehrungsassistent,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = changeset.validate_with(&None) {
|
let event_context = EventContext {
|
||||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
event: None,
|
||||||
|
user: &user.into_inner(),
|
||||||
|
pool: pool.get_ref(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = changeset.validate_with_context(&event_context).await {
|
||||||
|
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
||||||
};
|
};
|
||||||
|
|
||||||
Event::create(pool.get_ref(), changeset).await?;
|
Event::create(pool.get_ref(), changeset).await?;
|
||||||
|
|
||||||
let url = utils::get_return_url_for_date(&form.date);
|
let url = utils::get_return_url_for_date(&form.date);
|
||||||
//println!("redirecto to {url}");
|
|
||||||
Ok(HttpResponse::Found()
|
Ok(HttpResponse::Found()
|
||||||
.insert_header((LOCATION, url.clone()))
|
.insert_header((LOCATION, url.clone()))
|
||||||
.insert_header(("HX-LOCATION", url))
|
.insert_header(("HX-LOCATION", url))
|
||||||
|
@ -7,7 +7,6 @@ use actix_web::{
|
|||||||
test::init_service,
|
test::init_service,
|
||||||
};
|
};
|
||||||
use rand::{distr::Alphanumeric, rng, Rng};
|
use rand::{distr::Alphanumeric, rng, Rng};
|
||||||
use tracing_subscriber::{fmt, layer::SubscriberExt, registry, util::SubscriberInitExt, EnvFilter};
|
|
||||||
|
|
||||||
use crate::utils::Customization;
|
use crate::utils::Customization;
|
||||||
use crate::{create_app, mail::Mailer};
|
use crate::{create_app, mail::Mailer};
|
||||||
@ -33,7 +32,7 @@ impl DbTestContext {
|
|||||||
> {
|
> {
|
||||||
let customization = Customization {
|
let customization = Customization {
|
||||||
webmaster_email: self.config.webmaster_email.clone(),
|
webmaster_email: self.config.webmaster_email.clone(),
|
||||||
hostname: self.config.hostname.clone(),
|
hostname: self.config.hostname.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
init_service(create_app(
|
init_service(create_app(
|
||||||
@ -51,13 +50,6 @@ pub async fn setup() -> DbTestContext {
|
|||||||
let init_config: OnceCell<Config> = OnceCell::new();
|
let init_config: OnceCell<Config> = OnceCell::new();
|
||||||
let config = init_config.get_or_init(|| load_config(&Environment::Test).unwrap());
|
let config = init_config.get_or_init(|| load_config(&Environment::Test).unwrap());
|
||||||
|
|
||||||
// TODO: unite with init_tracing in main
|
|
||||||
let filter = EnvFilter::try_from_default_env()
|
|
||||||
.or_else(|_| EnvFilter::try_new("info"))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
registry().with(fmt::layer()).with(filter).init();
|
|
||||||
|
|
||||||
let test_db_config = prepare_db(&config.database_url).await;
|
let test_db_config = prepare_db(&config.database_url).await;
|
||||||
|
|
||||||
let test_db_pool = PgPoolOptions::new()
|
let test_db_pool = PgPoolOptions::new()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user