Merge commit 'e5088bddb0ab694abac5f6d2ab2d76a5042e52bb' into feature/frontend-triptype
# Conflicts: # templates/index.html.tera
This commit is contained in:
		
							
								
								
									
										37
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										37
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1238,9 +1238,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "linux-raw-sys" | name = "linux-raw-sys" | ||||||
| version = "0.3.4" | version = "0.3.6" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" | checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "lock_api" | name = "lock_api" | ||||||
| @@ -1540,9 +1540,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "pest" | name = "pest" | ||||||
| version = "2.5.7" | version = "2.6.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "7b1403e8401ad5dedea73c626b99758535b342502f8d1e361f4a2dd952749122" | checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "thiserror", |  "thiserror", | ||||||
|  "ucd-trie", |  "ucd-trie", | ||||||
| @@ -1550,9 +1550,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "pest_derive" | name = "pest_derive" | ||||||
| version = "2.5.7" | version = "2.6.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "be99c4c1d2fc2769b1d00239431d711d08f6efedcecb8b6e30707160aee99c15" | checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "pest", |  "pest", | ||||||
|  "pest_generator", |  "pest_generator", | ||||||
| @@ -1560,9 +1560,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "pest_generator" | name = "pest_generator" | ||||||
| version = "2.5.7" | version = "2.6.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "e56094789873daa36164de2e822b3888c6ae4b4f9da555a1103587658c805b1e" | checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "pest", |  "pest", | ||||||
|  "pest_meta", |  "pest_meta", | ||||||
| @@ -1573,9 +1573,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "pest_meta" | name = "pest_meta" | ||||||
| version = "2.5.7" | version = "2.6.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "6733073c7cff3d8459fda0e42f13a047870242aed8b509fe98000928975f359e" | checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "once_cell", |  "once_cell", | ||||||
|  "pest", |  "pest", | ||||||
| @@ -1655,9 +1655,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "pkg-config" | name = "pkg-config" | ||||||
| version = "0.3.26" | version = "0.3.27" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "polyval" | name = "polyval" | ||||||
| @@ -1964,9 +1964,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "rustix" | name = "rustix" | ||||||
| version = "0.37.15" | version = "0.37.18" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece" | checksum = "8bbfc1d1c7c40c01715f47d71444744a81669ca84e8b63e25a55e169b1f86433" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bitflags 1.3.2", |  "bitflags 1.3.2", | ||||||
|  "errno", |  "errno", | ||||||
| @@ -2489,9 +2489,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "tokio-stream" | name = "tokio-stream" | ||||||
| version = "0.1.13" | version = "0.1.14" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "76cd2598a37719e3cd4c28af93f978506a97a2920ef4d96e4b12e38b8cbc8940" | checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "futures-core", |  "futures-core", | ||||||
|  "pin-project-lite", |  "pin-project-lite", | ||||||
| @@ -2529,10 +2529,11 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "tracing" | name = "tracing" | ||||||
| version = "0.1.38" | version = "0.1.37" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  |  "cfg-if", | ||||||
|  "pin-project-lite", |  "pin-project-lite", | ||||||
|  "tracing-attributes", |  "tracing-attributes", | ||||||
|  "tracing-core", |  "tracing-core", | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,20 +1,26 @@ | |||||||
| # TODO |  | ||||||
| - [ ] Allow sign-outs only >2h before event |  | ||||||
|  |  | ||||||
| # Icons | # Icons | ||||||
| - Regatta: 🏅 | - Regatta: 🏅 | ||||||
| - Lange Ausfahrt: 💪 | - Lange Ausfahrt: 💪 | ||||||
| - Wanderfahrt: ⛱ | - Wanderfahrt: ⛱ | ||||||
|  |  | ||||||
| # Notes / Bugfixes | # Notes / Bugfixes | ||||||
| - [] max_people = 0 -> Rot hervorheben, dass Ausfahrt abgesagt wurde? | ## Frontend | ||||||
| - [] my trips for cox | - [] add UI for `trip_type` | ||||||
| - [] add `trip_type` (id, name, desc, question, icon) with a FK to `trip_details` | - [] 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) | - [] 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 | - [] `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 | # Nice to have | ||||||
|  | ## Frontend | ||||||
|  | - [] my trips for cox | ||||||
|  |  | ||||||
|  | ## Backend | ||||||
|  | - [] exactly same time -> deny registration | ||||||
| - [] automatically add regular planned trip | - [] automatically add regular planned trip | ||||||
| - [] User sync w/ nextcloud | - [] User sync w/ nextcloud | ||||||
| - [] remove key from src/rest/admin/rss.rs (line 8); at least before putting code somewhere public | - [] remove key from src/rest/admin/rss.rs (line 8); at least before putting code somewhere public | ||||||
|   | |||||||
| @@ -8,19 +8,29 @@ CREATE TABLE IF NOT EXISTS "user" ( | |||||||
| 	"deleted" boolean NOT NULL DEFAULT FALSE | 	"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" ( | CREATE TABLE IF NOT EXISTS "trip_details" ( | ||||||
| 	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, | 	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||||
| 	"planned_starting_time" text NOT NULL, | 	"planned_starting_time" text NOT NULL, | ||||||
| 	"max_people" INTEGER NOT NULL, | 	"max_people" INTEGER NOT NULL, | ||||||
| 	"day" TEXT 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" ( | CREATE TABLE IF NOT EXISTS "planned_event" ( | ||||||
| 	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, | 	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||||
| 	"name" text NOT NULL, | 	"name" text NOT NULL, | ||||||
| 	"planned_amount_cox" INTEGER unsigned NOT NULL, | 	"planned_amount_cox" INTEGER unsigned NOT NULL, | ||||||
| 	"allow_guests" boolean NOT NULL default false, |  | ||||||
| 	"trip_details_id" INTEGER NOT NULL, | 	"trip_details_id" INTEGER NOT NULL, | ||||||
| 	"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, | 	"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||||
| 	FOREIGN KEY(trip_details_id) REFERENCES trip_details(id) ON DELETE CASCADE | 	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, | 	"msg" text NOT NULL, | ||||||
| 	"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP | 	"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP | ||||||
| ); | ); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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_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" (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?', '🏅') | ||||||
|   | |||||||
| @@ -3,22 +3,23 @@ use serde::Serialize; | |||||||
| use sqlx::SqlitePool; | use sqlx::SqlitePool; | ||||||
|  |  | ||||||
| use self::{ | use self::{ | ||||||
|     planned_event::{PlannedEvent, PlannedEventWithUser}, |     planned_event::{PlannedEvent, PlannedEventWithUserAndTriptype}, | ||||||
|     trip::{Trip, TripWithUser}, |     trip::{Trip, TripWithUserAndType}, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| pub mod log; | pub mod log; | ||||||
| pub mod planned_event; | pub mod planned_event; | ||||||
| pub mod trip; | pub mod trip; | ||||||
| pub mod tripdetails; | pub mod tripdetails; | ||||||
|  | pub mod triptype; | ||||||
| pub mod user; | pub mod user; | ||||||
| pub mod usertrip; | pub mod usertrip; | ||||||
|  |  | ||||||
| #[derive(Serialize)] | #[derive(Serialize)] | ||||||
| pub struct Day { | pub struct Day { | ||||||
|     day: NaiveDate, |     day: NaiveDate, | ||||||
|     planned_events: Vec<PlannedEventWithUser>, |     planned_events: Vec<PlannedEventWithUserAndTriptype>, | ||||||
|     trips: Vec<TripWithUser>, |     trips: Vec<TripWithUserAndType>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Day { | impl Day { | ||||||
| @@ -29,4 +30,21 @@ impl Day { | |||||||
|             trips: Trip::get_for_day(db, day).await, |             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 | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,26 +1,28 @@ | |||||||
| use chrono::NaiveDate; | use chrono::NaiveDate; | ||||||
| use serde::Serialize; | 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 struct PlannedEvent { | ||||||
|     pub id: i64, |     pub id: i64, | ||||||
|     name: String, |     name: String, | ||||||
|     planned_amount_cox: i64, |     planned_amount_cox: i64, | ||||||
|     allow_guests: bool, |  | ||||||
|     trip_details_id: i64, |     trip_details_id: i64, | ||||||
|     planned_starting_time: String, |     planned_starting_time: String, | ||||||
|     max_people: i64, |     max_people: i64, | ||||||
|     day: String, |     day: String, | ||||||
|     notes: Option<String>, |     notes: Option<String>, | ||||||
|  |     pub allow_guests: bool, | ||||||
|  |     trip_type_id: Option<i64>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Serialize)] | #[derive(Serialize)] | ||||||
| pub struct PlannedEventWithUser { | pub struct PlannedEventWithUserAndTriptype { | ||||||
|     #[serde(flatten)] |     #[serde(flatten)] | ||||||
|     planned_event: PlannedEvent, |     pub planned_event: PlannedEvent, | ||||||
|  |     trip_type: Option<TripType>, | ||||||
|     cox_needed: bool, |     cox_needed: bool, | ||||||
|     cox: Vec<Registration>, |     cox: Vec<Registration>, | ||||||
|     rower: Vec<Registration>, |     rower: Vec<Registration>, | ||||||
| @@ -39,7 +41,8 @@ impl PlannedEvent { | |||||||
|         sqlx::query_as!( |         sqlx::query_as!( | ||||||
|             Self, |             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  | FROM planned_event  | ||||||
| INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id | INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id | ||||||
| WHERE planned_event.id like ? | WHERE planned_event.id like ? | ||||||
| @@ -51,11 +54,14 @@ WHERE planned_event.id like ? | |||||||
|         .ok() |         .ok() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec<PlannedEventWithUser> { |     pub async fn get_for_day( | ||||||
|  |         db: &SqlitePool, | ||||||
|  |         day: NaiveDate, | ||||||
|  |     ) -> Vec<PlannedEventWithUserAndTriptype> { | ||||||
|         let day = format!("{day}"); |         let day = format!("{day}"); | ||||||
|         let events = sqlx::query_as!( |         let events = sqlx::query_as!( | ||||||
|             PlannedEvent, |             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 | FROM planned_event | ||||||
| INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id | INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id | ||||||
| WHERE day=?", | WHERE day=?", | ||||||
| @@ -68,17 +74,23 @@ WHERE day=?", | |||||||
|         let mut ret = Vec::new(); |         let mut ret = Vec::new(); | ||||||
|         for event in events { |         for event in events { | ||||||
|             let cox = event.get_all_cox(db).await; |             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(), |                 planned_event: event.clone(), | ||||||
|                 cox_needed: event.planned_amount_cox > cox.len() as i64, |                 cox_needed: event.planned_amount_cox > cox.len() as i64, | ||||||
|                 cox, |                 cox, | ||||||
|                 rower: event.get_all_rower(db).await, |                 rower: event.get_all_rower(db).await, | ||||||
|  |                 trip_type, | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|         ret |         ret | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn get_all_cox(&self, db: &SqlitePool) -> Vec<Registration> { |     async fn get_all_cox(&self, db: &SqlitePool) -> Vec<Registration> { | ||||||
|  |         //TODO: switch to join | ||||||
|         sqlx::query_as!( |         sqlx::query_as!( | ||||||
|             Registration, |             Registration, | ||||||
|             " |             " | ||||||
| @@ -96,6 +108,7 @@ FROM trip WHERE planned_event_id = ? | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> { |     async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> { | ||||||
|  |         //TODO: switch to join | ||||||
|         sqlx::query_as!( |         sqlx::query_as!( | ||||||
|             Registration, |             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 |         .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( |     pub async fn create( | ||||||
|         db: &SqlitePool, |         db: &SqlitePool, | ||||||
|         name: String, |         name: String, | ||||||
|         planned_amount_cox: i32, |         planned_amount_cox: i32, | ||||||
|         allow_guests: bool, |  | ||||||
|         trip_details: TripDetails, |         trip_details: TripDetails, | ||||||
|     ) { |     ) { | ||||||
|         sqlx::query!( |         sqlx::query!( | ||||||
|             "INSERT INTO planned_event(name, planned_amount_cox, allow_guests, trip_details_id) VALUES(?, ?, ?, ?)", |             "INSERT INTO planned_event(name, planned_amount_cox, trip_details_id) VALUES(?, ?, ?)", | ||||||
|             name, planned_amount_cox, allow_guests, trip_details.id |             name, | ||||||
|  |             planned_amount_cox, | ||||||
|  |             trip_details.id | ||||||
|         ) |         ) | ||||||
|         .execute(db) |         .execute(db) | ||||||
|         .await |         .await | ||||||
| @@ -159,7 +190,7 @@ mod test { | |||||||
|  |  | ||||||
|         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); |         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 = |         let res = | ||||||
|             PlannedEvent::get_for_day(&pool, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()).await; |             PlannedEvent::get_for_day(&pool, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()).await; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ use sqlx::SqlitePool; | |||||||
| use super::{ | use super::{ | ||||||
|     planned_event::{PlannedEvent, Registration}, |     planned_event::{PlannedEvent, Registration}, | ||||||
|     tripdetails::TripDetails, |     tripdetails::TripDetails, | ||||||
|  |     triptype::TripType, | ||||||
|     user::CoxUser, |     user::CoxUser, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -18,13 +19,16 @@ pub struct Trip { | |||||||
|     max_people: i64, |     max_people: i64, | ||||||
|     day: String, |     day: String, | ||||||
|     notes: Option<String>, |     notes: Option<String>, | ||||||
|  |     pub allow_guests: bool, | ||||||
|  |     trip_type_id: Option<i64>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Serialize)] | #[derive(Serialize)] | ||||||
| pub struct TripWithUser { | pub struct TripWithUserAndType { | ||||||
|     #[serde(flatten)] |     #[serde(flatten)] | ||||||
|     trip: Trip, |     pub trip: Trip, | ||||||
|     rower: Vec<Registration>, |     rower: Vec<Registration>, | ||||||
|  |     trip_type: Option<TripType>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Trip { | impl Trip { | ||||||
| @@ -44,7 +48,7 @@ impl Trip { | |||||||
|         sqlx::query_as!( |         sqlx::query_as!( | ||||||
|             Self, |             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  | FROM trip  | ||||||
| INNER JOIN trip_details ON trip.trip_details_id = trip_details.id | INNER JOIN trip_details ON trip.trip_details_id = trip_details.id | ||||||
| INNER JOIN user ON trip.cox_id = user.id | INNER JOIN user ON trip.cox_id = user.id | ||||||
| @@ -63,19 +67,7 @@ WHERE trip.id=? | |||||||
|         cox: &CoxUser, |         cox: &CoxUser, | ||||||
|         planned_event: &PlannedEvent, |         planned_event: &PlannedEvent, | ||||||
|     ) -> Result<(), CoxHelpError> { |     ) -> Result<(), CoxHelpError> { | ||||||
|         let is_rower = sqlx::query!( |         if planned_event.is_rower_registered(db, &cox).await { | ||||||
|             "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 { |  | ||||||
|             return Err(CoxHelpError::AlreadyRegisteredAsRower); |             return Err(CoxHelpError::AlreadyRegisteredAsRower); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -92,12 +84,12 @@ WHERE trip.id=? | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec<TripWithUser> { |     pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec<TripWithUserAndType> { | ||||||
|         let day = format!("{day}"); |         let day = format!("{day}"); | ||||||
|         let trips = sqlx::query_as!( |         let trips = sqlx::query_as!( | ||||||
|             Trip, |             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  | FROM trip  | ||||||
| INNER JOIN trip_details ON trip.trip_details_id = trip_details.id | INNER JOIN trip_details ON trip.trip_details_id = trip_details.id | ||||||
| INNER JOIN user ON trip.cox_id = user.id | INNER JOIN user ON trip.cox_id = user.id | ||||||
| @@ -108,10 +100,16 @@ WHERE day=? | |||||||
|         .fetch_all(db) |         .fetch_all(db) | ||||||
|         .await |         .await | ||||||
|         .unwrap(); //TODO: fixme |         .unwrap(); //TODO: fixme | ||||||
|  |  | ||||||
|         let mut ret = Vec::new(); |         let mut ret = Vec::new(); | ||||||
|         for trip in trips { |         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: trip.clone(), | ||||||
|  |                 trip_type, | ||||||
|                 rower: trip.get_all_rower(db).await, |                 rower: trip.get_all_rower(db).await, | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ pub struct TripDetails { | |||||||
|     max_people: i64, |     max_people: i64, | ||||||
|     day: String, |     day: String, | ||||||
|     notes: Option<String>, |     notes: Option<String>, | ||||||
|  |     pub allow_guests: bool, | ||||||
|  |     trip_type_id: Option<i64>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl TripDetails { | impl TripDetails { | ||||||
| @@ -15,7 +17,7 @@ impl TripDetails { | |||||||
|         sqlx::query_as!( |         sqlx::query_as!( | ||||||
|             TripDetails, |             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  | FROM trip_details  | ||||||
| WHERE id like ? | WHERE id like ? | ||||||
|         ", |         ", | ||||||
| @@ -33,13 +35,17 @@ WHERE id like ? | |||||||
|         max_people: i32, |         max_people: i32, | ||||||
|         day: String, |         day: String, | ||||||
|         notes: Option<String>, |         notes: Option<String>, | ||||||
|  |         allow_guests: bool, | ||||||
|  |         trip_type_id: Option<i64>, | ||||||
|     ) -> i64 { |     ) -> i64 { | ||||||
|         let query = sqlx::query!( |         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, |             planned_starting_time, | ||||||
|             max_people, |             max_people, | ||||||
|             day, |             day, | ||||||
|             notes |             notes, | ||||||
|  |             allow_guests, | ||||||
|  |             trip_type_id | ||||||
|         ) |         ) | ||||||
|         .execute(db) |         .execute(db) | ||||||
|         .await |         .await | ||||||
| @@ -57,14 +63,7 @@ WHERE id like ? | |||||||
|         .unwrap(); //TODO: fixme |         .unwrap(); //TODO: fixme | ||||||
|         let amount_currently_registered = i64::from(amount_currently_registered.count); |         let amount_currently_registered = i64::from(amount_currently_registered.count); | ||||||
|  |  | ||||||
|         let amount_allowed_to_register = |         amount_currently_registered >= self.max_people | ||||||
|             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 |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -94,11 +93,29 @@ mod test { | |||||||
|         let pool = testdb!(); |         let pool = testdb!(); | ||||||
|  |  | ||||||
|         assert_eq!( |         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, |             3, | ||||||
|         ); |         ); | ||||||
|         assert_eq!( |         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, |             4, | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| @@ -115,4 +132,6 @@ mod test { | |||||||
|     fn test_true_full() { |     fn test_true_full() { | ||||||
|         //TODO: register user for trip_details = 1; check if is_full returns true |         //TODO: register user for trip_details = 1; check if is_full returns true | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     //TODO: add new tripdetails test with trip_type != None | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										55
									
								
								src/model/triptype.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/model/triptype.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<Self> { | ||||||
|  |         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<Self> { | ||||||
|  |         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 | ||||||
|  | } | ||||||
| @@ -16,8 +16,8 @@ pub struct User { | |||||||
|     pub name: String, |     pub name: String, | ||||||
|     pw: Option<String>, |     pw: Option<String>, | ||||||
|     pub is_cox: bool, |     pub is_cox: bool, | ||||||
|     is_admin: bool, |     pub is_admin: bool, | ||||||
|     is_guest: bool, |     pub is_guest: bool, | ||||||
|     #[serde(default = "bool::default")] |     #[serde(default = "bool::default")] | ||||||
|     deleted: bool, |     deleted: bool, | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,6 +14,10 @@ impl UserTrip { | |||||||
|             return Err(UserTripError::EventAlreadyFull); |             return Err(UserTripError::EventAlreadyFull); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if user.is_guest && !trip_details.allow_guests { | ||||||
|  |             return Err(UserTripError::GuestNotAllowedForThisEvent); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         //check if cox if own event |         //check if cox if own event | ||||||
|         let is_cox = sqlx::query!( |         let is_cox = sqlx::query!( | ||||||
|             "SELECT count(*) as amount |             "SELECT count(*) as amount | ||||||
| @@ -30,6 +34,7 @@ impl UserTrip { | |||||||
|             return Err(UserTripError::CantRegisterAtOwnEvent); |             return Err(UserTripError::CantRegisterAtOwnEvent); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         //TODO: can probably move to trip.rs? | ||||||
|         //check if cox if planned_event |         //check if cox if planned_event | ||||||
|         let is_cox = sqlx::query!( |         let is_cox = sqlx::query!( | ||||||
|             "SELECT count(*) as amount |             "SELECT count(*) as amount | ||||||
| @@ -80,6 +85,7 @@ pub enum UserTripError { | |||||||
|     AlreadyRegisteredAsCox, |     AlreadyRegisteredAsCox, | ||||||
|     EventAlreadyFull, |     EventAlreadyFull, | ||||||
|     CantRegisterAtOwnEvent, |     CantRegisterAtOwnEvent, | ||||||
|  |     GuestNotAllowedForThisEvent, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| @@ -180,4 +186,19 @@ mod test { | |||||||
|  |  | ||||||
|         assert_eq!(result, UserTripError::AlreadyRegisteredAsCox); |         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); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ struct AddPlannedEventForm { | |||||||
|     planned_starting_time: String, |     planned_starting_time: String, | ||||||
|     max_people: i32, |     max_people: i32, | ||||||
|     notes: Option<String>, |     notes: Option<String>, | ||||||
|  |     trip_type: Option<i64>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[post("/planned-event", data = "<data>")] | #[post("/planned-event", data = "<data>")] | ||||||
| @@ -33,6 +34,8 @@ async fn create( | |||||||
|         data.max_people, |         data.max_people, | ||||||
|         data.day.clone(), |         data.day.clone(), | ||||||
|         data.notes.clone(), |         data.notes.clone(), | ||||||
|  |         data.allow_guests, | ||||||
|  |         data.trip_type, | ||||||
|     ) |     ) | ||||||
|     .await; |     .await; | ||||||
|  |  | ||||||
| @@ -41,14 +44,7 @@ async fn create( | |||||||
|                                                                                     //the object |                                                                                     //the object | ||||||
|  |  | ||||||
|     //TODO: fix clone() |     //TODO: fix clone() | ||||||
|     PlannedEvent::create( |     PlannedEvent::create(db, data.name.clone(), data.planned_amount_cox, trip_details).await; | ||||||
|         db, |  | ||||||
|         data.name.clone(), |  | ||||||
|         data.planned_amount_cox, |  | ||||||
|         data.allow_guests, |  | ||||||
|         trip_details, |  | ||||||
|     ) |  | ||||||
|     .await; |  | ||||||
|  |  | ||||||
|     Flash::success(Redirect::to("/"), "Successfully planned the event") |     Flash::success(Redirect::to("/"), "Successfully planned the event") | ||||||
| } | } | ||||||
|   | |||||||
| @@ -41,7 +41,6 @@ async fn login( | |||||||
| ) -> Flash<Redirect> { | ) -> Flash<Redirect> { | ||||||
|     let user = User::login(db, login.name.clone(), login.password.clone()).await; |     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 { |     let user = match user { | ||||||
|         Ok(user) => user, |         Ok(user) => user, | ||||||
|         Err(LoginError::NoPasswordSet(user)) => { |         Err(LoginError::NoPasswordSet(user)) => { | ||||||
|   | |||||||
| @@ -14,13 +14,16 @@ use crate::model::{ | |||||||
|     user::CoxUser, |     user::CoxUser, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| //TODO: add constraints (e.g. planned_amount_cox > 0) |  | ||||||
| #[derive(FromForm)] | #[derive(FromForm)] | ||||||
| struct AddTripForm { | struct AddTripForm { | ||||||
|     day: String, |     day: String, | ||||||
|  |     //TODO: properly parse `planned_starting_time` | ||||||
|     planned_starting_time: String, |     planned_starting_time: String, | ||||||
|  |     #[field(validate = range(1..))] | ||||||
|     max_people: i32, |     max_people: i32, | ||||||
|     notes: Option<String>, |     notes: Option<String>, | ||||||
|  |     trip_type: Option<i64>, | ||||||
|  |     allow_guests: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[post("/trip", data = "<data>")] | #[post("/trip", data = "<data>")] | ||||||
| @@ -32,6 +35,8 @@ async fn create(db: &State<SqlitePool>, data: Form<AddTripForm>, cox: CoxUser) - | |||||||
|         data.max_people, |         data.max_people, | ||||||
|         data.day.clone(), |         data.day.clone(), | ||||||
|         data.notes.clone(), |         data.notes.clone(), | ||||||
|  |         data.allow_guests, | ||||||
|  |         data.trip_type, | ||||||
|     ) |     ) | ||||||
|     .await; |     .await; | ||||||
|     let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc just |     let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc just | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ use sqlx::SqlitePool; | |||||||
| use crate::model::{ | use crate::model::{ | ||||||
|     log::Log, |     log::Log, | ||||||
|     tripdetails::TripDetails, |     tripdetails::TripDetails, | ||||||
|  |     triptype::TripType, | ||||||
|     user::User, |     user::User, | ||||||
|     usertrip::{UserTrip, UserTripError}, |     usertrip::{UserTrip, UserTripError}, | ||||||
|     Day, |     Day, | ||||||
| @@ -22,25 +23,39 @@ mod admin; | |||||||
| mod auth; | mod auth; | ||||||
| mod cox; | 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("/")] | #[get("/")] | ||||||
| async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_>>) -> Template { | async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_>>) -> Template { | ||||||
|     let mut days = Vec::new(); |     let mut days = Vec::new(); | ||||||
|  |  | ||||||
|     let mut show_next_n_days = 6; |     let mut context = Context::new(); | ||||||
|     if user.is_cox { |  | ||||||
|         let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); |     if user.is_cox || user.is_admin { | ||||||
|         show_next_n_days = end_of_year |         let triptypes = TripType::all(db).await; | ||||||
|             .signed_duration_since(Local::now().date_naive()) |         context.insert("trip_types", &triptypes); | ||||||
|             .num_days() |  | ||||||
|             + 1; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     let show_next_n_days = amount_days_to_show(user.is_cox); | ||||||
|     for i in 0..show_next_n_days { |     for i in 0..show_next_n_days { | ||||||
|         let date = (Local::now() + Duration::days(i)).date_naive(); |         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 { |     if let Some(msg) = flash { | ||||||
|         context.insert("flash", &msg.into_inner()); |         context.insert("flash", &msg.into_inner()); | ||||||
| @@ -81,6 +96,10 @@ async fn join(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash | |||||||
|             Redirect::to("/"), |             Redirect::to("/"), | ||||||
|             "Du kannst bei einer selbst ausgeschriebenen Fahrt nicht mitrudern ;)", |             "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.", | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -50,10 +50,13 @@ | |||||||
|        {% if user.pw %} |        {% if user.pw %} | ||||||
|         <a class="inline-block mt-1 text-primary-600 hover:text-primary-900 underline" href="/admin/user/{{ user.id }}/reset-pw">Passwort zurücksetzen</a> |         <a class="inline-block mt-1 text-primary-600 hover:text-primary-900 underline" href="/admin/user/{{ user.id }}/reset-pw">Passwort zurücksetzen</a> | ||||||
|       {% endif %} |       {% endif %} | ||||||
|         <a class="inline-block mt-1 text-primary-600 hover:text-primary-900 underline" href="/admin/user/{{ user.id }}/delete" onclick="return confirm('Really delete user?');">User löschen</a> |  | ||||||
|     </div> |     </div> | ||||||
| 		<div> | 		<div class="grid gap-3"> | ||||||
|       <input value="Ändern" type="submit" class="w-28 rounded-md bg-primary-600 px-3 py-2 text-sm font-semibold text-white hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer"/> |       <a href="/admin/user/{{ user.id }}/delete" class="inline-block btn btn-alert" onclick="return confirm('Wirklich löschen?');"> | ||||||
|  |         {% include "includes/delete-icon" %} | ||||||
|  |         Löschen | ||||||
|  |       </a> | ||||||
|  |       <input value="Ändern" type="submit" class="w-28 btn btn-primary"/> | ||||||
|     </div> |     </div> | ||||||
| 	</form> | 	</form> | ||||||
| {% endfor %} | {% endfor %} | ||||||
|   | |||||||
| @@ -14,10 +14,10 @@ | |||||||
|       <input type="hidden" name="remember" value="true"> |       <input type="hidden" name="remember" value="true"> | ||||||
|       <div class="-space-y-px rounded-md shadow-sm"> |       <div class="-space-y-px rounded-md shadow-sm"> | ||||||
|         <div> |         <div> | ||||||
|           {{ 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) }} | ||||||
|         </div> |         </div> | ||||||
|         <div> |         <div> | ||||||
|           {{ 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) }} | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,10 +9,10 @@ | |||||||
|     <input type="hidden" name="userid" value="{{ userid }}" /> |     <input type="hidden" name="userid" value="{{ userid }}" /> | ||||||
|     <div class="-space-y-px rounded-md shadow-sm"> |     <div class="-space-y-px rounded-md shadow-sm"> | ||||||
|       <div> |       <div> | ||||||
|         {{ 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) }} | ||||||
|       </div> |       </div> | ||||||
|       <div> |       <div> | ||||||
|         {{ 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) }} | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|    |    | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ | |||||||
|     {{ macros::input(label='Startzeit', name='planned_starting_time', type='time', required=true) }} |     {{ 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 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::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='Anmerkungen', name='notes', type='input') }} | ||||||
|     <select name="trip_type"> |     <select name="trip_type"> | ||||||
| 	      <option selected value>Reguläre Ausfahrt</option> | 	      <option selected value>Reguläre Ausfahrt</option> | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
|         <input class="day-js" type="hidden" name="day" value="" /> |         <input class="day-js" type="hidden" name="day" value="" /> | ||||||
|         {{ macros::input(label='Startzeit (zB "10:00")', name='planned_starting_time', type='time', required=true) }} |         {{ 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::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') }} |         {{ macros::input(label='Anmerkungen', name='notes', type='input') }} | ||||||
| 	    <select name="trip_type"> | 	    <select name="trip_type"> | ||||||
| 		      <option selected value>Reguläre Ausfahrt</option> | 		      <option selected value>Reguläre Ausfahrt</option> | ||||||
|   | |||||||
| @@ -24,9 +24,11 @@ | |||||||
|     <div class="h-8"></div> |     <div class="h-8"></div> | ||||||
| {% endmacro header %} | {% endmacro header %} | ||||||
|  |  | ||||||
| {% macro input(label, name, type, required=false, class='rounded-md', value='', min='') %} | {% macro input(label, name, type, required=false, class='rounded-md', value='', min='', hide_label=false) %} | ||||||
|   <label for="{{ name }}" class="sr-only">{{ label }}</label> | <div> | ||||||
|   <input id="{{ name }}" name="{{ name }}" type="{{ type }}" {% if required %} required {% endif %} value="{{ value }}" class="input {{ class }}" placeholder="{{ label }}" {% if min %} min="{{ min }}" {% endif %}> |   <label for="{{ name }}" class="{% if hide_label %} sr-only {% else %} small text-gray-600 {% endif %}">{{ label }}</label> | ||||||
|  |   <input id="{{ name }}" name="{{ name }}" type="{{ type }}" {% if required %} required {% endif %} value="{{ value }}" class="input {{ class }}" placeholder="{% if hide_label %}{{ label }}{% endif %}" {% if min %} min="{{ min }}" {% endif %}> | ||||||
|  | </div> | ||||||
| {% endmacro input %} | {% endmacro input %} | ||||||
|  |  | ||||||
| {% macro checkbox(label, name, id='', checked=false) %} | {% macro checkbox(label, name, id='', checked=false) %} | ||||||
| @@ -41,7 +43,7 @@ | |||||||
| </div> | </div> | ||||||
| {% endmacro alert %} | {% 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') %} | ||||||
| <div class="text-{{ color }} bg-{{ bg }} text-center p-1 mt-1 rounded-t-md">{{ header }} {{ empty_seats }}</div> | <div class="text-{{ color }} bg-{{ bg }} text-center p-1 mt-1 rounded-t-md">{{ header }} {{ empty_seats }}</div> | ||||||
| <div class="p-2 border border-t-0 border-{{ bg }} mb-4 rounded-b-md"> | <div class="p-2 border border-t-0 border-{{ bg }} mb-4 rounded-b-md"> | ||||||
|   {% if participants | length > 0 %} |   {% if participants | length > 0 %} | ||||||
|   | |||||||
| @@ -142,13 +142,19 @@ | |||||||
|         <div class="pt-2 reset-js" data-coxneeded="false"> |         <div class="pt-2 reset-js" data-coxneeded="false"> | ||||||
|           <div class="flex justify-between items-center"> |           <div class="flex justify-between items-center"> | ||||||
|             <div> |             <div> | ||||||
|               <strong class="text-primary-900">{{ trip.planned_starting_time }} Uhr</strong> <small |               {% if trip.max_people == 0 %} | ||||||
|                 class="text-gray-600">({{ trip.cox_name }})</small><br /> |                 <strong class="text-[#f43f5e]">⚠ {{ trip.planned_starting_time }} Uhr</strong> | ||||||
| 		{% if trip.trip_type %} |                 <small class="text-[#f43f5e]">(Absage {{ trip.cox_name }})</small> | ||||||
| 			Spezielles Event: {{ trip.trip_type.name }} |               {% else %} | ||||||
| 		{% endif %} |                 <strong class="text-primary-900">{{ trip.planned_starting_time }} Uhr</strong>  | ||||||
|  |                 <small class="text-gray-600">({{ trip.cox_name }})</small> | ||||||
|  |               {% endif %} | ||||||
|  |               <br /> | ||||||
|  |               {% if trip.trip_type %} | ||||||
|  |                 Spezielles Event: {{ trip.trip_type.name }} | ||||||
|  |               {% endif %} | ||||||
|               <a href="#" data-sidebar="true" data-trigger="sidebar"  |               <a href="#" data-sidebar="true" data-trigger="sidebar"  | ||||||
|                 data-header="<strong>{{ trip.planned_starting_time }} Uhr</strong> ({{ trip.cox_name }}){% if trip.notes %}<small class='block'>{{ trip.notes }}</small>{% endif %}" |                 data-header="<strong>{% if trip.max_people == 0 %}⚠ {% endif %}{{ trip.planned_starting_time }} Uhr</strong> ({{ trip.cox_name }}){% if trip.notes %}<small class='block'>{{ trip.notes }}</small>{% endif %}" | ||||||
|                 data-body="#trip{{ trip.trip_details_id }}" |                 data-body="#trip{{ trip.trip_details_id }}" | ||||||
|                 class="inline-block link-primary mr-3"> |                 class="inline-block link-primary mr-3"> | ||||||
|                 Details |                 Details | ||||||
| @@ -177,9 +183,15 @@ | |||||||
|         {# --- START Sidebar Content --- #} |         {# --- START Sidebar Content --- #} | ||||||
|         <div class="hidden"> |         <div class="hidden"> | ||||||
|             <div id="trip{{ trip.trip_details_id }}"> |             <div id="trip{{ trip.trip_details_id }}"> | ||||||
|               {% set amount_cur_rower = trip.rower | length %} |               {% if trip.max_people == 0 %} | ||||||
|               {{ macros::box(participants=trip.rower, empty_seats=trip.max_people - amount_cur_rower, bg='primary-100', color='black') }} |                 {# --- border-[#f43f5e] bg-[#f43f5e] --- #} | ||||||
|             {# --- START Edit Form --- #} |                 {{ macros::box(participants=trip.rower,bg='[#f43f5e]',header='Absage') }} | ||||||
|  |               {% else %} | ||||||
|  |                 {% set amount_cur_rower = trip.rower | length %} | ||||||
|  |                 {{ macros::box(participants=trip.rower, empty_seats=trip.max_people - amount_cur_rower, bg='primary-100', color='black') }} | ||||||
|  |               {% endif %} | ||||||
|  |  | ||||||
|  |                  {# --- START Edit Form --- #} | ||||||
|             {% if trip.cox_id == loggedin_user.id %} |             {% if trip.cox_id == loggedin_user.id %} | ||||||
|             <div class="bg-gray-100 p-3 mt-4 rounded-md"> |             <div class="bg-gray-100 p-3 mt-4 rounded-md"> | ||||||
|               <h3 class="text-primary-950 font-bold uppercase tracking-wide mb-2">Ausfahrt bearbeiten</h3> |               <h3 class="text-primary-950 font-bold uppercase tracking-wide mb-2">Ausfahrt bearbeiten</h3> | ||||||
| @@ -246,7 +258,13 @@ | |||||||
| </div> | </div> | ||||||
|  |  | ||||||
| {% include "dynamics/sidebar" %} | {% include "dynamics/sidebar" %} | ||||||
| {% include "forms/trip" %} |  | ||||||
| {% include "forms/event" %} | {% if loggedin_user.is_cox %} | ||||||
|  |   {% include "forms/trip" %} | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
|  | {% if loggedin_user.is_admin %} | ||||||
|  |   {% include "forms/event" %} | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
| {% endblock content %} | {% endblock content %} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Marie Birner
					Marie Birner