Compare commits
No commits in common. "feature/db-crate" and "master" have entirely different histories.
feature/db
...
master
@ -16,4 +16,3 @@ SMTP_PORT="1025"
|
|||||||
# SMTP_LOGIN=""
|
# SMTP_LOGIN=""
|
||||||
# SMTP_PASSWORD=""
|
# SMTP_PASSWORD=""
|
||||||
SMTP_TLSTYPE="none"
|
SMTP_TLSTYPE="none"
|
||||||
RUST_LOG="info,brass_web=trace,brass_db=trace"
|
|
||||||
|
70
Cargo.lock
generated
70
Cargo.lock
generated
@ -775,7 +775,6 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-std",
|
"async-std",
|
||||||
"brass-config",
|
"brass-config",
|
||||||
"chrono",
|
|
||||||
"clap",
|
"clap",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
]
|
]
|
||||||
@ -788,19 +787,6 @@ dependencies = [
|
|||||||
"dotenvy",
|
"dotenvy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "brass-db"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"fake",
|
|
||||||
"rand 0.9.1",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"sqlx",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brass-macros"
|
name = "brass-macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -823,13 +809,13 @@ dependencies = [
|
|||||||
"argon2",
|
"argon2",
|
||||||
"askama",
|
"askama",
|
||||||
"brass-config",
|
"brass-config",
|
||||||
"brass-db",
|
|
||||||
"brass-macros",
|
"brass-macros",
|
||||||
"built",
|
"built",
|
||||||
"change-detection",
|
"change-detection",
|
||||||
"chrono",
|
"chrono",
|
||||||
"fake",
|
"fake",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"garde",
|
||||||
"insta",
|
"insta",
|
||||||
"lettre",
|
"lettre",
|
||||||
"maud",
|
"maud",
|
||||||
@ -904,6 +890,15 @@ 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"
|
||||||
@ -1012,6 +1007,20 @@ 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"
|
||||||
@ -1634,6 +1643,31 @@ 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"
|
||||||
@ -3349,6 +3383,12 @@ 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"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [ "cli", "config", "db", "macros", "web", ]
|
members = [ "cli", "config", "macros", "web", ]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
default-members = ["web"]
|
default-members = ["web"]
|
||||||
|
|
||||||
|
@ -15,4 +15,3 @@ brass-config = { path = "../config" }
|
|||||||
async-std = { version = "1.13.0", features = ["attributes"] }
|
async-std = { version = "1.13.0", features = ["attributes"] }
|
||||||
sqlx = { version = "0.8.2", features = ["runtime-async-std", "postgres"] }
|
sqlx = { version = "0.8.2", features = ["runtime-async-std", "postgres"] }
|
||||||
anyhow = "1.0.94"
|
anyhow = "1.0.94"
|
||||||
chrono = "0.4.41"
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::Local;
|
|
||||||
use sqlx::migrate::Migrate;
|
use sqlx::migrate::Migrate;
|
||||||
use sqlx::{migrate::Migrator, Executor};
|
use sqlx::{migrate::Migrator, Executor};
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -31,12 +28,9 @@ enum Command {
|
|||||||
Reset,
|
Reset,
|
||||||
#[command(about = "Run all pending migrations on database")]
|
#[command(about = "Run all pending migrations on database")]
|
||||||
Migrate,
|
Migrate,
|
||||||
#[command(about = "Create a new migration")]
|
|
||||||
NewMigration { title: String },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
#[allow(unused)]
|
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
let config = load_config(&cli.environment).expect("Could not load config!");
|
let config = load_config(&cli.environment).expect("Could not load config!");
|
||||||
@ -65,17 +59,12 @@ async fn main() {
|
|||||||
migrate_db(&db_config)
|
migrate_db(&db_config)
|
||||||
.await
|
.await
|
||||||
.expect("Failed migrating database.");
|
.expect("Failed migrating database.");
|
||||||
}
|
},
|
||||||
Command::Migrate => {
|
Command::Migrate => {
|
||||||
migrate_db(&db_config)
|
migrate_db(&db_config)
|
||||||
.await
|
.await
|
||||||
.expect("Failed migrating database.");
|
.expect("Failed migrating database.");
|
||||||
}
|
}
|
||||||
Command::NewMigration { title } => {
|
|
||||||
create_new_migration(&title)
|
|
||||||
.await
|
|
||||||
.expect("Failed creating new migration.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +111,13 @@ async fn migrate_db(db_config: &PgConnectOptions) -> anyhow::Result<()> {
|
|||||||
.await
|
.await
|
||||||
.context("Connection to database failed!")?;
|
.context("Connection to database failed!")?;
|
||||||
|
|
||||||
let migrations_path = db_package_root()?.join("migrations");
|
let migrations_path = PathBuf::from(
|
||||||
|
std::env::var("CARGO_MANIFEST_DIR").expect("This command needs to be invoked using cargo"),
|
||||||
|
)
|
||||||
|
.join("..")
|
||||||
|
.join("migrations")
|
||||||
|
.canonicalize()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let migrator = Migrator::new(Path::new(&migrations_path))
|
let migrator = Migrator::new(Path::new(&migrations_path))
|
||||||
.await
|
.await
|
||||||
@ -153,27 +148,3 @@ async fn migrate_db(db_config: &PgConnectOptions) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_new_migration(title: &str) -> anyhow::Result<()> {
|
|
||||||
let now = Local::now();
|
|
||||||
let timestamp = now.format("%Y%m%d%H%M%S");
|
|
||||||
let file_name = format!("{timestamp}_{title}.sql");
|
|
||||||
let path = db_package_root()?.join("migrations").join(&file_name);
|
|
||||||
|
|
||||||
let mut file = File::create(&path).context(format!(r#"Could not create file "{:?}""#, path))?;
|
|
||||||
file.write_all("".as_bytes())
|
|
||||||
.context(format!(r#"Could not write file "{:?}""#, path))?;
|
|
||||||
|
|
||||||
println!("Created migration {file_name}.");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn db_package_root() -> Result<PathBuf, anyhow::Error> {
|
|
||||||
Ok(PathBuf::from(
|
|
||||||
std::env::var("CARGO_MANIFEST_DIR").expect("This command needs to be invoked using cargo"),
|
|
||||||
)
|
|
||||||
.join("..")
|
|
||||||
.join("db")
|
|
||||||
.canonicalize()?)
|
|
||||||
}
|
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "brass-db"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
license = "AGPL-3.0"
|
|
||||||
authors = ["Max Hohlfeld <maxhohlfeld@posteo.de>"]
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
sqlx = { version = "^0.8", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
|
|
||||||
chrono = { version = "0.4.33", features = ["serde", "now"] }
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
rand = { version = "0.9", features = ["os_rng"] }
|
|
||||||
regex = "1.11.1"
|
|
||||||
tracing = "0.1.41"
|
|
||||||
fake = { version = "4", features = ["chrono", "derive"], optional = true}
|
|
||||||
|
|
||||||
[features]
|
|
||||||
test-helpers = ["dep:fake"]
|
|
@ -1,31 +0,0 @@
|
|||||||
pub mod models;
|
|
||||||
mod support;
|
|
||||||
pub mod validation;
|
|
||||||
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use chrono::NaiveTime;
|
|
||||||
|
|
||||||
pub use support::{NoneToken, Token};
|
|
||||||
|
|
||||||
const START_OF_DAY: NaiveTime = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
|
|
||||||
const END_OF_DAY: NaiveTime = NaiveTime::from_hms_opt(23, 59, 59).unwrap();
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct UnsupportedEnumValue {
|
|
||||||
pub value: u8,
|
|
||||||
pub enum_name: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for UnsupportedEnumValue {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"unsupported enum value '{}' given for enum '{}'",
|
|
||||||
self.value, self.enum_name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for UnsupportedEnumValue {}
|
|
@ -1,186 +0,0 @@
|
|||||||
use chrono::NaiveDateTime;
|
|
||||||
use sqlx::PgPool;
|
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
use crate::validation::{
|
|
||||||
AsyncValidate, AsyncValidateError, start_date_time_lies_before_end_date_time,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{Assignment, Availability, Event, Function, Role, User};
|
|
||||||
|
|
||||||
pub struct AssignmentChangeset {
|
|
||||||
pub function: Function,
|
|
||||||
pub time: (NaiveDateTime, NaiveDateTime),
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
|
||||||
"Angegebene Veranstaltung 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),
|
|
||||||
availability: &Availability,
|
|
||||||
) -> Result<(), AsyncValidateError> {
|
|
||||||
if value.0 < availability.start || value.1 > availability.end {
|
|
||||||
return Err(AsyncValidateError::new(
|
|
||||||
"Die verfügbar gemachte Zeit passt nicht zu der zugewiesenen Zeit für die Veranstaltung.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn user_of_availability_has_function(
|
|
||||||
value: &Function,
|
|
||||||
availability: &Availability,
|
|
||||||
) -> Result<(), AsyncValidateError> {
|
|
||||||
let user_function = &availability.user.as_ref().unwrap().function;
|
|
||||||
|
|
||||||
if !user_function.contains(value) {
|
|
||||||
return Err(AsyncValidateError::new(
|
|
||||||
"Nutzer der Verfügbarkeit besitzt nicht die benötigte Funktion um für diese Position zugewiesen zu werden.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn event_has_free_slot_for_function(
|
|
||||||
value: &Function,
|
|
||||||
availability: &Availability,
|
|
||||||
event: &Event,
|
|
||||||
pool: &PgPool,
|
|
||||||
) -> Result<(), AsyncValidateError> {
|
|
||||||
debug!(?event, "event parameter");
|
|
||||||
let assignments_for_event: Vec<Assignment> = Assignment::read_all_by_event(pool, event.id)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.filter(|a| a.availability_id != availability.id)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
debug!(?assignments_for_event, "existing assignments for event");
|
|
||||||
|
|
||||||
let assignments_with_function = assignments_for_event
|
|
||||||
.iter()
|
|
||||||
.filter(|a| a.function == *value)
|
|
||||||
.count();
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
assignments_with_function,
|
|
||||||
"amount of existing assignments for function"
|
|
||||||
);
|
|
||||||
|
|
||||||
if match *value {
|
|
||||||
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(AsyncValidateError::new(
|
|
||||||
"Veranstaltung hat bereits genug Zuweisungen für diese Funktion.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn availability_not_already_assigned(
|
|
||||||
time: &(NaiveDateTime, NaiveDateTime),
|
|
||||||
availability: &Availability,
|
|
||||||
event: &Event,
|
|
||||||
pool: &PgPool,
|
|
||||||
) -> Result<(), AsyncValidateError> {
|
|
||||||
let list: Vec<Assignment> = Assignment::read_all_by_availability(pool, availability.id)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.filter(|a| a.event_id != event.id)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
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(AsyncValidateError::new(
|
|
||||||
"Die Verfügbarkeit des Nutzers wurde bereits zu einer anderen Veranstaltung zugewiesen.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: maybe merge with event changeset
|
|
||||||
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(
|
|
||||||
"Du verfügst nicht über die Berechtigung, Zuweisungen zu Veranstaltungen vorzunehmen.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,248 +0,0 @@
|
|||||||
use chrono::Days;
|
|
||||||
use chrono::NaiveDate;
|
|
||||||
use chrono::NaiveDateTime;
|
|
||||||
#[cfg(feature = "test-helpers")]
|
|
||||||
use fake::{Fake, Faker};
|
|
||||||
use sqlx::PgPool;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
pub struct EventChangeset {
|
|
||||||
pub time: (NaiveDateTime, NaiveDateTime),
|
|
||||||
pub name: String,
|
|
||||||
pub location_id: i32,
|
|
||||||
pub voluntary_wachhabender: bool,
|
|
||||||
pub voluntary_fuehrungsassistent: bool,
|
|
||||||
pub amount_of_posten: i16,
|
|
||||||
pub clothing: i32,
|
|
||||||
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")]
|
|
||||||
impl EventChangeset {
|
|
||||||
pub fn create_for_test(start: NaiveDateTime, end: NaiveDateTime) -> EventChangeset {
|
|
||||||
let changeset = EventChangeset {
|
|
||||||
time: (start, end),
|
|
||||||
name: Faker.fake(),
|
|
||||||
location_id: 1,
|
|
||||||
voluntary_wachhabender: true,
|
|
||||||
voluntary_fuehrungsassistent: true,
|
|
||||||
amount_of_posten: 5,
|
|
||||||
clothing: 1,
|
|
||||||
note: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
changeset
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
#[cfg(feature = "test-helpers")]
|
|
||||||
use fake::{Dummy, faker::internet::en::SafeEmail, faker::name::en::Name};
|
|
||||||
|
|
||||||
use sqlx::PgPool;
|
|
||||||
|
|
||||||
use super::{Area, Function, Role};
|
|
||||||
use crate::validation::{AsyncValidate, AsyncValidateError, DbContext, email_is_valid};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "test-helpers", derive(Dummy))]
|
|
||||||
pub struct UserChangeset {
|
|
||||||
#[cfg_attr(feature = "test-helpers", dummy(faker = "Name()"))]
|
|
||||||
pub name: String,
|
|
||||||
#[cfg_attr(feature = "test-helpers", dummy(faker = "SafeEmail()"))]
|
|
||||||
pub email: String,
|
|
||||||
#[cfg_attr(feature = "test-helpers", dummy(expr = "Role::Staff"))]
|
|
||||||
pub role: Role,
|
|
||||||
#[cfg_attr(feature = "test-helpers", dummy(expr = "vec![Function::Posten]"))]
|
|
||||||
pub functions: Vec<Function>,
|
|
||||||
#[cfg_attr(feature = "test-helpers", dummy(expr = "1"))]
|
|
||||||
pub area_id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> AsyncValidate<'a> for UserChangeset {
|
|
||||||
type Context = DbContext<'a>;
|
|
||||||
|
|
||||||
async fn validate_with_context(
|
|
||||||
&self,
|
|
||||||
context: &'a Self::Context,
|
|
||||||
) -> Result<(), AsyncValidateError> {
|
|
||||||
email_is_valid(&self.email)?;
|
|
||||||
area_exists(context.pool, self.area_id).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn area_exists(pool: &PgPool, id: i32) -> Result<(), AsyncValidateError> {
|
|
||||||
if Area::read_by_id(pool, id).await?.is_none() {
|
|
||||||
return Err(AsyncValidateError::new(
|
|
||||||
"Angegebener Bereich für Nutzer existiert nicht!",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
mod token_generation;
|
|
||||||
mod token_trait;
|
|
||||||
|
|
||||||
pub use token_generation::generate_token_and_expiration;
|
|
||||||
pub use token_trait::{Token, NoneToken};
|
|
@ -1,18 +0,0 @@
|
|||||||
use chrono::{NaiveDateTime, TimeDelta, Utc};
|
|
||||||
use rand::{Rng, distr::Alphanumeric, rng};
|
|
||||||
|
|
||||||
pub fn generate_token_and_expiration(
|
|
||||||
token_length_bytes: usize,
|
|
||||||
validity: TimeDelta,
|
|
||||||
) -> (String, NaiveDateTime) {
|
|
||||||
let value = std::iter::repeat(())
|
|
||||||
.map(|()| rng().sample(Alphanumeric))
|
|
||||||
.take(token_length_bytes)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let token = String::from_utf8(value).unwrap();
|
|
||||||
|
|
||||||
let expires = Utc::now().naive_utc() + validity;
|
|
||||||
|
|
||||||
(token, expires)
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
use sqlx::PgPool;
|
|
||||||
|
|
||||||
pub trait Token {
|
|
||||||
fn delete(&self, pool: &PgPool) -> impl Future<Output = Result<(), sqlx::Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NoneToken {}
|
|
||||||
impl Token for NoneToken {
|
|
||||||
async fn delete(&self, _pool: &PgPool) -> Result<(), sqlx::Error> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
use super::AsyncValidateError;
|
|
||||||
|
|
||||||
pub trait AsyncValidate<'a> {
|
|
||||||
type Context: 'a;
|
|
||||||
|
|
||||||
fn validate_with_context(
|
|
||||||
&self,
|
|
||||||
context: &'a Self::Context,
|
|
||||||
) -> impl Future<Output = Result<(), AsyncValidateError>>;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
imports_granularity = "Crate"
|
|
@ -28,15 +28,16 @@ zxcvbn = "3.1.0"
|
|||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
brass-macros = { path = "../macros" }
|
brass-macros = { path = "../macros" }
|
||||||
brass-config = { path = "../config" }
|
brass-config = { path = "../config" }
|
||||||
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"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
tracing-panic = "0.1.2"
|
tracing-panic = "0.1.2"
|
||||||
rust_xlsxwriter = "0.87.0"
|
rust_xlsxwriter = "0.87.0"
|
||||||
|
regex = "1.11.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = "0.7.4"
|
built = "0.7.4"
|
||||||
@ -46,5 +47,4 @@ change-detection = "1.2.0"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = { version = "1.41.1", features = ["yaml", "filters"] }
|
insta = { version = "1.41.1", features = ["yaml", "filters"] }
|
||||||
fake = { version = "4", features = ["chrono", "derive"]}
|
fake = { version = "4", features = ["chrono", "derive"]}
|
||||||
brass-db = { path = "../db", features = ["test-helpers"] }
|
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
---
|
|
||||||
source: web/src/endpoints/assignment/post_new.rs
|
|
||||||
expression: body
|
|
||||||
snapshot_kind: text
|
|
||||||
---
|
|
||||||
<table class="table is-fullwidth">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Funktion</th>
|
|
||||||
<th>Zeitraum</th>
|
|
||||||
<th>Kommentar</th>
|
|
||||||
<th>Planung</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>Max Mustermann</td>
|
|
||||||
<td>
|
|
||||||
<div class="tags"><span class="tag is-primary is-light">Posten</span></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
10:00 bis 10.01.2025 20:00
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="dropdown">
|
|
||||||
<div class="dropdown-trigger">
|
|
||||||
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
|
|
||||||
|
|
||||||
<span>als Posten geplant</span>
|
|
||||||
<svg class="icon">
|
|
||||||
<use href="/static/feather-sprite.svg#edit-2" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown-menu" id="dropdown-menu" role="menu">
|
|
||||||
<div class="dropdown-content" hx-target="closest table" hx-swap="outerHTML">
|
|
||||||
|
|
||||||
<a class="dropdown-item"
|
|
||||||
hx-post="/assignments/new?event=1&availability=1&function=1" disabled>
|
|
||||||
als Posten planen</a>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<hr class="dropdown-divider" />
|
|
||||||
<a class="dropdown-item"
|
|
||||||
hx-delete="/assignments/delete?event=1&availability=1"
|
|
||||||
class="button is-small">entplanen</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
@ -1,8 +1,11 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
use brass_db::models::{Area, Role, User};
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::IdPath, utils::ApplicationError};
|
use crate::{
|
||||||
|
endpoints::IdPath,
|
||||||
|
models::{Area, Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_web::delete("/area/delete/{id}")]
|
#[actix_web::delete("/area/delete/{id}")]
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
@ -25,8 +28,10 @@ pub async fn delete(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::test_helper::{test_delete, DbTestContext, RequestConfig, StatusCode};
|
use crate::{
|
||||||
use brass_db::models::{Area, Function, Location, Role};
|
models::{Area, Function, Location, Role},
|
||||||
|
utils::test_helper::{test_delete, DbTestContext, RequestConfig, StatusCode},
|
||||||
|
};
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{area::NewOrEditAreaTemplate, IdPath},
|
endpoints::{area::NewOrEditAreaTemplate, IdPath},
|
||||||
|
models::{Area, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Area, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/area/edit/{id}")]
|
#[actix_web::get("/area/edit/{id}")]
|
||||||
async fn get(
|
async fn get(
|
||||||
@ -32,18 +32,23 @@ async fn get(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_http::StatusCode;
|
use actix_http::StatusCode;
|
||||||
use brass_db::models::Role;
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
use crate::utils::test_helper::{
|
use crate::{
|
||||||
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig,
|
models::{Function, Role},
|
||||||
|
utils::test_helper::{assert_snapshot, read_body, test_get, DbTestContext, RequestConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
async fn produces_template_when_area_exists_and_user_is_admin(context: &DbTestContext) {
|
async fn produces_template_when_area_exists_and_user_is_admin(context: &DbTestContext) {
|
||||||
let app = context.app().await;
|
let app = context.app().await;
|
||||||
|
|
||||||
let config = RequestConfig::new("/area/edit/1").with_role(Role::Admin);
|
let config = RequestConfig {
|
||||||
|
uri: "/area/edit/1".to_string(),
|
||||||
|
role: Role::Admin,
|
||||||
|
function: vec![Function::Posten],
|
||||||
|
user_area: 1,
|
||||||
|
};
|
||||||
let response = test_get(&context.db_pool, app, &config).await;
|
let response = test_get(&context.db_pool, app, &config).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::OK, response.status());
|
assert_eq!(StatusCode::OK, response.status());
|
||||||
@ -56,7 +61,12 @@ mod tests {
|
|||||||
async fn returns_unauthorized_when_user_is_not_admin(context: &DbTestContext) {
|
async fn returns_unauthorized_when_user_is_not_admin(context: &DbTestContext) {
|
||||||
let app = context.app().await;
|
let app = context.app().await;
|
||||||
|
|
||||||
let config = RequestConfig::new("/area/edit/1").with_role(Role::AreaManager);
|
let config = RequestConfig {
|
||||||
|
uri: "/area/edit/1".to_string(),
|
||||||
|
role: Role::AreaManager,
|
||||||
|
function: vec![Function::Posten],
|
||||||
|
user_area: 1,
|
||||||
|
};
|
||||||
let response = test_get(&context.db_pool, app, &config).await;
|
let response = test_get(&context.db_pool, app, &config).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
|
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
|
||||||
@ -66,7 +76,12 @@ mod tests {
|
|||||||
async fn returns_not_found_when_area_does_not_exist(context: &DbTestContext) {
|
async fn returns_not_found_when_area_does_not_exist(context: &DbTestContext) {
|
||||||
let app = context.app().await;
|
let app = context.app().await;
|
||||||
|
|
||||||
let config = RequestConfig::new("/area/edit/2").with_role(Role::Admin);
|
let config = RequestConfig {
|
||||||
|
uri: "/area/edit/2".to_string(),
|
||||||
|
role: Role::Admin,
|
||||||
|
function: vec![Function::Posten],
|
||||||
|
user_area: 1,
|
||||||
|
};
|
||||||
let response = test_get(&context.db_pool, app, &config).await;
|
let response = test_get(&context.db_pool, app, &config).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::NOT_FOUND, response.status());
|
assert_eq!(StatusCode::NOT_FOUND, response.status());
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
endpoints::area::NewOrEditAreaTemplate,
|
endpoints::area::NewOrEditAreaTemplate,
|
||||||
|
models::{Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use actix_web::{web, Responder};
|
use actix_web::{web, Responder};
|
||||||
use brass_db::models::{Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/area/new")]
|
#[actix_web::get("/area/new")]
|
||||||
async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
|
async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
|
||||||
@ -21,13 +21,13 @@ async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationErro
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use brass_macros::db_test;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
models::{Function, Role},
|
||||||
utils::test_helper::{
|
utils::test_helper::{
|
||||||
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Function, Role};
|
|
||||||
use brass_macros::db_test;
|
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
async fn produces_template_when_user_is_admin(context: &DbTestContext) {
|
async fn produces_template_when_user_is_admin(context: &DbTestContext) {
|
||||||
|
@ -7,7 +7,7 @@ pub mod delete;
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use brass_db::models::{Area, Role, User};
|
use crate::models::{Area, Role, User};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[cfg_attr(not(test), template(path = "area/new_or_edit.html"))]
|
#[cfg_attr(not(test), template(path = "area/new_or_edit.html"))]
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::IdPath, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Area, Role, User};
|
endpoints::IdPath,
|
||||||
|
models::{Area, Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
use super::AreaForm;
|
use super::AreaForm;
|
||||||
|
|
||||||
@ -32,11 +35,11 @@ pub async fn post(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_http::StatusCode;
|
use actix_http::StatusCode;
|
||||||
use brass_db::models::{Area, Function, Role};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::area::AreaForm,
|
endpoints::area::AreaForm,
|
||||||
|
models::{Area, Function, Role},
|
||||||
utils::test_helper::{test_post, DbTestContext, RequestConfig},
|
utils::test_helper::{test_post, DbTestContext, RequestConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,7 +58,7 @@ mod tests {
|
|||||||
name: "Neuer Name".to_string(),
|
name: "Neuer Name".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(request)).await;
|
let response = test_post(&context.db_pool, app, &config, request).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::FOUND, response.status());
|
assert_eq!(StatusCode::FOUND, response.status());
|
||||||
|
|
||||||
@ -82,7 +85,7 @@ mod tests {
|
|||||||
name: "Neuer Name".to_string(),
|
name: "Neuer Name".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(request)).await;
|
let response = test_post(&context.db_pool, app, &config, request).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
|
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
|
||||||
}
|
}
|
||||||
@ -102,7 +105,7 @@ mod tests {
|
|||||||
name: "Neuer Name".to_string(),
|
name: "Neuer Name".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(request)).await;
|
let response = test_post(&context.db_pool, app, &config, request).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::NOT_FOUND, response.status());
|
assert_eq!(StatusCode::NOT_FOUND, response.status());
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::area::AreaForm, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Area, Role, User};
|
endpoints::area::AreaForm,
|
||||||
|
models::{Area, Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_web::post("/area/new")]
|
#[actix_web::post("/area/new")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
@ -25,11 +28,11 @@ pub async fn post(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_http::StatusCode;
|
use actix_http::StatusCode;
|
||||||
use brass_db::models::{Area, Function, Role};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::area::AreaForm,
|
endpoints::area::AreaForm,
|
||||||
|
models::{Area, Function, Role},
|
||||||
utils::test_helper::{test_post, DbTestContext, RequestConfig},
|
utils::test_helper::{test_post, DbTestContext, RequestConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,7 +51,7 @@ mod tests {
|
|||||||
name: "Neuer Name".to_string(),
|
name: "Neuer Name".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(request)).await;
|
let response = test_post(&context.db_pool, app, &config, request).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::FOUND, response.status());
|
assert_eq!(StatusCode::FOUND, response.status());
|
||||||
|
|
||||||
@ -75,7 +78,7 @@ mod tests {
|
|||||||
name: "Neuer Name".to_string(),
|
name: "Neuer Name".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(request)).await;
|
let response = test_post(&context.db_pool, app, &config, request).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
|
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::assignment::PlanEventPersonalTablePartialTemplate,
|
endpoints::assignment::PlanEventPersonalTablePartialTemplate,
|
||||||
|
models::{Assignment, Event, Role, User},
|
||||||
utils::{
|
utils::{
|
||||||
event_planning_template::{
|
event_planning_template::{
|
||||||
generate_availability_assignment_list, generate_status_whether_staff_is_required,
|
generate_availability_assignment_list, generate_status_whether_staff_is_required,
|
||||||
@ -11,7 +12,6 @@ use crate::{
|
|||||||
ApplicationError, TemplateResponse,
|
ApplicationError, TemplateResponse,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Assignment, Event, Role, User};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AssignmentDeleteQuery {
|
struct AssignmentDeleteQuery {
|
||||||
@ -71,16 +71,18 @@ mod tests {
|
|||||||
use fake::{faker::chrono::en::Date, Fake, Faker};
|
use fake::{faker::chrono::en::Date, Fake, Faker};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::test_helper::{test_delete, DbTestContext, RequestConfig};
|
use crate::{
|
||||||
use brass_db::models::{
|
models::{
|
||||||
Assignment, AssignmentChangeset, Availability, AvailabilityChangeset, Event,
|
Assignment, AssignmentChangeset, Availability, AvailabilityChangeset, Event,
|
||||||
EventChangeset, Function, Location, Role, User,
|
EventChangeset, Function, Location, Role, User,
|
||||||
|
},
|
||||||
|
utils::test_helper::{test_delete, DbTestContext, RequestConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn arrange(pool: &PgPool) -> anyhow::Result<()> {
|
async fn arrange(pool: &PgPool) -> anyhow::Result<()> {
|
||||||
Location::create(pool, "Location", 1).await?;
|
Location::create(pool, "Location", 1).await?;
|
||||||
|
|
||||||
User::create(pool, &Faker.fake()).await?;
|
User::create(pool, Faker.fake()).await?;
|
||||||
|
|
||||||
let date: NaiveDate = Date().fake();
|
let date: NaiveDate = Date().fake();
|
||||||
let start = NaiveTime::from_hms_opt(10, 0, 0).unwrap();
|
let start = NaiveTime::from_hms_opt(10, 0, 0).unwrap();
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
|
||||||
use crate::filters;
|
use crate::{
|
||||||
|
filters,
|
||||||
|
models::{Availability, AvailabilityAssignmentState, Event},
|
||||||
|
};
|
||||||
|
|
||||||
use brass_db::models::{Availability, AvailabilityAssignmentState, Event};
|
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
pub mod post_new;
|
pub mod post_new;
|
||||||
|
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
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;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::assignment::PlanEventPersonalTablePartialTemplate,
|
endpoints::assignment::PlanEventPersonalTablePartialTemplate,
|
||||||
|
models::{
|
||||||
|
Assignment, AssignmentChangeset, AssignmentContext, Availability, Event, Function, Role,
|
||||||
|
User,
|
||||||
|
},
|
||||||
utils::{
|
utils::{
|
||||||
event_planning_template::{
|
event_planning_template::{
|
||||||
generate_availability_assignment_list, generate_status_whether_staff_is_required,
|
generate_availability_assignment_list, generate_status_whether_staff_is_required,
|
||||||
@ -12,11 +17,6 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use brass_db::{
|
|
||||||
models::{Assignment, AssignmentChangeset, AssignmentContext, Event, Function, Role, User},
|
|
||||||
validation::AsyncValidate,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct AssignmentQuery {
|
pub struct AssignmentQuery {
|
||||||
availability: i32,
|
availability: i32,
|
||||||
@ -30,15 +30,33 @@ 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());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
@ -46,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::UnprocessableEntity().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?;
|
||||||
|
|
||||||
@ -77,289 +99,3 @@ pub async fn post(
|
|||||||
|
|
||||||
Ok(template.to_response()?)
|
Ok(template.to_response()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use actix_http::StatusCode;
|
|
||||||
use brass_db::models::{
|
|
||||||
Area, Assignment, AssignmentChangeset, Availability, AvailabilityChangeset, Event,
|
|
||||||
EventChangeset, Function, Location, Role, User, UserChangeset,
|
|
||||||
};
|
|
||||||
use brass_macros::db_test;
|
|
||||||
use chrono::NaiveDateTime;
|
|
||||||
use fake::{Fake, Faker};
|
|
||||||
use sqlx::PgPool;
|
|
||||||
|
|
||||||
use crate::utils::test_helper::{
|
|
||||||
assert_snapshot, test_post, DbTestContext, NaiveDateTimeExt, RequestConfig,
|
|
||||||
ServiceResponseExt,
|
|
||||||
};
|
|
||||||
|
|
||||||
async fn arrange(pool: &PgPool) {
|
|
||||||
Location::create(pool, &Faker.fake::<String>(), 1)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut user_changeset: UserChangeset = Faker.fake();
|
|
||||||
user_changeset.name = String::from("Max Mustermann");
|
|
||||||
|
|
||||||
User::create(pool, &user_changeset).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn arrange_event(pool: &PgPool, start: NaiveDateTime, end: NaiveDateTime, location: i32) {
|
|
||||||
let mut changeset: EventChangeset = EventChangeset::create_for_test(start, end);
|
|
||||||
changeset.location_id = location;
|
|
||||||
|
|
||||||
Event::create(pool, changeset).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn arrange_availability(pool: &PgPool, start: NaiveDateTime, end: NaiveDateTime) {
|
|
||||||
Availability::create(
|
|
||||||
pool,
|
|
||||||
1,
|
|
||||||
AvailabilityChangeset {
|
|
||||||
time: (start, end),
|
|
||||||
comment: None,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn response_produces_updated_template(context: &DbTestContext) {
|
|
||||||
let app = context.app().await;
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
arrange(&context.db_pool).await;
|
|
||||||
arrange_event(&context.db_pool, start, end, 1).await;
|
|
||||||
arrange_availability(&context.db_pool, start, end).await;
|
|
||||||
|
|
||||||
let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1")
|
|
||||||
.with_role(Role::Admin);
|
|
||||||
|
|
||||||
let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await;
|
|
||||||
let (status, body) = response.into_status_and_body().await;
|
|
||||||
|
|
||||||
assert_eq!(StatusCode::OK, status, "{body}");
|
|
||||||
|
|
||||||
assert_snapshot!(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_availability_does_not_exist(context: &DbTestContext) {
|
|
||||||
let app = context.app().await;
|
|
||||||
|
|
||||||
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();
|
|
||||||
arrange(&context.db_pool).await;
|
|
||||||
arrange_event(&context.db_pool, start, end, 1).await;
|
|
||||||
|
|
||||||
let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1")
|
|
||||||
.with_role(Role::Admin);
|
|
||||||
|
|
||||||
let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await;
|
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_event_does_not_exist(context: &DbTestContext) {
|
|
||||||
let app = context.app().await;
|
|
||||||
|
|
||||||
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();
|
|
||||||
arrange(&context.db_pool).await;
|
|
||||||
arrange_availability(&context.db_pool, start, end).await;
|
|
||||||
|
|
||||||
let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1")
|
|
||||||
.with_role(Role::Admin);
|
|
||||||
|
|
||||||
let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await;
|
|
||||||
|
|
||||||
assert_eq!(StatusCode::NOT_FOUND, response.status());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_area_manager_is_different_area_from_event(context: &DbTestContext) {
|
|
||||||
let app = context.app().await;
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
arrange(&context.db_pool).await;
|
|
||||||
arrange_event(&context.db_pool, start, end, 1).await;
|
|
||||||
arrange_availability(&context.db_pool, start, end).await;
|
|
||||||
Area::create(&context.db_pool, &Faker.fake::<String>())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1")
|
|
||||||
.with_role(Role::AreaManager)
|
|
||||||
.with_user_area(2);
|
|
||||||
|
|
||||||
let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await;
|
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_availability_user_not_in_event_area(context: &DbTestContext) {
|
|
||||||
let app = context.app().await;
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
arrange(&context.db_pool).await;
|
|
||||||
Area::create(&context.db_pool, &Faker.fake::<String>())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
Location::create(&context.db_pool, &Faker.fake::<String>(), 2)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
arrange_event(&context.db_pool, start, end, 2).await;
|
|
||||||
arrange_availability(&context.db_pool, start, end).await;
|
|
||||||
|
|
||||||
let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1")
|
|
||||||
.with_role(Role::Admin);
|
|
||||||
|
|
||||||
let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await;
|
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_assignment_time_doesnt_fit_into_availability_time(context: &DbTestContext) {
|
|
||||||
let app = context.app().await;
|
|
||||||
|
|
||||||
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();
|
|
||||||
let availability_start = NaiveDateTime::from_ymd_and_hms(2025, 01, 12, 10, 0, 0).unwrap();
|
|
||||||
let availability_end = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 18, 0, 0).unwrap();
|
|
||||||
|
|
||||||
arrange(&context.db_pool).await;
|
|
||||||
arrange_event(&context.db_pool, start, end, 1).await;
|
|
||||||
arrange_availability(&context.db_pool, availability_start, availability_end).await;
|
|
||||||
|
|
||||||
let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1")
|
|
||||||
.with_role(Role::Admin);
|
|
||||||
|
|
||||||
let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await;
|
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status());
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn fails_when_end_time_lies_before_start_time(context: &DbTestContext) -> not possible as event has the same constraint
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_availability_time_already_assigned(context: &DbTestContext) {
|
|
||||||
let app = context.app().await;
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
arrange(&context.db_pool).await;
|
|
||||||
arrange_event(&context.db_pool, start, end, 1).await;
|
|
||||||
arrange_event(&context.db_pool, start, end, 1).await;
|
|
||||||
arrange_availability(&context.db_pool, start, end).await;
|
|
||||||
|
|
||||||
let assignment_changeset = AssignmentChangeset {
|
|
||||||
function: Function::Posten,
|
|
||||||
time: (start, end),
|
|
||||||
};
|
|
||||||
|
|
||||||
Assignment::create(&context.db_pool, 1, 1, assignment_changeset)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=2")
|
|
||||||
.with_role(Role::Admin);
|
|
||||||
|
|
||||||
let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await;
|
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_availability_user_does_not_have_function(context: &DbTestContext) {
|
|
||||||
let app = context.app().await;
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
arrange(&context.db_pool).await;
|
|
||||||
arrange_event(&context.db_pool, start, end, 1).await;
|
|
||||||
arrange_availability(&context.db_pool, start, end).await;
|
|
||||||
|
|
||||||
let config = RequestConfig::new("/assignments/new?availability=1&function=5&event=1")
|
|
||||||
.with_role(Role::Admin);
|
|
||||||
|
|
||||||
let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await;
|
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[db_test]
|
|
||||||
fn fails_when_event_already_has_enough_assignments_for_function(context: &DbTestContext) {
|
|
||||||
let app = context.app().await;
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
Location::create(&context.db_pool, &Faker.fake::<String>(), 1)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut user_changeset: UserChangeset = Faker.fake();
|
|
||||||
user_changeset.functions.push(Function::Fuehrungsassistent);
|
|
||||||
|
|
||||||
User::create(&context.db_pool, &user_changeset)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
User::create(&context.db_pool, &user_changeset)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
arrange_event(&context.db_pool, start, end, 1).await;
|
|
||||||
|
|
||||||
Availability::create(
|
|
||||||
&context.db_pool,
|
|
||||||
1,
|
|
||||||
AvailabilityChangeset {
|
|
||||||
time: (start, end),
|
|
||||||
comment: None,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Availability::create(
|
|
||||||
&context.db_pool,
|
|
||||||
2,
|
|
||||||
AvailabilityChangeset {
|
|
||||||
time: (start, end),
|
|
||||||
comment: None,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let assignment_changeset = AssignmentChangeset {
|
|
||||||
function: Function::Fuehrungsassistent,
|
|
||||||
time: (start, end),
|
|
||||||
};
|
|
||||||
|
|
||||||
Assignment::create(&context.db_pool, 1, 1, assignment_changeset)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let config = RequestConfig::new("/assignments/new?availability=2&function=5&event=1")
|
|
||||||
.with_role(Role::Admin);
|
|
||||||
|
|
||||||
let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await;
|
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::IdPath, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Availability, User};
|
endpoints::IdPath,
|
||||||
|
models::{Availability, User}, utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_web::delete("/availability/delete/{id}")]
|
#[actix_web::delete("/availability/delete/{id}")]
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
|
@ -3,11 +3,9 @@ use chrono::{Days, NaiveDate, NaiveTime};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::endpoints::availability::NewOrEditAvailabilityTemplate;
|
||||||
endpoints::availability::NewOrEditAvailabilityTemplate,
|
use crate::models::{find_free_date_time_slots, Availability, User};
|
||||||
utils::{ApplicationError, TemplateResponse},
|
use crate::utils::{ApplicationError, TemplateResponse};
|
||||||
};
|
|
||||||
use brass_db::models::{find_free_date_time_slots, Availability, User};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AvailabilityNewQuery {
|
struct AvailabilityNewQuery {
|
||||||
|
@ -4,18 +4,16 @@ use chrono::{NaiveDate, Utc};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::filters;
|
||||||
filters,
|
use crate::models::{
|
||||||
utils::{
|
|
||||||
event_planning_template::generate_vehicles_assigned_and_available,
|
|
||||||
ApplicationError,
|
|
||||||
DateTimeFormat::{DayMonthYear, DayMonthYearHourMinute, HourMinute},
|
|
||||||
TemplateResponse,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use brass_db::models::{
|
|
||||||
find_free_date_time_slots, Area, Assignment, Availability, Event, Function, Role, User, Vehicle,
|
find_free_date_time_slots, Area, Assignment, Availability, Event, Function, Role, User, Vehicle,
|
||||||
};
|
};
|
||||||
|
use crate::utils::{
|
||||||
|
event_planning_template::generate_vehicles_assigned_and_available,
|
||||||
|
ApplicationError,
|
||||||
|
DateTimeFormat::{DayMonthYear, DayMonthYearHourMinute, HourMinute},
|
||||||
|
TemplateResponse,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct CalendarQuery {
|
pub struct CalendarQuery {
|
||||||
|
@ -4,9 +4,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{availability::NewOrEditAvailabilityTemplate, IdPath},
|
endpoints::{availability::NewOrEditAvailabilityTemplate, IdPath},
|
||||||
|
models::{find_free_date_time_slots, Availability, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{find_free_date_time_slots, Availability, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/availability/edit/{id}")]
|
#[actix_web::get("/availability/edit/{id}")]
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
|
@ -2,11 +2,9 @@ use askama::Template;
|
|||||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::filters;
|
||||||
filters,
|
use crate::models::{Role, User};
|
||||||
utils::DateTimeFormat::{DayMonth, DayMonthYear, DayMonthYearHourMinute, HourMinute},
|
use crate::utils::DateTimeFormat::{DayMonth, DayMonthYear, DayMonthYearHourMinute, HourMinute};
|
||||||
};
|
|
||||||
use brass_db::models::{Role, User};
|
|
||||||
|
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
pub mod get_new;
|
pub mod get_new;
|
||||||
|
@ -3,11 +3,8 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::availability::AvailabilityForm,
|
endpoints::availability::AvailabilityForm,
|
||||||
utils::{self, ApplicationError},
|
|
||||||
};
|
|
||||||
use brass_db::{
|
|
||||||
models::{Availability, AvailabilityChangeset, AvailabilityContext, User},
|
models::{Availability, AvailabilityChangeset, AvailabilityContext, User},
|
||||||
validation::AsyncValidate,
|
utils::{self, validation::AsyncValidate, ApplicationError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[actix_web::post("/availability/new")]
|
#[actix_web::post("/availability/new")]
|
||||||
|
@ -3,11 +3,8 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{availability::AvailabilityForm, IdPath},
|
endpoints::{availability::AvailabilityForm, IdPath},
|
||||||
utils::{self, ApplicationError},
|
|
||||||
};
|
|
||||||
use brass_db::{
|
|
||||||
models::{Availability, AvailabilityChangeset, AvailabilityContext, User},
|
models::{Availability, AvailabilityChangeset, AvailabilityContext, User},
|
||||||
validation::AsyncValidate,
|
utils::{self, validation::AsyncValidate, ApplicationError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[actix_web::post("/availability/edit/{id}")]
|
#[actix_web::post("/availability/edit/{id}")]
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::IdPath, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Clothing, Role, User};
|
endpoints::IdPath,
|
||||||
|
models::{Clothing, Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_web::delete("/clothing/{id}")]
|
#[actix_web::delete("/clothing/{id}")]
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
@ -25,8 +28,13 @@ pub async fn delete(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::test_helper::{test_delete, DbTestContext, RequestConfig, StatusCode};
|
use crate::{
|
||||||
use brass_db::models::{Clothing, Role};
|
models::{Clothing, Role},
|
||||||
|
utils::test_helper::{
|
||||||
|
test_delete, DbTestContext, RequestConfig,
|
||||||
|
StatusCode,
|
||||||
|
},
|
||||||
|
};
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{clothing::EditClothingPartialTemplate, IdPath},
|
endpoints::{clothing::EditClothingPartialTemplate, IdPath},
|
||||||
|
models::{Clothing, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Clothing, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/clothing/edit/{id}")]
|
#[actix_web::get("/clothing/edit/{id}")]
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
@ -31,10 +31,12 @@ pub async fn get(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::test_helper::{
|
use crate::{
|
||||||
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
models::{Clothing, Role},
|
||||||
|
utils::test_helper::{
|
||||||
|
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Clothing, Role};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
|
@ -2,9 +2,9 @@ use actix_web::{web, Responder};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::clothing::EditClothingPartialTemplate,
|
endpoints::clothing::EditClothingPartialTemplate,
|
||||||
|
models::{Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/clothing/new")]
|
#[actix_web::get("/clothing/new")]
|
||||||
pub async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
|
pub async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
|
||||||
|
@ -2,8 +2,10 @@ use actix_web::{web, Responder};
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::{ApplicationError, TemplateResponse};
|
use crate::{
|
||||||
use brass_db::models::{Clothing, Role, User};
|
models::{Clothing, Role, User},
|
||||||
|
utils::{ApplicationError, TemplateResponse},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[cfg_attr(not(test), template(path = "clothing/overview.html"))]
|
#[cfg_attr(not(test), template(path = "clothing/overview.html"))]
|
||||||
@ -38,10 +40,12 @@ pub async fn get(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::test_helper::{
|
use crate::{
|
||||||
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
models::{Clothing, Role},
|
||||||
|
utils::test_helper::{
|
||||||
|
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Clothing, Role};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{clothing::ReadClothingPartialTemplate, IdPath},
|
endpoints::{clothing::ReadClothingPartialTemplate, IdPath},
|
||||||
|
models::{Clothing, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Clothing, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/clothing/{id}")]
|
#[actix_web::get("/clothing/{id}")]
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
@ -28,10 +28,12 @@ pub async fn get(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::test_helper::{
|
use crate::{
|
||||||
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
models::{Clothing, Role},
|
||||||
|
utils::test_helper::{
|
||||||
|
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Clothing, Role};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
use garde::Validate;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::models::Clothing;
|
||||||
use crate::filters;
|
use crate::filters;
|
||||||
use brass_db::models::Clothing;
|
|
||||||
|
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
pub mod get_edit;
|
pub mod get_edit;
|
||||||
@ -25,7 +26,8 @@ struct ReadClothingPartialTemplate {
|
|||||||
c: Clothing,
|
c: Clothing,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Validate)]
|
||||||
struct NewOrEditClothingForm {
|
struct NewOrEditClothingForm {
|
||||||
|
#[garde(length(min=3))]
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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::{
|
||||||
@ -6,9 +7,9 @@ use crate::{
|
|||||||
clothing::{NewOrEditClothingForm, ReadClothingPartialTemplate},
|
clothing::{NewOrEditClothingForm, ReadClothingPartialTemplate},
|
||||||
IdPath,
|
IdPath,
|
||||||
},
|
},
|
||||||
|
models::{Clothing, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Clothing, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::post("/clothing/{id}")]
|
#[actix_web::post("/clothing/{id}")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
@ -25,8 +26,8 @@ pub async fn post(
|
|||||||
return Ok(HttpResponse::NotFound().finish());
|
return Ok(HttpResponse::NotFound().finish());
|
||||||
};
|
};
|
||||||
|
|
||||||
if form.name.chars().count() < 3 {
|
if let Err(e) = form.validate() {
|
||||||
return Ok(HttpResponse::UnprocessableEntity().body("Name der Bekleidung ist zu kurz."));
|
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
||||||
};
|
};
|
||||||
|
|
||||||
clothing.name = form.name.to_string();
|
clothing.name = form.name.to_string();
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
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::{
|
||||||
endpoints::clothing::{NewOrEditClothingForm, ReadClothingPartialTemplate},
|
endpoints::clothing::{NewOrEditClothingForm, ReadClothingPartialTemplate},
|
||||||
|
models::{Clothing, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Clothing, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::post("/clothing")]
|
#[actix_web::post("/clothing")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
@ -17,8 +18,8 @@ pub async fn post(
|
|||||||
return Err(ApplicationError::Unauthorized);
|
return Err(ApplicationError::Unauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.name.chars().count() < 3 {
|
if let Err(e) = form.validate() {
|
||||||
return Ok(HttpResponse::UnprocessableEntity().body("Name der Bekleidung ist zu kurz."));
|
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let clothing = Clothing::create(pool.get_ref(), &form.name).await?;
|
let clothing = Clothing::create(pool.get_ref(), &form.name).await?;
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::IdPath,
|
endpoints::IdPath,
|
||||||
|
models::{Assignment, Event, Role, User},
|
||||||
utils::{self, ApplicationError},
|
utils::{self, ApplicationError},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Assignment, Event, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::delete("/events/{id}")]
|
#[actix_web::delete("/events/{id}")]
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
|
@ -11,9 +11,9 @@ use chrono::{NaiveDate, NaiveTime};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{events::NewOrEditEventTemplate, IdPath},
|
endpoints::{events::NewOrEditEventTemplate, IdPath},
|
||||||
|
models::{Assignment, Clothing, Event, Function, Location, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Assignment, Clothing, Event, Function, Location, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/events/{id}/edit")]
|
#[actix_web::get("/events/{id}/edit")]
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
@ -94,14 +94,13 @@ async fn produces_template(context: &DbTestContext) {
|
|||||||
//)
|
//)
|
||||||
//.await
|
//.await
|
||||||
//.unwrap();
|
//.unwrap();
|
||||||
//// TODO: refactor
|
|
||||||
Location::create(&context.db_pool, "Hauptbahnhof", 1)
|
Location::create(&context.db_pool, "Hauptbahnhof", 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let date = NaiveDate::parse_from_str("2025-01-01", "%F").unwrap();
|
let date = NaiveDate::parse_from_str("2025-01-01", "%F").unwrap();
|
||||||
|
|
||||||
let changeset = brass_db::models::EventChangeset {
|
let changeset = crate::models::EventChangeset {
|
||||||
time: (
|
time: (
|
||||||
date.and_time(NaiveTime::parse_from_str("08:00", "%R").unwrap()),
|
date.and_time(NaiveTime::parse_from_str("08:00", "%R").unwrap()),
|
||||||
date.and_time(NaiveTime::parse_from_str("10:00", "%R").unwrap()),
|
date.and_time(NaiveTime::parse_from_str("10:00", "%R").unwrap()),
|
||||||
@ -121,7 +120,7 @@ async fn produces_template(context: &DbTestContext) {
|
|||||||
let config = RequestConfig {
|
let config = RequestConfig {
|
||||||
uri: "/events/1/edit".to_string(),
|
uri: "/events/1/edit".to_string(),
|
||||||
role: Role::Admin,
|
role: Role::Admin,
|
||||||
function: vec![brass_db::models::Function::Posten],
|
function: vec![crate::models::Function::Posten],
|
||||||
user_area: 1,
|
user_area: 1,
|
||||||
};
|
};
|
||||||
let response = test_get(&context.db_pool, app, &config).await;
|
let response = test_get(&context.db_pool, app, &config).await;
|
||||||
|
@ -4,9 +4,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{events::NewOrEditEventTemplate, NaiveDateQuery},
|
endpoints::{events::NewOrEditEventTemplate, NaiveDateQuery},
|
||||||
|
models::{Clothing, Location, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Clothing, Location, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/events/new")]
|
#[actix_web::get("/events/new")]
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
|
@ -5,6 +5,7 @@ use sqlx::PgPool;
|
|||||||
use crate::{
|
use crate::{
|
||||||
endpoints::IdPath,
|
endpoints::IdPath,
|
||||||
filters,
|
filters,
|
||||||
|
models::{Availability, AvailabilityAssignmentState, Event, Role, User, Vehicle},
|
||||||
utils::{
|
utils::{
|
||||||
event_planning_template::{
|
event_planning_template::{
|
||||||
generate_availability_assignment_list, generate_status_whether_staff_is_required,
|
generate_availability_assignment_list, generate_status_whether_staff_is_required,
|
||||||
@ -13,7 +14,6 @@ use crate::{
|
|||||||
ApplicationError, TemplateResponse,
|
ApplicationError, TemplateResponse,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Availability, AvailabilityAssignmentState, Event, Role, User, Vehicle};
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "events/plan.html")]
|
#[template(path = "events/plan.html")]
|
||||||
|
@ -3,8 +3,9 @@ use chrono::{Days, NaiveDateTime};
|
|||||||
use chrono::{NaiveDate, NaiveTime};
|
use chrono::{NaiveDate, NaiveTime};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{filters, utils::DateTimeFormat::{DayMonthYear, HourMinute, YearMonthDayTHourMinute}};
|
use crate::filters;
|
||||||
use brass_db::models::{Clothing, Location, Role, User};
|
use crate::models::{Clothing, Location, Role, User};
|
||||||
|
use crate::utils::DateTimeFormat::{DayMonthYear, HourMinute, YearMonthDayTHourMinute};
|
||||||
|
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
pub mod get_edit;
|
pub mod get_edit;
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
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},
|
||||||
|
models::{
|
||||||
|
Assignment, AssignmentChangeset, Availability, Event, EventChangeset, EventContext,
|
||||||
|
Function, Location, Role, User,
|
||||||
|
},
|
||||||
utils::{self, ApplicationError},
|
utils::{self, ApplicationError},
|
||||||
};
|
END_OF_DAY, START_OF_DAY,
|
||||||
use brass_db::{
|
|
||||||
models::{Assignment, AssignmentChangeset, Event, EventChangeset, EventContext, Role, User},
|
|
||||||
validation::AsyncValidate,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[actix_web::post("/events/{id}/edit")]
|
#[actix_web::post("/events/{id}/edit")]
|
||||||
@ -25,6 +28,21 @@ 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,
|
||||||
@ -39,18 +57,85 @@ pub async fn post(
|
|||||||
voluntary_fuehrungsassistent: form.voluntaryfuehrungsassistent,
|
voluntary_fuehrungsassistent: form.voluntaryfuehrungsassistent,
|
||||||
};
|
};
|
||||||
|
|
||||||
let context = EventContext {
|
let assignments_for_event = Assignment::read_all_by_event(pool.get_ref(), event.id).await?;
|
||||||
event: Some(event.id),
|
|
||||||
pool: pool.get_ref(),
|
|
||||||
user: &user.into_inner(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = changeset.validate_with_context(&context).await {
|
let start = event.start.date();
|
||||||
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
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.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,
|
||||||
@ -63,7 +148,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,14 +1,12 @@
|
|||||||
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,
|
||||||
|
models::{Event, EventChangeset, Location, Role, User},
|
||||||
utils::{self, ApplicationError},
|
utils::{self, ApplicationError},
|
||||||
};
|
};
|
||||||
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(
|
||||||
@ -20,6 +18,14 @@ 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,
|
||||||
@ -34,20 +40,14 @@ pub async fn post(
|
|||||||
voluntary_fuehrungsassistent: form.voluntaryfuehrungsassistent,
|
voluntary_fuehrungsassistent: form.voluntaryfuehrungsassistent,
|
||||||
};
|
};
|
||||||
|
|
||||||
let event_context = EventContext {
|
if let Err(e) = changeset.validate_with(&None) {
|
||||||
event: None,
|
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
||||||
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))
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::IdPath,
|
endpoints::IdPath,
|
||||||
|
models::{Event, Role, User},
|
||||||
utils::{self, ApplicationError},
|
utils::{self, ApplicationError},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Event, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::put("/events/{id}/cancel")]
|
#[actix_web::put("/events/{id}/cancel")]
|
||||||
pub async fn put_cancel(
|
pub async fn put_cancel(
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
use actix_web::{web, Responder};
|
use actix_web::{web, Responder};
|
||||||
use askama::Template;
|
|
||||||
use chrono::{NaiveDate, Utc};
|
use chrono::{NaiveDate, Utc};
|
||||||
|
use askama::Template;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::{ApplicationError, TemplateResponse};
|
use crate::{
|
||||||
use brass_db::models::{Area, Role, User};
|
models::{Area, Role, User},
|
||||||
|
utils::{ApplicationError, TemplateResponse},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "export/availability.html")]
|
#[template(path = "export/availability.html")]
|
||||||
|
@ -7,8 +7,10 @@ use quick_xml::se::Serializer;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::ApplicationError;
|
use crate::{
|
||||||
use brass_db::models::{Area, Availability, Role, User, UserFunction};
|
models::{Area, Availability, Role, User, UserFunction},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExportQuery {
|
struct ExportQuery {
|
||||||
|
@ -3,8 +3,10 @@ use askama::Template;
|
|||||||
use chrono::{Datelike, Months, NaiveDate, Utc};
|
use chrono::{Datelike, Months, NaiveDate, Utc};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::{ApplicationError, TemplateResponse};
|
use crate::{
|
||||||
use brass_db::models::{Area, Role, User};
|
models::{Area, Role, User},
|
||||||
|
utils::{ApplicationError, TemplateResponse},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "export/events.html")]
|
#[template(path = "export/events.html")]
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
|
use crate::models::{ExportEventRow, Function, SimpleAssignment};
|
||||||
use actix_http::header::CONTENT_DISPOSITION;
|
use actix_http::header::CONTENT_DISPOSITION;
|
||||||
use actix_web::{http::header::ContentDisposition, web, HttpResponse, Responder};
|
use actix_web::{http::header::ContentDisposition, web, HttpResponse, Responder};
|
||||||
use brass_db::models::{ExportEventRow, Function, Role, SimpleAssignment, User};
|
|
||||||
use chrono::{Datelike, NaiveDate};
|
use chrono::{Datelike, NaiveDate};
|
||||||
use rust_xlsxwriter::workbook::Workbook;
|
use rust_xlsxwriter::workbook::Workbook;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::{ApplicationError, DateTimeFormat};
|
use crate::{
|
||||||
|
models::{Role, User},
|
||||||
|
utils::{ApplicationError, DateTimeFormat},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ExportQuery {
|
struct ExportQuery {
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::IdPath, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Location, Role, User};
|
endpoints::IdPath,
|
||||||
|
models::{Location, Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_web::delete("/locations/delete/{id}")]
|
#[actix_web::delete("/locations/delete/{id}")]
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{location::LocationTemplate, IdPath},
|
endpoints::{location::LocationTemplate, IdPath},
|
||||||
|
models::{Area, Location, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Area, Location, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/locations/edit/{id}")]
|
#[actix_web::get("/locations/edit/{id}")]
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::location::LocationTemplate,
|
endpoints::location::LocationTemplate,
|
||||||
|
models::{Area, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Area, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/locations/new")]
|
#[actix_web::get("/locations/new")]
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
|
@ -2,8 +2,10 @@ use actix_web::{web, Responder};
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::{ApplicationError, TemplateResponse};
|
use crate::{
|
||||||
use brass_db::models::{Area, Location, Role, User};
|
models::{Area, Location, Role, User},
|
||||||
|
utils::{ApplicationError, TemplateResponse},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "location/overview.html")]
|
#[template(path = "location/overview.html")]
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::models::{Area, Location, Role, User};
|
||||||
use crate::filters;
|
use crate::filters;
|
||||||
use brass_db::models::{Area, Location, Role, User};
|
|
||||||
|
|
||||||
pub mod delete;
|
|
||||||
pub mod get_edit;
|
|
||||||
pub mod get_new;
|
pub mod get_new;
|
||||||
pub mod get_overview;
|
pub mod get_overview;
|
||||||
pub mod post_edit;
|
|
||||||
pub mod post_new;
|
pub mod post_new;
|
||||||
|
pub mod get_edit;
|
||||||
|
pub mod post_edit;
|
||||||
|
pub mod delete;
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "location/new_or_edit.html")]
|
#[template(path = "location/new_or_edit.html")]
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{location::LocationForm, IdPath},
|
endpoints::{location::LocationForm, IdPath},
|
||||||
|
models::{Location, Role, User},
|
||||||
utils::ApplicationError,
|
utils::ApplicationError,
|
||||||
};
|
};
|
||||||
use brass_db::models::{Location, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::post("/locations/edit/{id}")]
|
#[actix_web::post("/locations/edit/{id}")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||||
|
use brass_macros::db_test;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::location::LocationForm, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Location, Role, User};
|
endpoints::location::LocationForm,
|
||||||
|
models::{Location, Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use crate::utils::test_helper::{test_post, DbTestContext, RequestConfig, StatusCode};
|
||||||
|
|
||||||
#[actix_web::post("/locations/new")]
|
#[actix_web::post("/locations/new")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
@ -28,68 +35,58 @@ pub async fn post(
|
|||||||
.finish())
|
.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[db_test]
|
||||||
mod tests {
|
async fn works_when_user_is_admin(context: &DbTestContext) {
|
||||||
use crate::{
|
let app = context.app().await;
|
||||||
endpoints::location::LocationForm,
|
let config = RequestConfig {
|
||||||
utils::test_helper::{test_post, DbTestContext, RequestConfig, StatusCode},
|
uri: "/locations/new".to_string(),
|
||||||
|
role: Role::Admin,
|
||||||
|
function: vec![crate::models::Function::Posten],
|
||||||
|
user_area: 1,
|
||||||
};
|
};
|
||||||
use brass_db::models::{Function, Location, Role};
|
|
||||||
use brass_macros::db_test;
|
|
||||||
|
|
||||||
#[db_test]
|
let form = LocationForm {
|
||||||
async fn works_when_user_is_admin(context: &DbTestContext) {
|
name: "Hauptbahnhof".to_string(),
|
||||||
let app = context.app().await;
|
area: Some(1),
|
||||||
let config = RequestConfig {
|
};
|
||||||
uri: "/locations/new".to_string(),
|
|
||||||
role: Role::Admin,
|
|
||||||
function: vec![Function::Posten],
|
|
||||||
user_area: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let form = LocationForm {
|
let response = test_post(&context.db_pool, app, &config, form).await;
|
||||||
name: "Hauptbahnhof".to_string(),
|
assert_eq!(StatusCode::FOUND, response.status());
|
||||||
area: Some(1),
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(form)).await;
|
assert_eq!(
|
||||||
assert_eq!(StatusCode::FOUND, response.status());
|
"Hauptbahnhof".to_string(),
|
||||||
|
Location::read_by_id(&context.db_pool, 1)
|
||||||
assert_eq!(
|
.await
|
||||||
"Hauptbahnhof".to_string(),
|
.unwrap()
|
||||||
Location::read_by_id(&context.db_pool, 1)
|
.unwrap()
|
||||||
.await
|
.name
|
||||||
.unwrap()
|
);
|
||||||
.unwrap()
|
}
|
||||||
.name
|
|
||||||
);
|
#[db_test]
|
||||||
}
|
async fn uses_area_id_of_area_manager(context: &DbTestContext) {
|
||||||
|
let app = context.app().await;
|
||||||
#[db_test]
|
let config = RequestConfig {
|
||||||
async fn uses_area_id_of_area_manager(context: &DbTestContext) {
|
uri: "/locations/new".to_string(),
|
||||||
let app = context.app().await;
|
role: Role::AreaManager,
|
||||||
let config = RequestConfig {
|
function: vec![crate::models::Function::Posten],
|
||||||
uri: "/locations/new".to_string(),
|
user_area: 1,
|
||||||
role: Role::AreaManager,
|
};
|
||||||
function: vec![Function::Posten],
|
|
||||||
user_area: 1,
|
let form = LocationForm {
|
||||||
};
|
name: "Hauptbahnhof".to_string(),
|
||||||
|
area: None,
|
||||||
let form = LocationForm {
|
};
|
||||||
name: "Hauptbahnhof".to_string(),
|
|
||||||
area: None,
|
let response = test_post(&context.db_pool, app, &config, form).await;
|
||||||
};
|
assert_eq!(StatusCode::FOUND, response.status());
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(form)).await;
|
assert_eq!(
|
||||||
assert_eq!(StatusCode::FOUND, response.status());
|
"Hauptbahnhof".to_string(),
|
||||||
|
Location::read_by_id(&context.db_pool, 1)
|
||||||
assert_eq!(
|
.await
|
||||||
"Hauptbahnhof".to_string(),
|
.unwrap()
|
||||||
Location::read_by_id(&context.db_pool, 1)
|
.unwrap()
|
||||||
.await
|
.name
|
||||||
.unwrap()
|
);
|
||||||
.unwrap()
|
|
||||||
.name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::IdPath, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Role, User};
|
endpoints::IdPath,
|
||||||
|
models::{Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_web::delete("/users/{id}")]
|
#[actix_web::delete("/users/{id}")]
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use actix_web::{web, Responder};
|
use actix_web::{web, Responder};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
|
||||||
use crate::utils::{ApplicationError, TemplateResponse};
|
use crate::{
|
||||||
|
models::User,
|
||||||
use brass_db::models::User;
|
utils::{ApplicationError, TemplateResponse},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "user/profile_change_password.html")]
|
#[template(path = "user/profile_change_password.html")]
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{user::NewOrEditUserTemplate, IdPath},
|
endpoints::{user::NewOrEditUserTemplate, IdPath},
|
||||||
|
models::{Area, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Area, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/users/edit/{id}")]
|
#[actix_web::get("/users/edit/{id}")]
|
||||||
pub async fn get_edit(
|
pub async fn get_edit(
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::user::NewOrEditUserTemplate,
|
endpoints::user::NewOrEditUserTemplate,
|
||||||
|
models::{Area, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Area, Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/users/new")]
|
#[actix_web::get("/users/new")]
|
||||||
pub async fn get_new(
|
pub async fn get_new(
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
filters,
|
filters,
|
||||||
|
models::{Area, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Area, Role, User};
|
|
||||||
|
|
||||||
use actix_web::{web, Responder};
|
use actix_web::{web, Responder};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
@ -14,7 +14,7 @@ pub struct UsersTemplate {
|
|||||||
user: User,
|
user: User,
|
||||||
area: Option<Area>,
|
area: Option<Area>,
|
||||||
users: Vec<User>,
|
users: Vec<User>,
|
||||||
is_oob: bool,
|
is_oob: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::get("/users")]
|
#[actix_web::get("/users")]
|
||||||
@ -44,7 +44,7 @@ pub async fn get_overview(
|
|||||||
user: user.into_inner(),
|
user: user.into_inner(),
|
||||||
area,
|
area,
|
||||||
users,
|
users,
|
||||||
is_oob: false,
|
is_oob: false
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(template.to_response()?)
|
Ok(template.to_response()?)
|
||||||
|
@ -4,9 +4,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
filters,
|
filters,
|
||||||
|
models::{Area, Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Area, Role, User};
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "user/profile.html")]
|
#[template(path = "user/profile.html")]
|
||||||
|
@ -3,8 +3,10 @@ use actix_web::{get, http::header::LOCATION, web, HttpResponse, Responder};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::{ApplicationError, TemplateResponse};
|
use crate::{
|
||||||
use brass_db::models::Registration;
|
models::Registration,
|
||||||
|
utils::{ApplicationError, TemplateResponse},
|
||||||
|
};
|
||||||
|
|
||||||
use super::ResetPasswordTemplate;
|
use super::ResetPasswordTemplate;
|
||||||
|
|
||||||
|
@ -4,8 +4,10 @@ use askama::Template;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::{ApplicationError, Customization, TemplateResponse};
|
use crate::{
|
||||||
use brass_db::models::PasswordReset;
|
models::PasswordReset,
|
||||||
|
utils::{ApplicationError, Customization, TemplateResponse},
|
||||||
|
};
|
||||||
|
|
||||||
use super::ResetPasswordTemplate;
|
use super::ResetPasswordTemplate;
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::filters;
|
use crate::{
|
||||||
|
filters,
|
||||||
|
models::{Area, Role, User},
|
||||||
|
};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use brass_db::models::{Area, Role, User};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
@ -20,8 +22,8 @@ pub mod post_new;
|
|||||||
pub mod post_register;
|
pub mod post_register;
|
||||||
pub mod post_resend_registration;
|
pub mod post_resend_registration;
|
||||||
pub mod post_reset;
|
pub mod post_reset;
|
||||||
pub mod put_lock;
|
|
||||||
pub mod put_receive_notifications;
|
pub mod put_receive_notifications;
|
||||||
|
pub mod put_lock;
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "user/new_or_edit.html")]
|
#[template(path = "user/new_or_edit.html")]
|
||||||
|
@ -3,8 +3,10 @@ use maud::html;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::{password_change::PasswordChangeBuilder, ApplicationError};
|
use crate::{
|
||||||
use brass_db::{models::User, NoneToken};
|
models::{NoneToken, User},
|
||||||
|
utils::{password_change::PasswordChangeBuilder, ApplicationError},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ChangePasswordForm {
|
struct ChangePasswordForm {
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||||
use brass_db::{
|
|
||||||
models::{Function, Role, User, UserChangeset},
|
|
||||||
validation::{AsyncValidate, DbContext},
|
|
||||||
};
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{user::NewOrEditUserForm, IdPath},
|
endpoints::{user::NewOrEditUserForm, IdPath},
|
||||||
utils::ApplicationError,
|
models::{Function, Role, User, UserChangeset},
|
||||||
|
utils::{validation::{AsyncValidate, DbContext}, ApplicationError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[actix_web::post("/users/edit/{id}")]
|
#[actix_web::post("/users/edit/{id}")]
|
||||||
@ -81,18 +78,16 @@ pub async fn post_edit(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{endpoints::user::NewOrEditUserForm, utils::test_helper::*};
|
use crate::{endpoints::user::NewOrEditUserForm, models::*, utils::test_helper::*};
|
||||||
use brass_db::models::*;
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
use fake::{
|
use fake::{
|
||||||
faker::{internet::en::SafeEmail, name::en::Name},
|
faker::{internet::en::SafeEmail, name::en::Name},
|
||||||
Fake, Faker,
|
Fake, Faker,
|
||||||
};
|
};
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
async fn works_when_user_is_admin(context: &DbTestContext) {
|
async fn works_when_user_is_admin(context: &DbTestContext) {
|
||||||
User::create(&context.db_pool, &Faker.fake()).await.unwrap();
|
User::create(&context.db_pool, Faker.fake()).await.unwrap();
|
||||||
|
|
||||||
Area::create(&context.db_pool, "Süd").await.unwrap();
|
Area::create(&context.db_pool, "Süd").await.unwrap();
|
||||||
|
|
||||||
@ -117,7 +112,7 @@ mod tests {
|
|||||||
area: Some(2),
|
area: Some(2),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(form)).await;
|
let response = test_post(&context.db_pool, app, &config, form).await;
|
||||||
assert_eq!(StatusCode::FOUND, response.status());
|
assert_eq!(StatusCode::FOUND, response.status());
|
||||||
|
|
||||||
let updated_user = User::read_by_id(&context.db_pool, 1)
|
let updated_user = User::read_by_id(&context.db_pool, 1)
|
||||||
@ -152,13 +147,14 @@ mod tests {
|
|||||||
area: Some(1),
|
area: Some(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(form)).await;
|
let response = test_post(&context.db_pool, app, &config, form).await;
|
||||||
assert_eq!(StatusCode::BAD_REQUEST, response.status());
|
assert_eq!(StatusCode::BAD_REQUEST, response.status());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
async fn email_gets_cast_to_lowercase(context: &DbTestContext) {
|
async fn email_gets_cast_to_lowercase(context: &DbTestContext) {
|
||||||
User::create(&context.db_pool, &Faker.fake()).await.unwrap();
|
User::create(&context.db_pool, Faker.fake()).await.unwrap();
|
||||||
|
Area::create(&context.db_pool, "Süd").await.unwrap();
|
||||||
|
|
||||||
let app = context.app().await;
|
let app = context.app().await;
|
||||||
let config = RequestConfig::new("/users/edit/1").with_role(Role::Admin);
|
let config = RequestConfig::new("/users/edit/1").with_role(Role::Admin);
|
||||||
@ -173,14 +169,11 @@ mod tests {
|
|||||||
is_posten: None,
|
is_posten: None,
|
||||||
is_wachhabender: None,
|
is_wachhabender: None,
|
||||||
is_fuehrungsassistent: Some(true),
|
is_fuehrungsassistent: Some(true),
|
||||||
area: Some(1),
|
area: Some(2),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(form)).await;
|
let response = test_post(&context.db_pool, app, &config, form).await;
|
||||||
let (status, body) = response.into_status_and_body().await;
|
assert_eq!(StatusCode::FOUND, response.status());
|
||||||
debug!(body);
|
|
||||||
|
|
||||||
assert_eq!(StatusCode::FOUND, status);
|
|
||||||
|
|
||||||
let updated_user = User::read_by_id(&context.db_pool, 1)
|
let updated_user = User::read_by_id(&context.db_pool, 1)
|
||||||
.await
|
.await
|
||||||
@ -192,8 +185,8 @@ mod tests {
|
|||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
async fn fails_when_email_already_present(context: &DbTestContext) {
|
async fn fails_when_email_already_present(context: &DbTestContext) {
|
||||||
User::create(&context.db_pool, &Faker.fake()).await.unwrap();
|
User::create(&context.db_pool, Faker.fake()).await.unwrap();
|
||||||
User::create(&context.db_pool, &Faker.fake()).await.unwrap();
|
User::create(&context.db_pool, Faker.fake()).await.unwrap();
|
||||||
Area::create(&context.db_pool, "Süd").await.unwrap();
|
Area::create(&context.db_pool, "Süd").await.unwrap();
|
||||||
|
|
||||||
let app = context.app().await;
|
let app = context.app().await;
|
||||||
@ -217,7 +210,7 @@ mod tests {
|
|||||||
area: Some(2),
|
area: Some(2),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(form)).await;
|
let response = test_post(&context.db_pool, app, &config, form).await;
|
||||||
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status());
|
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use actix_identity::Identity;
|
use actix_identity::Identity;
|
||||||
use actix_web::{web, HttpMessage, HttpRequest, HttpResponse, Responder};
|
use actix_web::{web, HttpMessage, HttpRequest, HttpResponse, Responder};
|
||||||
use brass_db::models::User;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::auth::hash_plain_password_with_salt;
|
use crate::{models::User, utils::auth::hash_plain_password_with_salt};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct LoginForm {
|
pub struct LoginForm {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::user::NewOrEditUserForm, mail::Mailer, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::{
|
endpoints::user::NewOrEditUserForm,
|
||||||
|
mail::Mailer,
|
||||||
models::{Function, Registration, Role, User, UserChangeset},
|
models::{Function, Registration, Role, User, UserChangeset},
|
||||||
validation::{AsyncValidate, DbContext},
|
utils::{validation::{AsyncValidate, DbContext}, ApplicationError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[actix_web::post("/users/new")]
|
#[actix_web::post("/users/new")]
|
||||||
@ -61,7 +62,7 @@ pub async fn post_new(
|
|||||||
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = User::create(pool.get_ref(), &changeset).await?;
|
let id = User::create(pool.get_ref(), changeset).await?;
|
||||||
|
|
||||||
let registration = Registration::insert_new_for_user(pool.get_ref(), id).await?;
|
let registration = Registration::insert_new_for_user(pool.get_ref(), id).await?;
|
||||||
let new_user = User::read_by_id(pool.get_ref(), id).await?.unwrap();
|
let new_user = User::read_by_id(pool.get_ref(), id).await?.unwrap();
|
||||||
|
@ -3,8 +3,10 @@ use maud::html;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::{password_change::PasswordChangeBuilder, ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::Registration;
|
models::Registration,
|
||||||
|
utils::{password_change::PasswordChangeBuilder, ApplicationError},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct RegisterForm {
|
struct RegisterForm {
|
||||||
|
@ -2,8 +2,12 @@ use actix_web::{web, HttpResponse, Responder};
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::IdPath, mail::Mailer, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Registration, Role, User};
|
endpoints::IdPath,
|
||||||
|
mail::Mailer,
|
||||||
|
models::{Registration, Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_web::post("/users/{id}/resend-registration")]
|
#[actix_web::post("/users/{id}/resend-registration")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
|
@ -5,9 +5,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
mail::Mailer,
|
mail::Mailer,
|
||||||
|
models::{PasswordReset, User},
|
||||||
utils::{password_change::PasswordChangeBuilder, ApplicationError},
|
utils::{password_change::PasswordChangeBuilder, ApplicationError},
|
||||||
};
|
};
|
||||||
use brass_db::models::{PasswordReset, User};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct ResetPasswordForm {
|
struct ResetPasswordForm {
|
||||||
|
@ -2,8 +2,12 @@ use actix_web::{web, HttpResponse, Responder};
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::IdPath, filters, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Role, User};
|
endpoints::IdPath,
|
||||||
|
filters,
|
||||||
|
models::{Role, User},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Template, Debug)]
|
#[derive(Template, Debug)]
|
||||||
#[template(path = "user/overview_locked_td.html")]
|
#[template(path = "user/overview_locked_td.html")]
|
||||||
@ -82,17 +86,19 @@ async fn handle_lock_state_for_user(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::test_helper::{
|
use crate::{
|
||||||
assert_snapshot, read_body, test_put, DbTestContext, RequestConfig, StatusCode,
|
models::{Area, Function, Role, User},
|
||||||
|
utils::test_helper::{
|
||||||
|
assert_snapshot, read_body, test_put, DbTestContext, RequestConfig, StatusCode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Area, Function, Role, User};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
use fake::{Fake, Faker};
|
use fake::{Fake, Faker};
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
async fn admin_can_lock_and_unlock_user(context: &DbTestContext) {
|
async fn admin_can_lock_and_unlock_user(context: &DbTestContext) {
|
||||||
let app = context.app().await;
|
let app = context.app().await;
|
||||||
User::create(&context.db_pool, &Faker.fake()).await.unwrap();
|
User::create(&context.db_pool, Faker.fake()).await.unwrap();
|
||||||
|
|
||||||
let lock_config = RequestConfig {
|
let lock_config = RequestConfig {
|
||||||
uri: "/users/1/lock".to_string(),
|
uri: "/users/1/lock".to_string(),
|
||||||
@ -127,7 +133,7 @@ mod tests {
|
|||||||
async fn area_manager_cant_lock_outside_of_his_area(context: &DbTestContext) {
|
async fn area_manager_cant_lock_outside_of_his_area(context: &DbTestContext) {
|
||||||
let app = context.app().await;
|
let app = context.app().await;
|
||||||
Area::create(&context.db_pool, "Bereich 2").await.unwrap();
|
Area::create(&context.db_pool, "Bereich 2").await.unwrap();
|
||||||
User::create(&context.db_pool, &Faker.fake()).await.unwrap();
|
User::create(&context.db_pool, Faker.fake()).await.unwrap();
|
||||||
|
|
||||||
let config = RequestConfig {
|
let config = RequestConfig {
|
||||||
uri: "/users/1/lock".to_string(),
|
uri: "/users/1/lock".to_string(),
|
||||||
|
@ -7,9 +7,9 @@ use tracing::error;
|
|||||||
use crate::{
|
use crate::{
|
||||||
endpoints::IdPath,
|
endpoints::IdPath,
|
||||||
filters,
|
filters,
|
||||||
|
models::User,
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::User;
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "user/profile.html", block = "notificationinput")]
|
#[template(path = "user/profile.html", block = "notificationinput")]
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::IdPath, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Role, User, Vehicle};
|
endpoints::IdPath,
|
||||||
|
models::{Role, User, Vehicle},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_web::delete("/vehicles/{id}")]
|
#[actix_web::delete("/vehicles/{id}")]
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
@ -25,8 +28,10 @@ pub async fn delete(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::test_helper::{test_delete, DbTestContext, RequestConfig, StatusCode};
|
use crate::{
|
||||||
use brass_db::models::{Function, Role, Vehicle};
|
models::{Function, Role, Vehicle},
|
||||||
|
utils::test_helper::{test_delete, DbTestContext, RequestConfig, StatusCode},
|
||||||
|
};
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{vehicle::VehicleNewOrEditTemplate, IdPath},
|
endpoints::{vehicle::VehicleNewOrEditTemplate, IdPath},
|
||||||
|
models::{Role, User, Vehicle},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Role, User, Vehicle};
|
|
||||||
|
|
||||||
#[actix_web::get("/vehicles/{id}")]
|
#[actix_web::get("/vehicles/{id}")]
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
@ -31,10 +31,12 @@ pub async fn get(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::test_helper::{
|
use crate::{
|
||||||
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
models::{Function, Role, Vehicle},
|
||||||
|
utils::test_helper::{
|
||||||
|
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Role, Function, Vehicle};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
|
@ -2,9 +2,9 @@ use actix_web::{web, Responder};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::vehicle::VehicleNewOrEditTemplate,
|
endpoints::vehicle::VehicleNewOrEditTemplate,
|
||||||
|
models::{Role, User},
|
||||||
utils::{ApplicationError, TemplateResponse},
|
utils::{ApplicationError, TemplateResponse},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Role, User};
|
|
||||||
|
|
||||||
#[actix_web::get("/vehicles/new")]
|
#[actix_web::get("/vehicles/new")]
|
||||||
pub async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
|
pub async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
|
||||||
@ -22,10 +22,12 @@ pub async fn get(user: web::ReqData<User>) -> Result<impl Responder, Application
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::test_helper::{
|
use crate::{
|
||||||
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
models::{Function, Role},
|
||||||
|
utils::test_helper::{
|
||||||
|
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Function, Role};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
|
@ -2,8 +2,10 @@ use actix_web::{web, Responder};
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::utils::{ApplicationError, TemplateResponse};
|
use crate::{
|
||||||
use brass_db::models::{Role, User, Vehicle};
|
models::{Role, User, Vehicle},
|
||||||
|
utils::{ApplicationError, TemplateResponse},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[cfg_attr(not(test), template(path = "vehicles/overview.html"))]
|
#[cfg_attr(not(test), template(path = "vehicles/overview.html"))]
|
||||||
@ -38,10 +40,12 @@ pub async fn get(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::test_helper::{
|
use crate::{
|
||||||
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
models::{Function, Role, Vehicle},
|
||||||
|
utils::test_helper::{
|
||||||
|
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Role, Vehicle};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
#[db_test]
|
#[db_test]
|
||||||
@ -58,7 +62,12 @@ mod tests {
|
|||||||
async fn area_manager_can_view_overview(context: &DbTestContext) {
|
async fn area_manager_can_view_overview(context: &DbTestContext) {
|
||||||
let app = context.app().await;
|
let app = context.app().await;
|
||||||
|
|
||||||
let config = RequestConfig::new("/vehicles").with_role(Role::AreaManager);
|
let config = RequestConfig {
|
||||||
|
uri: "/vehicles".to_string(),
|
||||||
|
role: Role::AreaManager,
|
||||||
|
function: vec![Function::Posten],
|
||||||
|
user_area: 1,
|
||||||
|
};
|
||||||
|
|
||||||
let response = test_get(&context.db_pool, &app, &config).await;
|
let response = test_get(&context.db_pool, &app, &config).await;
|
||||||
assert_eq!(StatusCode::OK, response.status());
|
assert_eq!(StatusCode::OK, response.status());
|
||||||
@ -71,7 +80,12 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let config = RequestConfig::new("/vehicles").with_role(Role::Admin);
|
let config = RequestConfig {
|
||||||
|
uri: "/vehicles".to_string(),
|
||||||
|
role: Role::Admin,
|
||||||
|
function: vec![Function::Posten],
|
||||||
|
user_area: 1,
|
||||||
|
};
|
||||||
|
|
||||||
let response = test_get(&context.db_pool, &app, &config).await;
|
let response = test_get(&context.db_pool, &app, &config).await;
|
||||||
assert_eq!(StatusCode::OK, response.status());
|
assert_eq!(StatusCode::OK, response.status());
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use brass_db::models::{Role, User, Vehicle};
|
use crate::models::{Role, User, Vehicle};
|
||||||
|
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
pub mod get_edit;
|
pub mod get_edit;
|
||||||
|
@ -3,9 +3,9 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::{vehicle::VehicleForm, IdPath},
|
endpoints::{vehicle::VehicleForm, IdPath},
|
||||||
|
models::{Role, User, Vehicle},
|
||||||
utils::ApplicationError,
|
utils::ApplicationError,
|
||||||
};
|
};
|
||||||
use brass_db::models::{Role, User, Vehicle};
|
|
||||||
|
|
||||||
#[actix_web::post("/vehicles/{id}")]
|
#[actix_web::post("/vehicles/{id}")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
@ -41,11 +41,11 @@ pub async fn post(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_http::StatusCode;
|
use actix_http::StatusCode;
|
||||||
use brass_db::models::{Role, Vehicle};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::vehicle::VehicleForm,
|
endpoints::vehicle::VehicleForm,
|
||||||
|
models::{Role, Vehicle},
|
||||||
utils::test_helper::{test_post, DbTestContext, RequestConfig},
|
utils::test_helper::{test_post, DbTestContext, RequestConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ mod tests {
|
|||||||
radio_call_name: "11.49.2".to_string(),
|
radio_call_name: "11.49.2".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(request)).await;
|
let response = test_post(&context.db_pool, app, &config, request).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::FOUND, response.status());
|
assert_eq!(StatusCode::FOUND, response.status());
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ mod tests {
|
|||||||
radio_call_name: "11.49.2".to_string(),
|
radio_call_name: "11.49.2".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(request)).await;
|
let response = test_post(&context.db_pool, app, &config, request).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
|
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ mod tests {
|
|||||||
radio_call_name: "11.49.2".to_string(),
|
radio_call_name: "11.49.2".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(request)).await;
|
let response = test_post(&context.db_pool, app, &config, request).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::NOT_FOUND, response.status());
|
assert_eq!(StatusCode::NOT_FOUND, response.status());
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{endpoints::vehicle::VehicleForm, utils::ApplicationError};
|
use crate::{
|
||||||
use brass_db::models::{Role, User, Vehicle};
|
endpoints::vehicle::VehicleForm,
|
||||||
|
models::{Role, User, Vehicle},
|
||||||
|
utils::ApplicationError,
|
||||||
|
};
|
||||||
|
|
||||||
#[actix_web::post("/vehicles/new")]
|
#[actix_web::post("/vehicles/new")]
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
@ -25,11 +28,11 @@ pub async fn post(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_http::StatusCode;
|
use actix_http::StatusCode;
|
||||||
use brass_db::models::{Role, Vehicle};
|
|
||||||
use brass_macros::db_test;
|
use brass_macros::db_test;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::vehicle::VehicleForm,
|
endpoints::vehicle::VehicleForm,
|
||||||
|
models::{Role, Vehicle},
|
||||||
utils::test_helper::{test_post, DbTestContext, RequestConfig},
|
utils::test_helper::{test_post, DbTestContext, RequestConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,7 +56,7 @@ mod tests {
|
|||||||
radio_call_name: "11.49.1".to_string(),
|
radio_call_name: "11.49.1".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(request)).await;
|
let response = test_post(&context.db_pool, app, &config, request).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::FOUND, response.status());
|
assert_eq!(StatusCode::FOUND, response.status());
|
||||||
|
|
||||||
@ -73,7 +76,7 @@ mod tests {
|
|||||||
radio_call_name: "11.49.2".to_string(),
|
radio_call_name: "11.49.2".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = test_post(&context.db_pool, app, &config, Some(request)).await;
|
let response = test_post(&context.db_pool, app, &config, request).await;
|
||||||
|
|
||||||
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
|
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,12 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::vehicle_assignment::PlanVehiclesPartialTemplate,
|
endpoints::vehicle_assignment::PlanVehiclesPartialTemplate,
|
||||||
|
models::{Event, Role, User, VehicleAssignment},
|
||||||
utils::{
|
utils::{
|
||||||
event_planning_template::generate_vehicles_assigned_and_available, ApplicationError,
|
event_planning_template::generate_vehicles_assigned_and_available, ApplicationError,
|
||||||
TemplateResponse,
|
TemplateResponse,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Event, Role, User, VehicleAssignment};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct VehicleAssignmentDeleteQuery {
|
struct VehicleAssignmentDeleteQuery {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
|
||||||
use brass_db::models::{Event, Vehicle};
|
use crate::models::{Event, Vehicle};
|
||||||
|
|
||||||
pub mod delete;
|
|
||||||
pub mod post_new;
|
pub mod post_new;
|
||||||
|
pub mod delete;
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "events/plan_vehicles.html")]
|
#[template(path = "events/plan_vehicles.html")]
|
||||||
|
@ -4,12 +4,12 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::vehicle_assignment::PlanVehiclesPartialTemplate,
|
endpoints::vehicle_assignment::PlanVehiclesPartialTemplate,
|
||||||
|
models::{Event, Role, User, Vehicle, VehicleAssignment},
|
||||||
utils::{
|
utils::{
|
||||||
event_planning_template::generate_vehicles_assigned_and_available, ApplicationError,
|
event_planning_template::generate_vehicles_assigned_and_available, ApplicationError,
|
||||||
TemplateResponse,
|
TemplateResponse,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use brass_db::models::{Event, Role, User, Vehicle, VehicleAssignment};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct VehicleAssignmentQuery {
|
pub struct VehicleAssignmentQuery {
|
||||||
@ -65,7 +65,8 @@ pub async fn post(
|
|||||||
.body("Vehicle already assigned to a timely conflicting event."));
|
.body("Vehicle already assigned to a timely conflicting event."));
|
||||||
}
|
}
|
||||||
|
|
||||||
VehicleAssignment::create(pool.get_ref(), event.id, vehicle.id, event.start, event.end).await?;
|
VehicleAssignment::create(pool.get_ref(), event.id, vehicle.id, event.start, event.end)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let (vehicles_assigned, vehicles_available) =
|
let (vehicles_assigned, vehicles_available) =
|
||||||
generate_vehicles_assigned_and_available(pool.get_ref(), &event).await?;
|
generate_vehicles_assigned_and_available(pool.get_ref(), &event).await?;
|
||||||
|
@ -4,8 +4,7 @@ use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
|||||||
use maud::html;
|
use maud::html;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
use crate::utils::DateTimeFormat;
|
use crate::{models::UserFunction, utils::DateTimeFormat};
|
||||||
use brass_db::models::UserFunction;
|
|
||||||
|
|
||||||
pub fn show_area_query(a: &Option<i32>, first: bool) -> askama::Result<String> {
|
pub fn show_area_query(a: &Option<i32>, first: bool) -> askama::Result<String> {
|
||||||
let char = if first { '?' } else { '&' };
|
let char = if first { '?' } else { '&' };
|
||||||
@ -78,30 +77,21 @@ pub fn show_tree(f: &UserFunction) -> askama::Result<String> {
|
|||||||
|
|
||||||
pub fn fmt_date(v: &NaiveDate, format: DateTimeFormat) -> askama::Result<String> {
|
pub fn fmt_date(v: &NaiveDate, format: DateTimeFormat) -> askama::Result<String> {
|
||||||
let format_string = format.into();
|
let format_string = format.into();
|
||||||
trace!(
|
trace!(format=format_string, "formatting naivedate into string with format");
|
||||||
format = format_string,
|
|
||||||
"formatting naivedate into string with format"
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(v.format(format_string).to_string())
|
Ok(v.format(format_string).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmt_datetime(v: &NaiveDateTime, format: DateTimeFormat) -> askama::Result<String> {
|
pub fn fmt_datetime(v: &NaiveDateTime, format: DateTimeFormat) -> askama::Result<String> {
|
||||||
let format_string = format.into();
|
let format_string = format.into();
|
||||||
trace!(
|
trace!(format=format_string, "formatting naivedatetime into string with format");
|
||||||
format = format_string,
|
|
||||||
"formatting naivedatetime into string with format"
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(v.format(format_string).to_string())
|
Ok(v.format(format_string).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmt_time(v: &NaiveTime, format: DateTimeFormat) -> askama::Result<String> {
|
pub fn fmt_time(v: &NaiveTime, format: DateTimeFormat) -> askama::Result<String> {
|
||||||
let format_string = format.into();
|
let format_string = format.into();
|
||||||
trace!(
|
trace!(format=format_string, "formatting naivetime into string with format");
|
||||||
format = format_string,
|
|
||||||
"formatting naivetime into string with format"
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(v.format(format_string).to_string())
|
Ok(v.format(format_string).to_string())
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,7 @@ use lettre::{
|
|||||||
Address, AsyncTransport, Message,
|
Address, AsyncTransport, Message,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::utils::ApplicationError;
|
use crate::{models::User, utils::ApplicationError};
|
||||||
use brass_db::models::User;
|
|
||||||
|
|
||||||
use super::Mailer;
|
use super::Mailer;
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use lettre::{
|
use lettre::{
|
||||||
message::{Mailbox, MultiPart, SinglePart},
|
message::{Mailbox, MultiPart, SinglePart}, Address, AsyncTransport, Message
|
||||||
Address, AsyncTransport, Message,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::utils::ApplicationError;
|
use crate::{models::User, utils::ApplicationError};
|
||||||
use brass_db::models::User;
|
|
||||||
|
|
||||||
use super::Mailer;
|
use super::Mailer;
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ use crate::utils::manage_commands::{handle_command, parse_args};
|
|||||||
mod endpoints;
|
mod endpoints;
|
||||||
mod mail;
|
mod mail;
|
||||||
mod middleware;
|
mod middleware;
|
||||||
|
mod models;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
mod filters;
|
mod filters;
|
||||||
|
@ -11,7 +11,7 @@ use actix_web::{
|
|||||||
use futures_util::{future::LocalBoxFuture, FutureExt};
|
use futures_util::{future::LocalBoxFuture, FutureExt};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use brass_db::models::User;
|
use crate::models::User;
|
||||||
|
|
||||||
pub struct LoadCurrentUser;
|
pub struct LoadCurrentUser;
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user