Compare commits

..

10 Commits

205 changed files with 1460 additions and 991 deletions

View File

@ -16,3 +16,4 @@ SMTP_PORT="1025"
# SMTP_LOGIN=""
# SMTP_PASSWORD=""
SMTP_TLSTYPE="none"
RUST_LOG="info,brass_web=trace,brass_db=trace"

70
Cargo.lock generated
View File

@ -775,6 +775,7 @@ dependencies = [
"anyhow",
"async-std",
"brass-config",
"chrono",
"clap",
"sqlx",
]
@ -787,6 +788,19 @@ dependencies = [
"dotenvy",
]
[[package]]
name = "brass-db"
version = "0.1.0"
dependencies = [
"chrono",
"fake",
"rand 0.9.1",
"regex",
"serde",
"sqlx",
"tracing",
]
[[package]]
name = "brass-macros"
version = "0.1.0"
@ -809,13 +823,13 @@ dependencies = [
"argon2",
"askama",
"brass-config",
"brass-db",
"brass-macros",
"built",
"change-detection",
"chrono",
"fake",
"futures-util",
"garde",
"insta",
"lettre",
"maud",
@ -890,15 +904,6 @@ dependencies = [
"bytes",
]
[[package]]
name = "castaway"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
dependencies = [
"rustversion",
]
[[package]]
name = "cc"
version = "1.2.22"
@ -1007,20 +1012,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "concurrent-queue"
version = "2.5.0"
@ -1643,31 +1634,6 @@ dependencies = [
"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]]
name = "generic-array"
version = "0.14.7"
@ -3383,12 +3349,6 @@ dependencies = [
"path-slash",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stringprep"
version = "0.1.5"

View File

@ -1,5 +1,5 @@
[workspace]
members = [ "cli", "config", "macros", "web", ]
members = [ "cli", "config", "db", "macros", "web", ]
resolver = "2"
default-members = ["web"]

View File

@ -15,3 +15,4 @@ brass-config = { path = "../config" }
async-std = { version = "1.13.0", features = ["attributes"] }
sqlx = { version = "0.8.2", features = ["runtime-async-std", "postgres"] }
anyhow = "1.0.94"
chrono = "0.4.41"

View File

@ -1,6 +1,9 @@
use anyhow::Context;
use chrono::Local;
use sqlx::migrate::Migrate;
use sqlx::{migrate::Migrator, Executor};
use std::fs::File;
use std::io::Write;
use std::{
collections::HashMap,
path::{Path, PathBuf},
@ -28,9 +31,14 @@ enum Command {
Reset,
#[command(about = "Run all pending migrations on database")]
Migrate,
#[command(about = "Create a new migration")]
NewMigration { title: String },
#[command(about = "Prepare sqlx query metadata for offline compile-time verification")]
Prepare,
}
#[async_std::main]
#[allow(unused)]
async fn main() {
let cli = Cli::parse();
let config = load_config(&cli.environment).expect("Could not load config!");
@ -42,7 +50,6 @@ async fn main() {
create_db(&db_config)
.await
.expect("Failed creating database.");
migrate_db(&db_config)
.await
.expect("Failed migrating database.");
@ -51,20 +58,24 @@ async fn main() {
drop_db(&db_config)
.await
.expect("Failed dropping database.");
create_db(&db_config)
.await
.expect("Failed creating database.");
migrate_db(&db_config)
.await
.expect("Failed migrating database.");
},
}
Command::Migrate => {
migrate_db(&db_config)
.await
.expect("Failed migrating database.");
}
Command::NewMigration { title } => {
create_new_migration(&title)
.await
.expect("Failed creating new migration.");
}
Command::Prepare => prepare().await.expect("Failed preparing query metadata."),
}
}
@ -111,13 +122,7 @@ async fn migrate_db(db_config: &PgConnectOptions) -> anyhow::Result<()> {
.await
.context("Connection to database failed!")?;
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 migrations_path = db_package_root()?.join("migrations");
let migrator = Migrator::new(Path::new(&migrations_path))
.await
@ -148,3 +153,56 @@ async fn migrate_db(db_config: &PgConnectOptions) -> anyhow::Result<()> {
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(())
}
async fn prepare() -> anyhow::Result<()> {
let cargo = std::env::var("CARGO")
.map_err(|_| anyhow::anyhow!("Please invoke me using Cargo, e.g.: `cargo db <ARGS>`"))
.expect("Existence of CARGO env var is asserted by calling `ensure_sqlx_cli_installed`");
let mut sqlx_prepare_command = {
let mut cmd = std::process::Command::new(&cargo);
cmd.args(["sqlx", "prepare", "--", "--all-targets", "--all-features"]);
let cmd_cwd = db_package_root().context("Error finding the root of the db package!")?;
cmd.current_dir(cmd_cwd);
cmd
};
let o = sqlx_prepare_command
.output()
.context("Could not run {cargo} sqlx prepare!")?;
if !o.status.success() {
let error = anyhow::anyhow!(String::from_utf8_lossy(&o.stdout).to_string()).context("Error generating query metadata. Are you sure the database is running and all migrations are applied?");
return Err(error);
}
println!("Query data written to db/.sqlx directory; please check this into version control.");
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()?)
}

19
db/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[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"]

31
db/src/lib.rs Normal file
View File

@ -0,0 +1,31 @@
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 {}

View File

@ -1,8 +1,9 @@
use chrono::NaiveDateTime;
use sqlx::{query, PgPool};
use sqlx::{PgPool, query};
use super::{assignment_changeset::AssignmentChangeset, Function, Result};
use super::{AssignmentChangeset, Function, Result};
#[derive(Debug)]
pub struct Assignment {
pub event_id: i32,
pub availability_id: i32,

View File

@ -0,0 +1,186 @@
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(())
}

View File

@ -1,5 +1,5 @@
use chrono::{NaiveDate, NaiveDateTime};
use sqlx::{query, PgPool};
use sqlx::{PgPool, query};
use super::{Area, AvailabilityChangeset, Result, Role, User, UserFunction};

View File

@ -1,14 +1,10 @@
use chrono::{Days, NaiveDateTime};
use sqlx::PgPool;
use crate::{
utils::validation::{
start_date_time_lies_before_end_date_time, AsyncValidate, AsyncValidateError,
},
END_OF_DAY, START_OF_DAY,
};
use super::Availability;
use crate::{validation::{
start_date_time_lies_before_end_date_time, AsyncValidate, AsyncValidateError
}, END_OF_DAY, START_OF_DAY};
pub struct AvailabilityChangeset {
pub time: (NaiveDateTime, NaiveDateTime),

View File

@ -1,7 +1,7 @@
use chrono::{NaiveDate, NaiveDateTime};
use sqlx::{query, PgPool};
use sqlx::{PgPool, query};
use super::{event_changeset::EventChangeset, Clothing, Location, Result};
use super::{Clothing, EventChangeset, Location, Result};
#[derive(Clone, Debug)]
pub struct Event {

View File

@ -0,0 +1,248 @@
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
}
}

View File

@ -1,12 +1,11 @@
use chrono::{NaiveDate, NaiveDateTime};
use sqlx::{
PgPool,
postgres::{PgHasArrayType, PgTypeInfo},
query, PgPool,
query,
};
use crate::utils::ApplicationError;
use super::Function;
use super::{Function, Result};
pub struct ExportEventRow {
pub start_timestamp: NaiveDateTime,
@ -38,7 +37,7 @@ impl ExportEventRow {
pool: &PgPool,
time: (NaiveDate, NaiveDate),
area: i32,
) -> Result<Vec<ExportEventRow>, ApplicationError> {
) -> Result<Vec<ExportEventRow>> {
let rows = query!(
"select
event.starttimestamp,

View File

@ -1,8 +1,9 @@
use std::fmt::Display;
use crate::utils::ApplicationError;
use serde::Serialize;
use crate::UnsupportedEnumValue;
#[derive(sqlx::Type, Debug, Clone, Copy, PartialEq, Eq, Serialize, PartialOrd, Ord)]
#[sqlx(type_name = "function", rename_all = "lowercase")]
pub enum Function {
@ -22,16 +23,16 @@ impl Display for Function {
}
impl TryFrom<u8> for Function {
type Error = ApplicationError;
type Error = UnsupportedEnumValue;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Function::Posten),
5 => Ok(Function::Fuehrungsassistent),
10 => Ok(Function::Wachhabender),
_ => Err(ApplicationError::UnsupportedEnumValue {
value: value.to_string(),
enum_name: String::from("Function"),
_ => Err(UnsupportedEnumValue {
value,
enum_name: "Function",
}),
}
}

View File

@ -1,8 +1,6 @@
use sqlx::{query, PgPool};
use sqlx::{PgPool, query};
use super::Area;
use super::Result;
use super::{Area, Result};
#[derive(Clone, Debug)]
pub struct Location {

View File

@ -25,7 +25,7 @@ pub use assignment_changeset::{AssignmentChangeset, AssignmentContext};
pub use availability::Availability;
pub use availability_assignment_state::AvailabilityAssignmentState;
pub use availability_changeset::{
find_free_date_time_slots, AvailabilityChangeset, AvailabilityContext,
AvailabilityChangeset, AvailabilityContext, find_free_date_time_slots,
};
pub use clothing::Clothing;
pub use event::Event;
@ -33,7 +33,7 @@ pub use event_changeset::{EventChangeset, EventContext};
pub use export_event_row::{ExportEventRow, SimpleAssignment};
pub use function::Function;
pub use location::Location;
pub use password_reset::{NoneToken, PasswordReset, Token};
pub use password_reset::PasswordReset;
pub use registration::Registration;
pub use role::Role;
pub use user::User;
@ -42,17 +42,4 @@ pub use user_funtion::UserFunction;
pub use vehicle::Vehicle;
pub use vehicle_assignment::VehicleAssignment;
use chrono::NaiveDateTime;
type Result<T> = std::result::Result<T, sqlx::Error>;
fn start_date_time_lies_before_end_date_time<T>(
value: &(NaiveDateTime, NaiveDateTime),
_context: &T,
) -> garde::Result {
if value.0 >= value.1 {
return Err(garde::Error::new("endtime can't lie before starttime"));
}
Ok(())
}

View File

@ -1,19 +1,9 @@
use chrono::TimeDelta;
use sqlx::{query_as, PgPool};
use sqlx::{PgPool, query_as};
use crate::support::{Token, generate_token_and_expiration};
use super::Result;
use crate::utils::token_generation::generate_token_and_expiration;
pub trait Token {
async fn delete(&self, pool: &PgPool) -> Result<()>;
}
pub struct NoneToken {}
impl Token for NoneToken {
async fn delete(&self, _pool: &PgPool) -> Result<()> {
unimplemented!()
}
}
#[derive(Debug)]
pub struct PasswordReset {

View File

@ -1,7 +1,5 @@
use chrono::TimeDelta;
use sqlx::{query_as, PgPool};
use crate::utils::token_generation::generate_token_and_expiration;
use sqlx::{PgPool, query_as};
#[derive(Debug)]
pub struct Registration {
@ -9,7 +7,9 @@ pub struct Registration {
pub userid: i32,
}
use super::{password_reset::Token, Result};
use crate::support::{Token, generate_token_and_expiration};
use super::Result;
impl Registration {
pub async fn insert_new_for_user(pool: &PgPool, user_id: i32) -> Result<Registration> {

Some files were not shown because too many files have changed in this diff Show More