diff --git a/Cargo.lock b/Cargo.lock index ef2f92d..30c7155 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1238,9 +1238,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" +checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" [[package]] name = "lock_api" @@ -1540,9 +1540,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1403e8401ad5dedea73c626b99758535b342502f8d1e361f4a2dd952749122" +checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" dependencies = [ "thiserror", "ucd-trie", @@ -1550,9 +1550,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be99c4c1d2fc2769b1d00239431d711d08f6efedcecb8b6e30707160aee99c15" +checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" dependencies = [ "pest", "pest_generator", @@ -1560,9 +1560,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56094789873daa36164de2e822b3888c6ae4b4f9da555a1103587658c805b1e" +checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" dependencies = [ "pest", "pest_meta", @@ -1573,9 +1573,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6733073c7cff3d8459fda0e42f13a047870242aed8b509fe98000928975f359e" +checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" dependencies = [ "once_cell", "pest", @@ -1655,9 +1655,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "polyval" @@ -1964,9 +1964,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.15" +version = "0.37.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece" +checksum = "8bbfc1d1c7c40c01715f47d71444744a81669ca84e8b63e25a55e169b1f86433" dependencies = [ "bitflags 1.3.2", "errno", @@ -2489,9 +2489,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cd2598a37719e3cd4c28af93f978506a97a2920ef4d96e4b12e38b8cbc8940" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -2529,10 +2529,11 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.38" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ + "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", diff --git a/README.md b/README.md index b079d5b..d40bc4c 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,26 @@ -# TODO -- [ ] Allow sign-outs only >2h before event - # Icons - Regatta: 🏅 - Lange Ausfahrt: 💪 - Wanderfahrt: ⛱ # Notes / Bugfixes -- [] max_people = 0 -> Rot hervorheben, dass Ausfahrt abgesagt wurde? -- [] my trips for cox -- [] add `trip_type` (id, name, desc, question, icon) with a FK to `trip_details` +## Frontend +- [] add UI for `trip_type` +- [] support esc to close sidebar +- [] FAQ page + +## Backend +- [] Allow sign-outs only >2h before event - [] add `always_show` to `planned_trips` (e.g. for wanderfahrten) -- [] exactly same time -> deny registration - [] `planned_events` -> ICS feed to be [integrated](https://icscalendar.com/shortcode-overview/) in homepage calendar +- [] Notification system (-> signal msgs?) e.g. if new event; somebody unregistered # Nice to have +## Frontend +- [] my trips for cox + +## Backend +- [] exactly same time -> deny registration - [] automatically add regular planned trip - [] User sync w/ nextcloud - [] remove key from src/rest/admin/rss.rs (line 8); at least before putting code somewhere public diff --git a/migration.sql b/migration.sql index 01a9b5e..aaa127a 100644 --- a/migration.sql +++ b/migration.sql @@ -8,19 +8,29 @@ CREATE TABLE IF NOT EXISTS "user" ( "deleted" boolean NOT NULL DEFAULT FALSE ); +CREATE TABLE IF NOT EXISTS "trip_type" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" text NOT NULL UNIQUE, + "desc" text NOT NULL, + "question" text NOT NULL, + "icon" text NOT NULL +); + CREATE TABLE IF NOT EXISTS "trip_details" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "planned_starting_time" text NOT NULL, "max_people" INTEGER NOT NULL, "day" TEXT NOT NULL, - "notes" TEXT + "allow_guests" boolean NOT NULL default false, + "notes" TEXT, + "trip_type_id" INTEGER, + FOREIGN KEY(trip_type_id) REFERENCES trip_type(id) ); CREATE TABLE IF NOT EXISTS "planned_event" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" text NOT NULL, "planned_amount_cox" INTEGER unsigned NOT NULL, - "allow_guests" boolean NOT NULL default false, "trip_details_id" INTEGER NOT NULL, "created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(trip_details_id) REFERENCES trip_details(id) ON DELETE CASCADE @@ -52,3 +62,4 @@ CREATE TABLE IF NOT EXISTS "log" ( "msg" text NOT NULL, "created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP ); + diff --git a/seeds.sql b/seeds.sql index 5b67d82..b860c99 100644 --- a/seeds.sql +++ b/seeds.sql @@ -12,3 +12,4 @@ INSERT INTO "planned_event" (name, planned_amount_cox, trip_details_id) VALUES(' INSERT INTO "trip_details" (planned_starting_time, max_people, day, notes) VALUES('11:00', 1, '1970-01-02', 'trip_details for trip from cox'); INSERT INTO "trip" (cox_id, trip_details_id) VALUES(4, 2); +INSERT INTO "trip_type" (name, desc, question, icon) VALUES ('Regatta', 'Regatta!', 'Kein normales Event. Das ist eine Regatta! Willst du wirklich teilnehmen?', '🏅') diff --git a/src/model/mod.rs b/src/model/mod.rs index 0115c70..73be211 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -3,22 +3,23 @@ use serde::Serialize; use sqlx::SqlitePool; use self::{ - planned_event::{PlannedEvent, PlannedEventWithUser}, - trip::{Trip, TripWithUser}, + planned_event::{PlannedEvent, PlannedEventWithUserAndTriptype}, + trip::{Trip, TripWithUserAndType}, }; pub mod log; pub mod planned_event; pub mod trip; pub mod tripdetails; +pub mod triptype; pub mod user; pub mod usertrip; #[derive(Serialize)] pub struct Day { day: NaiveDate, - planned_events: Vec, - trips: Vec, + planned_events: Vec, + trips: Vec, } impl Day { @@ -29,4 +30,21 @@ impl Day { trips: Trip::get_for_day(db, day).await, } } + pub async fn new_guest(db: &SqlitePool, day: NaiveDate) -> Self { + let mut day = Self::new(db, day).await; + + day.planned_events = day + .planned_events + .into_iter() + .filter(|e| e.planned_event.allow_guests) + .collect(); + + day.trips = day + .trips + .into_iter() + .filter(|t| t.trip.allow_guests) + .collect(); + + day + } } diff --git a/src/model/planned_event.rs b/src/model/planned_event.rs index 4d1fdd8..3364a34 100644 --- a/src/model/planned_event.rs +++ b/src/model/planned_event.rs @@ -1,26 +1,28 @@ use chrono::NaiveDate; use serde::Serialize; -use sqlx::SqlitePool; +use sqlx::{FromRow, SqlitePool}; -use super::tripdetails::TripDetails; +use super::{tripdetails::TripDetails, triptype::TripType, user::User}; -#[derive(Serialize, Clone)] +#[derive(Serialize, Clone, FromRow)] pub struct PlannedEvent { pub id: i64, name: String, planned_amount_cox: i64, - allow_guests: bool, trip_details_id: i64, planned_starting_time: String, max_people: i64, day: String, notes: Option, + pub allow_guests: bool, + trip_type_id: Option, } #[derive(Serialize)] -pub struct PlannedEventWithUser { +pub struct PlannedEventWithUserAndTriptype { #[serde(flatten)] - planned_event: PlannedEvent, + pub planned_event: PlannedEvent, + trip_type: Option, cox_needed: bool, cox: Vec, rower: Vec, @@ -39,7 +41,8 @@ impl PlannedEvent { sqlx::query_as!( Self, " -SELECT planned_event.id, name, planned_amount_cox, allow_guests, trip_details_id, planned_starting_time, max_people, day, notes +SELECT + planned_event.id, planned_event.name, planned_amount_cox, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id FROM planned_event INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id WHERE planned_event.id like ? @@ -51,11 +54,14 @@ WHERE planned_event.id like ? .ok() } - pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec { + pub async fn get_for_day( + db: &SqlitePool, + day: NaiveDate, + ) -> Vec { let day = format!("{day}"); let events = sqlx::query_as!( PlannedEvent, - "SELECT planned_event.id, name, planned_amount_cox, allow_guests, trip_details_id, planned_starting_time, max_people, day, notes + "SELECT planned_event.id, planned_event.name, planned_amount_cox, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id FROM planned_event INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id WHERE day=?", @@ -68,17 +74,23 @@ WHERE day=?", let mut ret = Vec::new(); for event in events { let cox = event.get_all_cox(db).await; - ret.push(PlannedEventWithUser { + let mut trip_type = None; + if let Some(trip_type_id) = event.trip_type_id { + trip_type = TripType::find_by_id(db, trip_type_id).await; + } + ret.push(PlannedEventWithUserAndTriptype { planned_event: event.clone(), cox_needed: event.planned_amount_cox > cox.len() as i64, cox, rower: event.get_all_rower(db).await, + trip_type, }); } ret } async fn get_all_cox(&self, db: &SqlitePool) -> Vec { + //TODO: switch to join sqlx::query_as!( Registration, " @@ -96,6 +108,7 @@ FROM trip WHERE planned_event_id = ? } async fn get_all_rower(&self, db: &SqlitePool) -> Vec { + //TODO: switch to join sqlx::query_as!( Registration, " @@ -112,16 +125,34 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_even .unwrap() //Okay, as PlannedEvent can only be created with proper DB backing } + //TODO: add tests + pub async fn is_rower_registered(&self, db: &SqlitePool, user: &User) -> bool { + let is_rower = sqlx::query!( + "SELECT count(*) as amount + FROM user_trip + WHERE trip_details_id = + (SELECT trip_details_id FROM planned_event WHERE id = ?) + AND user_id = ?", + self.id, + user.id + ) + .fetch_one(db) + .await + .unwrap(); //Okay, bc planned_event can only be created with proper DB backing + is_rower.amount > 0 + } + pub async fn create( db: &SqlitePool, name: String, planned_amount_cox: i32, - allow_guests: bool, trip_details: TripDetails, ) { sqlx::query!( - "INSERT INTO planned_event(name, planned_amount_cox, allow_guests, trip_details_id) VALUES(?, ?, ?, ?)", - name, planned_amount_cox, allow_guests, trip_details.id + "INSERT INTO planned_event(name, planned_amount_cox, trip_details_id) VALUES(?, ?, ?)", + name, + planned_amount_cox, + trip_details.id ) .execute(db) .await @@ -159,7 +190,7 @@ mod test { let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); - PlannedEvent::create(&pool, "new-event".into(), 2, false, trip_details).await; + PlannedEvent::create(&pool, "new-event".into(), 2, trip_details).await; let res = PlannedEvent::get_for_day(&pool, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()).await; diff --git a/src/model/trip.rs b/src/model/trip.rs index c40bbfa..6314e7a 100644 --- a/src/model/trip.rs +++ b/src/model/trip.rs @@ -5,6 +5,7 @@ use sqlx::SqlitePool; use super::{ planned_event::{PlannedEvent, Registration}, tripdetails::TripDetails, + triptype::TripType, user::CoxUser, }; @@ -18,13 +19,16 @@ pub struct Trip { max_people: i64, day: String, notes: Option, + pub allow_guests: bool, + trip_type_id: Option, } #[derive(Serialize)] -pub struct TripWithUser { +pub struct TripWithUserAndType { #[serde(flatten)] - trip: Trip, + pub trip: Trip, rower: Vec, + trip_type: Option, } impl Trip { @@ -44,7 +48,7 @@ impl Trip { sqlx::query_as!( Self, " -SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes +SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id FROM trip INNER JOIN trip_details ON trip.trip_details_id = trip_details.id INNER JOIN user ON trip.cox_id = user.id @@ -63,19 +67,7 @@ WHERE trip.id=? cox: &CoxUser, planned_event: &PlannedEvent, ) -> Result<(), CoxHelpError> { - let is_rower = sqlx::query!( - "SELECT count(*) as amount - FROM user_trip - WHERE trip_details_id = - (SELECT trip_details_id FROM planned_event WHERE id = ?) - AND user_id = ?", - planned_event.id, - cox.id - ) - .fetch_one(db) - .await - .unwrap(); //Okay, bc planned_event can only be created with proper DB backing - if is_rower.amount > 0 { + if planned_event.is_rower_registered(db, &cox).await { return Err(CoxHelpError::AlreadyRegisteredAsRower); } @@ -92,12 +84,12 @@ WHERE trip.id=? } } - pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec { + pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec { let day = format!("{day}"); let trips = sqlx::query_as!( Trip, " -SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes +SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id FROM trip INNER JOIN trip_details ON trip.trip_details_id = trip_details.id INNER JOIN user ON trip.cox_id = user.id @@ -108,10 +100,16 @@ WHERE day=? .fetch_all(db) .await .unwrap(); //TODO: fixme + let mut ret = Vec::new(); for trip in trips { - ret.push(TripWithUser { + let mut trip_type = None; + if let Some(trip_type_id) = trip.trip_type_id { + trip_type = TripType::find_by_id(db, trip_type_id).await; + } + ret.push(TripWithUserAndType { trip: trip.clone(), + trip_type, rower: trip.get_all_rower(db).await, }); } diff --git a/src/model/tripdetails.rs b/src/model/tripdetails.rs index 663620d..0026b2b 100644 --- a/src/model/tripdetails.rs +++ b/src/model/tripdetails.rs @@ -8,6 +8,8 @@ pub struct TripDetails { max_people: i64, day: String, notes: Option, + pub allow_guests: bool, + trip_type_id: Option, } impl TripDetails { @@ -15,7 +17,7 @@ impl TripDetails { sqlx::query_as!( TripDetails, " -SELECT id, planned_starting_time, max_people, day, notes +SELECT id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id FROM trip_details WHERE id like ? ", @@ -33,13 +35,17 @@ WHERE id like ? max_people: i32, day: String, notes: Option, + allow_guests: bool, + trip_type_id: Option, ) -> i64 { let query = sqlx::query!( - "INSERT INTO trip_details(planned_starting_time, max_people, day, notes) VALUES(?, ?, ?, ?)" , + "INSERT INTO trip_details(planned_starting_time, max_people, day, notes, allow_guests, trip_type_id) VALUES(?, ?, ?, ?, ?, ?)" , planned_starting_time, max_people, day, - notes + notes, + allow_guests, + trip_type_id ) .execute(db) .await @@ -57,14 +63,7 @@ WHERE id like ? .unwrap(); //TODO: fixme let amount_currently_registered = i64::from(amount_currently_registered.count); - let amount_allowed_to_register = - sqlx::query!("SELECT max_people FROM trip_details WHERE id = ?", self.id) - .fetch_one(db) - .await - .unwrap(); //Okay, TripDetails can only be created if self.id exists - let amount_allowed_to_register = amount_allowed_to_register.max_people; - - amount_currently_registered >= amount_allowed_to_register + amount_currently_registered >= self.max_people } } @@ -94,11 +93,29 @@ mod test { let pool = testdb!(); assert_eq!( - TripDetails::create(&pool, "10:00".into(), 2, "1970-01-01".into(), None).await, + TripDetails::create( + &pool, + "10:00".into(), + 2, + "1970-01-01".into(), + None, + false, + None + ) + .await, 3, ); assert_eq!( - TripDetails::create(&pool, "10:00".into(), 2, "1970-01-01".into(), None).await, + TripDetails::create( + &pool, + "10:00".into(), + 2, + "1970-01-01".into(), + None, + false, + None + ) + .await, 4, ); } @@ -115,4 +132,6 @@ mod test { fn test_true_full() { //TODO: register user for trip_details = 1; check if is_full returns true } + + //TODO: add new tripdetails test with trip_type != None } diff --git a/src/model/triptype.rs b/src/model/triptype.rs new file mode 100644 index 0000000..f13e634 --- /dev/null +++ b/src/model/triptype.rs @@ -0,0 +1,55 @@ +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, SqlitePool}; + +#[derive(FromRow, Debug, Serialize, Deserialize, Clone)] +pub struct TripType { + pub id: i64, + name: String, + desc: String, + question: String, + icon: String, +} + +impl TripType { + pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option { + sqlx::query_as!( + Self, + " +SELECT id, name, desc, question, icon +FROM trip_type +WHERE id like ? + ", + id + ) + .fetch_one(db) + .await + .ok() + } + + pub async fn all(db: &SqlitePool) -> Vec { + sqlx::query_as!( + Self, + " +SELECT id, name, desc, question, icon +FROM trip_type + " + ) + .fetch_all(db) + .await + .unwrap() //TODO: fixme + } +} + +#[cfg(test)] +mod test { + use crate::testdb; + + use sqlx::SqlitePool; + + #[sqlx::test] + fn test_find_true() { + let pool = testdb!(); + } + + //TODO: write tests +} diff --git a/src/model/user.rs b/src/model/user.rs index ac9f85b..c9d6a63 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -16,8 +16,8 @@ pub struct User { pub name: String, pw: Option, pub is_cox: bool, - is_admin: bool, - is_guest: bool, + pub is_admin: bool, + pub is_guest: bool, #[serde(default = "bool::default")] deleted: bool, } diff --git a/src/model/usertrip.rs b/src/model/usertrip.rs index 3a8e481..a12cab7 100644 --- a/src/model/usertrip.rs +++ b/src/model/usertrip.rs @@ -14,6 +14,10 @@ impl UserTrip { return Err(UserTripError::EventAlreadyFull); } + if user.is_guest && !trip_details.allow_guests { + return Err(UserTripError::GuestNotAllowedForThisEvent); + } + //check if cox if own event let is_cox = sqlx::query!( "SELECT count(*) as amount @@ -30,6 +34,7 @@ impl UserTrip { return Err(UserTripError::CantRegisterAtOwnEvent); } + //TODO: can probably move to trip.rs? //check if cox if planned_event let is_cox = sqlx::query!( "SELECT count(*) as amount @@ -80,6 +85,7 @@ pub enum UserTripError { AlreadyRegisteredAsCox, EventAlreadyFull, CantRegisterAtOwnEvent, + GuestNotAllowedForThisEvent, } #[cfg(test)] @@ -180,4 +186,19 @@ mod test { assert_eq!(result, UserTripError::AlreadyRegisteredAsCox); } + + #[sqlx::test] + fn test_fail_create_guest() { + let pool = testdb!(); + + let user = User::find_by_name(&pool, "guest".into()).await.unwrap(); + + let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); + + let result = UserTrip::create(&pool, &user, &trip_details) + .await + .expect_err("Not allowed for guests"); + + assert_eq!(result, UserTripError::GuestNotAllowedForThisEvent); + } } diff --git a/src/rest/admin/planned_event.rs b/src/rest/admin/planned_event.rs index 61cd20f..a5750df 100644 --- a/src/rest/admin/planned_event.rs +++ b/src/rest/admin/planned_event.rs @@ -18,6 +18,7 @@ struct AddPlannedEventForm { planned_starting_time: String, max_people: i32, notes: Option, + trip_type: Option, } #[post("/planned-event", data = "")] @@ -33,6 +34,8 @@ async fn create( data.max_people, data.day.clone(), data.notes.clone(), + data.allow_guests, + data.trip_type, ) .await; @@ -41,14 +44,7 @@ async fn create( //the object //TODO: fix clone() - PlannedEvent::create( - db, - data.name.clone(), - data.planned_amount_cox, - data.allow_guests, - trip_details, - ) - .await; + PlannedEvent::create(db, data.name.clone(), data.planned_amount_cox, trip_details).await; Flash::success(Redirect::to("/"), "Successfully planned the event") } diff --git a/src/rest/auth.rs b/src/rest/auth.rs index 84da633..e42f80a 100644 --- a/src/rest/auth.rs +++ b/src/rest/auth.rs @@ -41,7 +41,6 @@ async fn login( ) -> Flash { let user = User::login(db, login.name.clone(), login.password.clone()).await; - //TODO: be able to use ? for login. This would get rid of the following match clause. let user = match user { Ok(user) => user, Err(LoginError::NoPasswordSet(user)) => { diff --git a/src/rest/cox.rs b/src/rest/cox.rs index 5e379ea..b2715da 100644 --- a/src/rest/cox.rs +++ b/src/rest/cox.rs @@ -14,13 +14,16 @@ use crate::model::{ user::CoxUser, }; -//TODO: add constraints (e.g. planned_amount_cox > 0) #[derive(FromForm)] struct AddTripForm { day: String, + //TODO: properly parse `planned_starting_time` planned_starting_time: String, + #[field(validate = range(1..))] max_people: i32, notes: Option, + trip_type: Option, + allow_guests: bool, } #[post("/trip", data = "")] @@ -32,6 +35,8 @@ async fn create(db: &State, data: Form, cox: CoxUser) - data.max_people, data.day.clone(), data.notes.clone(), + data.allow_guests, + data.trip_type, ) .await; let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc just diff --git a/src/rest/mod.rs b/src/rest/mod.rs index 4c8222b..78eb77d 100644 --- a/src/rest/mod.rs +++ b/src/rest/mod.rs @@ -13,6 +13,7 @@ use sqlx::SqlitePool; use crate::model::{ log::Log, tripdetails::TripDetails, + triptype::TripType, user::User, usertrip::{UserTrip, UserTripError}, Day, @@ -22,25 +23,39 @@ mod admin; mod auth; mod cox; +fn amount_days_to_show(is_cox: bool) -> i64 { + if is_cox { + let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); + end_of_year + .signed_duration_since(Local::now().date_naive()) + .num_days() + + 1 + } else { + 6 + } +} + #[get("/")] async fn index(db: &State, user: User, flash: Option>) -> Template { let mut days = Vec::new(); - let mut show_next_n_days = 6; - if user.is_cox { - let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); - show_next_n_days = end_of_year - .signed_duration_since(Local::now().date_naive()) - .num_days() - + 1; + let mut context = Context::new(); + + if user.is_cox || user.is_admin { + let triptypes = TripType::all(db).await; + context.insert("trip_types", &triptypes); } + let show_next_n_days = amount_days_to_show(user.is_cox); for i in 0..show_next_n_days { let date = (Local::now() + Duration::days(i)).date_naive(); - days.push(Day::new(db, date).await); - } - let mut context = Context::new(); + if user.is_guest { + days.push(Day::new_guest(db, date).await); + } else { + days.push(Day::new(db, date).await); + } + } if let Some(msg) = flash { context.insert("flash", &msg.into_inner()); @@ -81,6 +96,10 @@ async fn join(db: &State, trip_details_id: i64, user: User) -> Flash Redirect::to("/"), "Du kannst bei einer selbst ausgeschriebenen Fahrt nicht mitrudern ;)", ), + Err(UserTripError::GuestNotAllowedForThisEvent) => Flash::error( + Redirect::to("/"), + "Bei dieser Ausfahrt können leider keine Gäste mitfahren.", + ), } } diff --git a/templates/admin/user/index.html.tera b/templates/admin/user/index.html.tera index 8211e42..7f6f059 100644 --- a/templates/admin/user/index.html.tera +++ b/templates/admin/user/index.html.tera @@ -50,10 +50,13 @@ {% if user.pw %} Passwort zurücksetzen {% endif %} - User löschen -
- + {% endfor %} diff --git a/templates/auth/login.html.tera b/templates/auth/login.html.tera index f16fe0c..86bc354 100644 --- a/templates/auth/login.html.tera +++ b/templates/auth/login.html.tera @@ -14,10 +14,10 @@
- {{ macros::input(label='Name', name='name', type='input', required=true, class='rounded-t-md') }} + {{ macros::input(label='Name', name='name', type='input', required=true, class='rounded-t-md',hide_label=true) }}
- {{ macros::input(label='Passwort', name='password', type='password', class='rounded-b-md') }} + {{ macros::input(label='Passwort', name='password', type='password', class='rounded-b-md',hide_label=true) }}
diff --git a/templates/auth/set-pw.html.tera b/templates/auth/set-pw.html.tera index 1670f97..93d5582 100644 --- a/templates/auth/set-pw.html.tera +++ b/templates/auth/set-pw.html.tera @@ -9,10 +9,10 @@
- {{ macros::input(label='Passwort', name='password', type='password', required=true, class='rounded-t-md') }} + {{ macros::input(label='Passwort', name='password', type='password', required=true, class='rounded-t-md',hide_label=true) }}
- {{ macros::input(label='Passwort bestätigen', name='password_confirm', type='password', required=true, class='rounded-b-md') }} + {{ macros::input(label='Passwort bestätigen', name='password_confirm', type='password', required=true, class='rounded-b-md',hide_label=true) }}
diff --git a/templates/forms/event.html.tera b/templates/forms/event.html.tera index 1eb14fd..72eca77 100644 --- a/templates/forms/event.html.tera +++ b/templates/forms/event.html.tera @@ -7,7 +7,7 @@ {{ macros::input(label='Startzeit', name='planned_starting_time', type='time', required=true) }} {{ macros::input(label='Anzahl Steuerleute', name='planned_amount_cox', type='number', required=true, min='0') }} {{ macros::input(label='Anzahl Ruderer (ohne Steuerperson)', name='max_people', type='number', required=true, min='0') }} - {{ macros::checkbox(label='Gäste erlauben', name='max_allow_guestspeople') }} + {{ macros::checkbox(label='Gäste erlauben', name='allow_guests') }} {{ macros::input(label='Anmerkungen', name='notes', type='input') }} {{ macros::input(label='Startzeit (zB "10:00")', name='planned_starting_time', type='time', required=true) }} {{ macros::input(label='Anzahl Ruderer (ohne Steuerperson)', name='max_people', type='number', required=true, min='0') }} + {{ macros::checkbox(label='Gäste erlauben', name='allow_guests') }} {{ macros::input(label='Anmerkungen', name='notes', type='input') }} +{% macro input(label, name, type, required=false, class='rounded-md', value='', min='', hide_label=false) %} +
+ + +
{% endmacro input %} {% macro checkbox(label, name, id='', checked=false) %} @@ -41,7 +43,7 @@
{% endmacro alert %} -{% macro box(participants, empty_seats, header='Freie Plätze:', text='Keine Ruderer angemeldet', bg='primary-600', color='white') %} +{% macro box(participants, empty_seats='', header='Freie Plätze:', text='Keine Ruderer angemeldet', bg='primary-600', color='white') %}
{{ header }} {{ empty_seats }}
{% if participants | length > 0 %} diff --git a/templates/index.html.tera b/templates/index.html.tera index 47c9cfc..50534a6 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -142,13 +142,19 @@