refactor: clothing and event changeset

This commit is contained in:
Max Hohlfeld 2025-07-02 18:56:40 +02:00
parent bdaf8ff20e
commit 428f46b853
13 changed files with 251 additions and 288 deletions

56
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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,

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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"

View File

@ -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());

View File

@ -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,
} }

View File

@ -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();

View File

@ -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?;

View File

@ -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))

View File

@ -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))

View File

@ -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()