groups #104
| @@ -2,10 +2,6 @@ CREATE TABLE IF NOT EXISTS "user" ( | ||||
| 	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||
| 	"name" text NOT NULL UNIQUE, | ||||
| 	"pw" text,  | ||||
| 	"is_cox" boolean NOT NULL DEFAULT FALSE, | ||||
| 	"is_admin" boolean NOT NULL DEFAULT FALSE, | ||||
| 	"is_guest" boolean NOT NULL DEFAULT TRUE, | ||||
| 	"is_tech" boolean NOT NULL DEFAULT FALSE, | ||||
| 	"deleted" boolean NOT NULL DEFAULT FALSE, | ||||
| 	"last_access" DATETIME, | ||||
| 	"dob" text, | ||||
| @@ -15,6 +11,17 @@ CREATE TABLE IF NOT EXISTS "user" ( | ||||
| 	"dirty_dozen" text | ||||
| ); | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS "role" ( | ||||
| 	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||
| 	"name" text NOT NULL UNIQUE | ||||
| ); | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS "user_role" ( | ||||
| 	"user_id" INTEGER NOT NULL REFERENCES user(id), | ||||
| 	"role_id" INTEGER NOT NULL REFERENCES role(id), | ||||
| 	CONSTRAINT unq UNIQUE (user_id, role_id)  | ||||
| ); | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS "trip_type" ( | ||||
| 	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||
| 	"name" text NOT NULL UNIQUE, | ||||
|   | ||||
							
								
								
									
										21
									
								
								seeds.sql
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								seeds.sql
									
									
									
									
									
								
							| @@ -1,10 +1,19 @@ | ||||
| INSERT INTO "user" (name, is_cox, is_admin, is_guest, pw) VALUES('admin', true, true, false, '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$4P4NCw4Ukhv80/eQYTsarHhnw61JuL1KMx/L9dm82YM'); | ||||
| INSERT INTO "user" (name, is_cox, is_admin, is_guest, pw) VALUES('rower', false, false, false, '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY'); | ||||
| INSERT INTO "user" (name, is_cox, is_admin, is_guest, pw) VALUES('guest', false, false, true, '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$GF6gizbI79Bh0zA9its8S0gram956v+YIV8w8VpwJnQ'); | ||||
| INSERT INTO "user" (name, is_cox, is_admin, is_guest, pw) VALUES('cox', true, false, false, '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$lnWzHx3DdqS9GQyWYel82kIotZuK2wk9EyfhPFtjNzs'); | ||||
| INSERT INTO "role" (name) VALUES ('admin'); | ||||
| INSERT INTO "role" (name) VALUES ('cox'); | ||||
| INSERT INTO "role" (name) VALUES ('scheckbuch'); | ||||
| INSERT INTO "role" (name) VALUES ('tech'); | ||||
| INSERT INTO "user" (name, pw) VALUES('admin', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$4P4NCw4Ukhv80/eQYTsarHhnw61JuL1KMx/L9dm82YM'); | ||||
| INSERT INTO "user_role" (user_id, role_id) VALUES(1,1); | ||||
| INSERT INTO "user_role" (user_id, role_id) VALUES(1,2); | ||||
| INSERT INTO "user" (name, pw) VALUES('rower', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY'); | ||||
| INSERT INTO "user" (name, pw) VALUES('guest', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$GF6gizbI79Bh0zA9its8S0gram956v+YIV8w8VpwJnQ'); | ||||
| INSERT INTO "user_role" (user_id, role_id) VALUES(3,3); | ||||
| INSERT INTO "user" (name, pw) VALUES('cox', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$lnWzHx3DdqS9GQyWYel82kIotZuK2wk9EyfhPFtjNzs'); | ||||
| INSERT INTO "user_role" (user_id, role_id) VALUES(4,2); | ||||
| INSERT INTO "user" (name) VALUES('new'); | ||||
| INSERT INTO "user" (name, is_cox, is_admin, is_guest, pw) VALUES('cox2', true, false, false, '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$lnWzHx3DdqS9GQyWYel82kIotZuK2wk9EyfhPFtjNzs'); | ||||
| INSERT INTO "user" (name, is_cox, is_admin, is_guest, pw) VALUES('rower2', false, false, false, '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY'); | ||||
| INSERT INTO "user" (name, pw) VALUES('cox2', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$lnWzHx3DdqS9GQyWYel82kIotZuK2wk9EyfhPFtjNzs'); | ||||
| INSERT INTO "user_role" (user_id, role_id) VALUES(6,2); | ||||
| INSERT INTO "user" (name,  pw) VALUES('rower2', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY'); | ||||
|  | ||||
| INSERT INTO "trip_details" (planned_starting_time, max_people, day, notes) VALUES('10:00', 2, '1970-01-01', 'trip_details for a planned event'); | ||||
| INSERT INTO "planned_event" (name, planned_amount_cox, trip_details_id) VALUES('test-planned-event', 2, 1); | ||||
|   | ||||
| @@ -89,7 +89,7 @@ impl Boat { | ||||
|             .ok() | ||||
|     } | ||||
|  | ||||
|     pub async fn shipmaster_allowed(&self, user: &User) -> bool { | ||||
|     pub async fn shipmaster_allowed(&self, db: &SqlitePool, user: &User) -> bool { | ||||
|         if let Some(owner_id) = self.owner { | ||||
|             return owner_id == user.id; | ||||
|         } | ||||
| @@ -98,7 +98,23 @@ impl Boat { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         user.is_cox | ||||
|         user.has_role(db, "cox").await | ||||
|     } | ||||
|  | ||||
|     pub async fn shipmaster_allowed_tx( | ||||
|         &self, | ||||
|         db: &mut Transaction<'_, Sqlite>, | ||||
|         user: &User, | ||||
|     ) -> bool { | ||||
|         if let Some(owner_id) = self.owner { | ||||
|             return owner_id == user.id; | ||||
|         } | ||||
|  | ||||
|         if self.amount_seats == 1 { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         user.has_role_tx(db, "cox").await | ||||
|     } | ||||
|  | ||||
|     pub async fn is_locked(&self, db: &SqlitePool) -> bool { | ||||
| @@ -156,10 +172,10 @@ ORDER BY amount_seats DESC | ||||
|     } | ||||
|  | ||||
|     pub async fn for_user(db: &SqlitePool, user: &User) -> Vec<BoatWithDetails> { | ||||
|         if user.is_admin { | ||||
|         if user.has_role(db, "admin").await { | ||||
|             return Self::all(db).await; | ||||
|         } | ||||
|         let boats = if user.is_cox { | ||||
|         let boats = if user.has_role(db, "cox").await { | ||||
|             sqlx::query_as!( | ||||
|             Boat, | ||||
|             " | ||||
|   | ||||
| @@ -141,7 +141,7 @@ ORDER BY created_at DESC | ||||
|         .map_err(|e| e.to_string())?; | ||||
|  | ||||
|         let user = User::find_by_id(db, boat.user_id_fixed).await.unwrap(); | ||||
|         if user.is_tech { | ||||
|         if user.has_role(db, "tech").await { | ||||
|             return self | ||||
|                 .verified( | ||||
|                     db, | ||||
| @@ -162,7 +162,7 @@ ORDER BY created_at DESC | ||||
|         boat: BoatDamageVerified<'_>, | ||||
|     ) -> Result<(), String> { | ||||
|         if let Some(verifier) = User::find_by_id(db, boat.user_id_verified).await { | ||||
|             if !verifier.is_tech { | ||||
|             if !verifier.has_role(db, "tech").await { | ||||
|                 Log::create(db, format!("User {verifier:?} tried to verify boat {boat:?}. The user is no tech. Manually craftted request?")).await; | ||||
|                 return Err("You are not allowed to verify the boat!".into()); | ||||
|             } | ||||
|   | ||||
| @@ -272,7 +272,7 @@ ORDER BY departure DESC | ||||
|         if let Ok(log_to_finalize) = TryInto::<LogToFinalize>::try_into(log.clone()) { | ||||
|             //TODO: fix clone() above | ||||
|  | ||||
|             if !boat.shipmaster_allowed(created_by_user).await { | ||||
|             if !boat.shipmaster_allowed(db, created_by_user).await { | ||||
|                 return Err(LogbookCreateError::UserNotAllowedToUseBoat); | ||||
|             } | ||||
|  | ||||
| @@ -343,7 +343,7 @@ ORDER BY departure DESC | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if !boat.shipmaster_allowed(created_by_user).await { | ||||
|         if !boat.shipmaster_allowed(db, created_by_user).await { | ||||
|             return Err(LogbookCreateError::UserNotAllowedToUseBoat); | ||||
|         } | ||||
|  | ||||
| @@ -452,7 +452,7 @@ ORDER BY departure DESC | ||||
|             return Err(LogbookUpdateError::SteeringPersonNotInRowers); | ||||
|         } | ||||
|  | ||||
|         if !boat.shipmaster_allowed(user).await && self.shipmaster != user.id { | ||||
|         if !boat.shipmaster_allowed_tx(db, user).await && self.shipmaster != user.id { | ||||
|             //second part: shipmaster has entered a different user, then the user should be able to | ||||
|             //`home` it | ||||
|             return Err(LogbookUpdateError::UserNotAllowedToUseBoat); | ||||
| @@ -471,7 +471,7 @@ ORDER BY departure DESC | ||||
|             return Err(LogbookUpdateError::ArrivalNotAfterDeparture); | ||||
|         } | ||||
|         let today = Utc::now().date_naive(); | ||||
|         if arr.date() != today && !user.is_admin { | ||||
|         if arr.date() != today && !user.has_role_tx(db, "admin").await { | ||||
|             return Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday); | ||||
|         } | ||||
|  | ||||
| @@ -506,7 +506,7 @@ ORDER BY departure DESC | ||||
|     pub async fn delete(&self, db: &SqlitePool, user: &User) -> Result<(), LogbookDeleteError> { | ||||
|         Log::create(db, format!("{user:?} deleted trip: {self:?}")).await; | ||||
|  | ||||
|         if user.is_admin || user.id == self.shipmaster { | ||||
|         if user.has_role(db, "admin").await || user.id == self.shipmaster { | ||||
|             sqlx::query!("DELETE FROM logbook WHERE id=?", self.id) | ||||
|                 .execute(db) | ||||
|                 .await | ||||
|   | ||||
| @@ -14,6 +14,7 @@ pub mod log; | ||||
| pub mod logbook; | ||||
| pub mod logtype; | ||||
| pub mod planned_event; | ||||
| pub mod role; | ||||
| pub mod rower; | ||||
| pub mod stat; | ||||
| pub mod trip; | ||||
|   | ||||
| @@ -6,7 +6,7 @@ use ics::{ | ||||
|     Event, ICalendar, | ||||
| }; | ||||
| use serde::Serialize; | ||||
| use sqlx::{FromRow, SqlitePool}; | ||||
| use sqlx::{FromRow, SqlitePool, Row}; | ||||
|  | ||||
| use super::{tripdetails::TripDetails, triptype::TripType, user::User}; | ||||
|  | ||||
| @@ -47,27 +47,28 @@ pub struct Registration { | ||||
|  | ||||
| impl Registration { | ||||
|     pub async fn all_rower(db: &SqlitePool, trip_details_id: i64) -> Vec<Registration> { | ||||
|         sqlx::query!( | ||||
|         sqlx::query( | ||||
|             &format!( | ||||
|             r#" | ||||
| SELECT | ||||
|     (SELECT name FROM user WHERE user_trip.user_id = user.id) as "name?",  | ||||
|     user_note, | ||||
|     user_id, | ||||
|     (SELECT created_at FROM user WHERE user_trip.user_id = user.id) as registered_at, | ||||
|     (SELECT is_guest FROM user WHERE user_trip.user_id = user.id) as is_guest | ||||
| FROM user_trip WHERE trip_details_id = ?  | ||||
|         "#, | ||||
|             trip_details_id, | ||||
|     (SELECT EXISTS (SELECT 1 FROM user_role WHERE user_role.user_id = user_trip.user_id AND user_role.role_id = (SELECT id FROM role WHERE name = 'scheckbuch'))) as is_guest | ||||
| FROM user_trip WHERE trip_details_id = {}  | ||||
|         "#,trip_details_id), | ||||
|         ) | ||||
|         .fetch_all(db) | ||||
|         .await | ||||
|         .unwrap() | ||||
|         .into_iter() | ||||
|         .map(|r| Registration { | ||||
|             name: r.name.or(r.user_note).unwrap(), //Ok, either name or user_note needs to be set | ||||
|             registered_at: r.registered_at, | ||||
|             is_guest: r.is_guest, | ||||
|             is_real_guest: r.user_id.is_none(), | ||||
|         .map(|r|  | ||||
|             Registration { | ||||
|             name: r.get::<Option<String>, usize>(0).or(r.get::<Option<String>, usize>(1)).unwrap(), //Ok, either name or user_note needs to be set | ||||
|             registered_at: r.get::<String,usize>(3), | ||||
|             is_guest: r.get::<bool, usize>(4), | ||||
|             is_real_guest: r.get::<Option<i64>, usize>(2).is_none(), | ||||
|         }) | ||||
|         .collect() | ||||
|     } | ||||
| @@ -78,8 +79,7 @@ FROM user_trip WHERE trip_details_id = ? | ||||
|             " | ||||
| SELECT | ||||
|     (SELECT name FROM user WHERE cox_id = id) as name, | ||||
|     (SELECT created_at FROM user WHERE cox_id = id) as registered_at, | ||||
|     (SELECT is_guest FROM user WHERE cox_id = id) as is_guest | ||||
|     (SELECT created_at FROM user WHERE cox_id = id) as registered_at | ||||
| FROM trip WHERE planned_event_id = ? | ||||
|         ", | ||||
|             trip_details_id | ||||
| @@ -91,7 +91,7 @@ FROM trip WHERE planned_event_id = ? | ||||
|         .map(|r| Registration { | ||||
|             name: r.name, | ||||
|             registered_at: r.registered_at, | ||||
|             is_guest: r.is_guest, | ||||
|             is_guest: false, | ||||
|             is_real_guest: false, | ||||
|         }) | ||||
|         .collect() //Okay, as PlannedEvent can only be created with proper DB backing | ||||
|   | ||||
							
								
								
									
										17
									
								
								src/model/role.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/model/role.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| use serde::Serialize; | ||||
| use sqlx::{FromRow, SqlitePool}; | ||||
|  | ||||
| #[derive(FromRow, Serialize, Clone)] | ||||
| pub struct Role { | ||||
|     id: i64, | ||||
|     name: String, | ||||
| } | ||||
|  | ||||
| impl Role { | ||||
|     pub async fn all(db: &SqlitePool) -> Vec<Role> { | ||||
|         sqlx::query_as!(Role, "SELECT id, name FROM role") | ||||
|             .fetch_all(db) | ||||
|             .await | ||||
|             .unwrap() | ||||
|     } | ||||
| } | ||||
| @@ -16,7 +16,7 @@ impl Rower { | ||||
|         sqlx::query_as!( | ||||
|             User, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex | ||||
| SELECT id, name, pw, deleted, last_access, dob, weight, sex | ||||
| FROM user | ||||
| WHERE id in (SELECT rower_id FROM rower WHERE logbook_id=?) | ||||
|         ", | ||||
|   | ||||
| @@ -276,11 +276,12 @@ mod test { | ||||
|     fn test_new_own() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
| @@ -301,11 +302,12 @@ mod test { | ||||
|     fn test_new_succ_join() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox2".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox2".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
| @@ -316,11 +318,12 @@ mod test { | ||||
|     fn test_new_failed_join_already_cox() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox2".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox2".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
| @@ -332,11 +335,12 @@ mod test { | ||||
|     fn test_succ_update_own() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let trip = Trip::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
| @@ -354,11 +358,12 @@ mod test { | ||||
|     fn test_succ_update_own_with_triptype() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let trip = Trip::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
| @@ -377,11 +382,12 @@ mod test { | ||||
|     fn test_fail_update_own_not_your_trip() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox2".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox2".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let trip = Trip::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
| @@ -397,11 +403,12 @@ mod test { | ||||
|     fn test_succ_delete_by_planned_event() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
| @@ -419,11 +426,12 @@ mod test { | ||||
|     fn test_succ_delete() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let trip = Trip::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
| @@ -436,11 +444,12 @@ mod test { | ||||
|     fn test_fail_delete_diff_cox() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox2".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox2".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let trip = Trip::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
| @@ -457,11 +466,12 @@ mod test { | ||||
|     fn test_fail_delete_someone_registered() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let trip = Trip::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
|   | ||||
| @@ -120,7 +120,7 @@ ORDER BY day;", | ||||
|  | ||||
|     pub(crate) async fn user_allowed_to_change(&self, db: &SqlitePool, user: &User) -> bool { | ||||
|         if self.belongs_to_event(db).await { | ||||
|             user.is_admin | ||||
|             user.has_role(db, "admin").await | ||||
|         } else { | ||||
|             self.user_is_cox(db, user).await != CoxAtTrip::No | ||||
|         } | ||||
|   | ||||
| @@ -21,10 +21,6 @@ pub struct User { | ||||
|     pub id: i64, | ||||
|     pub name: String, | ||||
|     pub pw: Option<String>, | ||||
|     pub is_cox: bool, | ||||
|     pub is_admin: bool, | ||||
|     pub is_guest: bool, | ||||
|     pub is_tech: bool, | ||||
|     pub dob: Option<String>, | ||||
|     pub weight: Option<String>, | ||||
|     pub sex: Option<String>, | ||||
| @@ -32,17 +28,35 @@ pub struct User { | ||||
|     pub last_access: Option<chrono::NaiveDateTime>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub struct UserWithRoles { | ||||
|     #[serde(flatten)] | ||||
|     pub user: User, | ||||
|     pub roles: Vec<String>, | ||||
| } | ||||
|  | ||||
| impl UserWithRoles { | ||||
|     pub async fn from_user(user: User, db: &SqlitePool) -> Self { | ||||
|         Self { | ||||
|             roles: user.roles(db).await, | ||||
|             user, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub struct UserWithWaterStatus { | ||||
|     #[serde(flatten)] | ||||
|     pub user: User, | ||||
|     pub on_water: bool, | ||||
|     pub roles: Vec<String>, | ||||
| } | ||||
|  | ||||
| impl UserWithWaterStatus { | ||||
|     pub async fn from_user(user: User, db: &SqlitePool) -> Self { | ||||
|         Self { | ||||
|             on_water: user.on_water(db).await, | ||||
|             roles: user.roles(db).await, | ||||
|             user, | ||||
|         } | ||||
|     } | ||||
| @@ -91,11 +105,56 @@ impl User { | ||||
|         .rowed_km | ||||
|     } | ||||
|  | ||||
|     pub async fn has_role(&self, db: &SqlitePool, role: &str) -> bool { | ||||
|         if sqlx::query!( | ||||
|             "SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)", | ||||
|             self.id, | ||||
|             role | ||||
|         ) | ||||
|         .fetch_optional(db) | ||||
|         .await | ||||
|         .unwrap() | ||||
|         .is_some() | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         false | ||||
|     } | ||||
|  | ||||
|     pub async fn roles(&self, db: &SqlitePool) -> Vec<String> { | ||||
|         sqlx::query!( | ||||
|             "SELECT r.name FROM role r JOIN user_role ur ON r.id = ur.role_id WHERE ur.user_id = ?;", | ||||
|             self.id | ||||
|         ) | ||||
|             .fetch_all(db) | ||||
|         .await | ||||
|         .unwrap() | ||||
|         .into_iter().map(|r| r.name).collect() | ||||
|     } | ||||
|  | ||||
|     pub async fn has_role_tx(&self, db: &mut Transaction<'_, Sqlite>, role: &str) -> bool { | ||||
|         if sqlx::query!( | ||||
|             "SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)", | ||||
|             self.id, | ||||
|             role | ||||
|         ) | ||||
|         .fetch_optional(db.deref_mut()) | ||||
|         .await | ||||
|         .unwrap() | ||||
|         .is_some() | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         false | ||||
|     } | ||||
|  | ||||
|     pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> { | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex | ||||
| SELECT id, name, pw, deleted, last_access, dob, weight, sex | ||||
| FROM user  | ||||
| WHERE id like ? | ||||
|         ", | ||||
| @@ -110,7 +169,7 @@ WHERE id like ? | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex | ||||
| SELECT id, name, pw, deleted, last_access, dob, weight, sex | ||||
| FROM user  | ||||
| WHERE id like ? | ||||
|         ", | ||||
| @@ -125,7 +184,7 @@ WHERE id like ? | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex | ||||
| SELECT id, name, pw, deleted, last_access, dob, weight, sex | ||||
| FROM user  | ||||
| WHERE name like ? | ||||
|         ", | ||||
| @@ -167,7 +226,7 @@ WHERE name like ? | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex | ||||
| SELECT id, name, pw, deleted, last_access, dob, weight, sex | ||||
| FROM user | ||||
| WHERE deleted = 0 | ||||
| ORDER BY last_access DESC | ||||
| @@ -182,7 +241,7 @@ ORDER BY last_access DESC | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex | ||||
| SELECT id, name, pw, deleted, last_access, dob, weight, sex | ||||
| FROM user | ||||
| WHERE deleted = 0 AND dob != '' and weight != '' and sex != '' | ||||
| ORDER BY name  | ||||
| @@ -197,9 +256,9 @@ ORDER BY name | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech, dob, weight, sex | ||||
| SELECT id, name, pw, deleted, last_access, dob, weight, sex | ||||
| FROM user | ||||
| WHERE deleted = 0 AND is_cox=true | ||||
| WHERE deleted = 0 AND (SELECT COUNT(*) FROM user_role WHERE user_id=user.id AND role_id = (SELECT id FROM role WHERE name = 'cox')) > 0 | ||||
| ORDER BY last_access DESC | ||||
|         " | ||||
|         ) | ||||
| @@ -208,24 +267,16 @@ ORDER BY last_access DESC | ||||
|         .unwrap() | ||||
|     } | ||||
|  | ||||
|     pub async fn create(db: &SqlitePool, name: &str, is_guest: bool) -> bool { | ||||
|         sqlx::query!( | ||||
|             "INSERT INTO USER(name, is_guest) VALUES (?,?)", | ||||
|             name, | ||||
|             is_guest, | ||||
|         ) | ||||
|         .execute(db) | ||||
|         .await | ||||
|         .is_ok() | ||||
|     pub async fn create(db: &SqlitePool, name: &str) -> bool { | ||||
|         sqlx::query!("INSERT INTO USER(name) VALUES (?)", name) | ||||
|             .execute(db) | ||||
|             .await | ||||
|             .is_ok() | ||||
|     } | ||||
|  | ||||
|     pub async fn update(&self, db: &SqlitePool, data: UserEditForm) { | ||||
|         sqlx::query!( | ||||
|             "UPDATE user SET is_cox = ?, is_admin = ?, is_guest = ?, is_tech = ?, dob = ?, weight = ?, sex = ? where id = ?", | ||||
|             data.is_cox, | ||||
|             data.is_admin, | ||||
|             data.is_guest, | ||||
|             data.is_tech, | ||||
|             "UPDATE user SET dob = ?, weight = ?, sex = ? where id = ?", | ||||
|             data.dob, | ||||
|             data.weight, | ||||
|             data.sex, | ||||
| @@ -234,6 +285,23 @@ ORDER BY last_access DESC | ||||
|         .execute(db) | ||||
|         .await | ||||
|         .unwrap(); //Okay, because we can only create a User of a valid id | ||||
|  | ||||
|         // handle roles | ||||
|         sqlx::query!("DELETE FROM user_role WHERE user_id = ?", self.id) | ||||
|             .execute(db) | ||||
|             .await | ||||
|             .unwrap(); | ||||
|  | ||||
|         for role_id in data.roles.into_keys() { | ||||
|             sqlx::query!( | ||||
|                 "INSERT INTO user_role(user_id, role_id) VALUES (?, ?)", | ||||
|                 self.id, | ||||
|                 role_id | ||||
|             ) | ||||
|             .execute(db) | ||||
|             .await | ||||
|             .unwrap(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result<Self, LoginError> { | ||||
| @@ -309,18 +377,18 @@ ORDER BY last_access DESC | ||||
|  | ||||
|     pub async fn get_days(&self, db: &SqlitePool) -> Vec<Day> { | ||||
|         let mut days = Vec::new(); | ||||
|         for i in 0..self.amount_days_to_show() { | ||||
|         for i in 0..self.amount_days_to_show(db).await { | ||||
|             let date = (Local::now() + chrono::Duration::days(i)).date_naive(); | ||||
|  | ||||
|             if self.is_guest { | ||||
|             if self.has_role(db, "scheckbuch").await { | ||||
|                 days.push(Day::new_guest(db, date, false).await); | ||||
|             } else { | ||||
|                 days.push(Day::new(db, date, false).await); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for date in TripDetails::pinned_days(db, self.amount_days_to_show() - 1).await { | ||||
|             if self.is_guest { | ||||
|         for date in TripDetails::pinned_days(db, self.amount_days_to_show(db).await - 1).await { | ||||
|             if self.has_role(db, "scheckbuch").await { | ||||
|                 let day = Day::new_guest(db, date, true).await; | ||||
|                 if !day.planned_events.is_empty() { | ||||
|                     days.push(day); | ||||
| @@ -332,8 +400,8 @@ ORDER BY last_access DESC | ||||
|         days | ||||
|     } | ||||
|  | ||||
|     fn amount_days_to_show(&self) -> i64 { | ||||
|         if self.is_cox { | ||||
|     async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 { | ||||
|         if self.has_role(db, "cox").await { | ||||
|             let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok, | ||||
|                                                                                              //december | ||||
|                                                                                              //has 31 | ||||
| @@ -393,28 +461,21 @@ impl Deref for TechUser { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<User> for TechUser { | ||||
|     type Error = LoginError; | ||||
|  | ||||
|     fn try_from(user: User) -> Result<Self, Self::Error> { | ||||
|         if user.is_tech { | ||||
|             Ok(TechUser { user }) | ||||
|         } else { | ||||
|             Err(LoginError::NotATech) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait] | ||||
| impl<'r> FromRequest<'r> for TechUser { | ||||
|     type Error = LoginError; | ||||
|  | ||||
|     async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { | ||||
|         let db = req.rocket().state::<SqlitePool>().unwrap(); | ||||
|  | ||||
|         match User::from_request(req).await { | ||||
|             Outcome::Success(user) => match user.try_into() { | ||||
|                 Ok(user) => Outcome::Success(user), | ||||
|                 Err(_) => Outcome::Error((Status::Unauthorized, LoginError::NotACox)), | ||||
|             }, | ||||
|             Outcome::Success(user) => { | ||||
|                 if user.has_role(db, "tech").await { | ||||
|                     Outcome::Success(TechUser { user }) | ||||
|                 } else { | ||||
|                     Outcome::Error((Status::Unauthorized, LoginError::NotACox)) | ||||
|                 } | ||||
|             } | ||||
|             Outcome::Error(f) => Outcome::Error(f), | ||||
|             Outcome::Forward(f) => Outcome::Forward(f), | ||||
|         } | ||||
| @@ -433,14 +494,12 @@ impl Deref for CoxUser { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<User> for CoxUser { | ||||
|     type Error = LoginError; | ||||
|  | ||||
|     fn try_from(user: User) -> Result<Self, Self::Error> { | ||||
|         if user.is_cox { | ||||
|             Ok(CoxUser { user }) | ||||
| impl CoxUser { | ||||
|     pub async fn new(db: &SqlitePool, user: User) -> Option<Self> { | ||||
|         if user.has_role(db, "cox").await { | ||||
|             Some(CoxUser { user }) | ||||
|         } else { | ||||
|             Err(LoginError::NotACox) | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -450,11 +509,16 @@ impl<'r> FromRequest<'r> for CoxUser { | ||||
|     type Error = LoginError; | ||||
|  | ||||
|     async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { | ||||
|         let db = req.rocket().state::<SqlitePool>().unwrap(); | ||||
|  | ||||
|         match User::from_request(req).await { | ||||
|             Outcome::Success(user) => match user.try_into() { | ||||
|                 Ok(user) => Outcome::Success(user), | ||||
|                 Err(_) => Outcome::Error((Status::Unauthorized, LoginError::NotACox)), | ||||
|             }, | ||||
|             Outcome::Success(user) => { | ||||
|                 if user.has_role(db, "cox").await { | ||||
|                     Outcome::Success(CoxUser { user }) | ||||
|                 } else { | ||||
|                     Outcome::Error((Status::Unauthorized, LoginError::NotACox)) | ||||
|                 } | ||||
|             } | ||||
|             Outcome::Error(f) => Outcome::Error(f), | ||||
|             Outcome::Forward(f) => Outcome::Forward(f), | ||||
|         } | ||||
| @@ -466,28 +530,20 @@ pub struct AdminUser { | ||||
|     pub(crate) user: User, | ||||
| } | ||||
|  | ||||
| impl TryFrom<User> for AdminUser { | ||||
|     type Error = LoginError; | ||||
|  | ||||
|     fn try_from(user: User) -> Result<Self, Self::Error> { | ||||
|         if user.is_admin { | ||||
|             Ok(AdminUser { user }) | ||||
|         } else { | ||||
|             Err(LoginError::NotAnAdmin) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait] | ||||
| impl<'r> FromRequest<'r> for AdminUser { | ||||
|     type Error = LoginError; | ||||
|  | ||||
|     async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { | ||||
|         let db = req.rocket().state::<SqlitePool>().unwrap(); | ||||
|         match User::from_request(req).await { | ||||
|             Outcome::Success(user) => match user.try_into() { | ||||
|                 Ok(user) => Outcome::Success(user), | ||||
|                 Err(_) => Outcome::Error((Status::Unauthorized, LoginError::NotAnAdmin)), | ||||
|             }, | ||||
|             Outcome::Success(user) => { | ||||
|                 if user.has_role(db, "admin").await { | ||||
|                     Outcome::Success(AdminUser { user }) | ||||
|                 } else { | ||||
|                     Outcome::Error((Status::Unauthorized, LoginError::NotACox)) | ||||
|                 } | ||||
|             } | ||||
|             Outcome::Error(f) => Outcome::Error(f), | ||||
|             Outcome::Forward(f) => Outcome::Forward(f), | ||||
|         } | ||||
| @@ -499,28 +555,20 @@ pub struct NonGuestUser { | ||||
|     pub(crate) user: User, | ||||
| } | ||||
|  | ||||
| impl TryFrom<User> for NonGuestUser { | ||||
|     type Error = LoginError; | ||||
|  | ||||
|     fn try_from(user: User) -> Result<Self, Self::Error> { | ||||
|         if user.is_guest { | ||||
|             Err(LoginError::GuestNotAllowed) | ||||
|         } else { | ||||
|             Ok(NonGuestUser { user }) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait] | ||||
| impl<'r> FromRequest<'r> for NonGuestUser { | ||||
|     type Error = LoginError; | ||||
|  | ||||
|     async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { | ||||
|         let db = req.rocket().state::<SqlitePool>().unwrap(); | ||||
|         match User::from_request(req).await { | ||||
|             Outcome::Success(user) => match user.try_into() { | ||||
|                 Ok(user) => Outcome::Success(user), | ||||
|                 Err(_) => Outcome::Error((Status::Unauthorized, LoginError::NotAnAdmin)), | ||||
|             }, | ||||
|             Outcome::Success(user) => { | ||||
|                 if !user.has_role(db, "scheckbuch").await { | ||||
|                     Outcome::Success(NonGuestUser { user }) | ||||
|                 } else { | ||||
|                     Outcome::Error((Status::Unauthorized, LoginError::NotACox)) | ||||
|                 } | ||||
|             } | ||||
|             Outcome::Error(f) => Outcome::Error(f), | ||||
|             Outcome::Forward(f) => Outcome::Forward(f), | ||||
|         } | ||||
| @@ -529,6 +577,8 @@ impl<'r> FromRequest<'r> for NonGuestUser { | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use std::collections::HashMap; | ||||
|  | ||||
|     use crate::{tera::admin::user::UserEditForm, testdb}; | ||||
|  | ||||
|     use super::User; | ||||
| @@ -580,17 +630,14 @@ mod test { | ||||
|     fn test_succ_create() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         assert_eq!( | ||||
|             User::create(&pool, "new-user-name".into(), false).await, | ||||
|             true | ||||
|         ); | ||||
|         assert_eq!(User::create(&pool, "new-user-name".into()).await, true); | ||||
|     } | ||||
|  | ||||
|     #[sqlx::test] | ||||
|     fn test_duplicate_name_create() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         assert_eq!(User::create(&pool, "admin".into(), false).await, false); | ||||
|         assert_eq!(User::create(&pool, "admin".into()).await, false); | ||||
|     } | ||||
|  | ||||
|     #[sqlx::test] | ||||
| @@ -602,19 +649,17 @@ mod test { | ||||
|             &pool, | ||||
|             UserEditForm { | ||||
|                 id: 1, | ||||
|                 is_guest: false, | ||||
|                 is_cox: false, | ||||
|                 is_admin: false, | ||||
|                 is_tech: false, | ||||
|                 dob: None, | ||||
|                 weight: None, | ||||
|                 sex: None, | ||||
|                 sex: Some("m".into()), | ||||
|                 roles: HashMap::new(), | ||||
|             }, | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|         let user = User::find_by_id(&pool, 1).await.unwrap(); | ||||
|         assert_eq!(user.is_admin, false); | ||||
|  | ||||
|         assert_eq!(user.sex, Some("m".into())); | ||||
|     } | ||||
|  | ||||
|     #[sqlx::test] | ||||
|   | ||||
| @@ -20,7 +20,7 @@ impl UserTrip { | ||||
|             return Err(UserTripError::DetailsLocked); | ||||
|         } | ||||
|  | ||||
|         if user.is_guest && !trip_details.allow_guests { | ||||
|         if user.has_role(db, "scheckbuch").await && !trip_details.allow_guests { | ||||
|             return Err(UserTripError::GuestNotAllowedForThisEvent); | ||||
|         } | ||||
|  | ||||
| @@ -211,11 +211,12 @@ mod test { | ||||
|     fn test_fail_create_is_cox_planned_event() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let cox: CoxUser = User::find_by_name(&pool, "cox".into()) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .try_into() | ||||
|             .unwrap(); | ||||
|         let cox = CoxUser::new( | ||||
|             &pool, | ||||
|             User::find_by_name(&pool, "cox".into()).await.unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap(); | ||||
|         Trip::new_join(&pool, &cox, &planned_event).await.unwrap(); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| use crate::model::{ | ||||
|     boat::{Boat, BoatToAdd, BoatToUpdate}, | ||||
|     location::Location, | ||||
|     user::{AdminUser, User}, | ||||
|     user::{AdminUser, User, UserWithRoles}, | ||||
| }; | ||||
| use rocket::{ | ||||
|     form::Form, | ||||
| @@ -30,7 +30,10 @@ async fn index( | ||||
|     context.insert("boats", &boats); | ||||
|     context.insert("locations", &locations); | ||||
|     context.insert("users", &users); | ||||
|     context.insert("loggedin_user", &admin.user); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(admin.user, db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("admin/boat/index", context.into_json()) | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,10 @@ | ||||
| use crate::model::user::{AdminUser, User}; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use crate::model::{ | ||||
|     role::Role, | ||||
|     user::{AdminUser, User, UserWithRoles}, | ||||
| }; | ||||
| use futures::future::join_all; | ||||
| use rocket::{ | ||||
|     form::Form, | ||||
|     get, post, | ||||
| @@ -15,14 +21,26 @@ async fn index( | ||||
|     admin: AdminUser, | ||||
|     flash: Option<FlashMessage<'_>>, | ||||
| ) -> Template { | ||||
|     let users = User::all(db).await; | ||||
|     let user_futures: Vec<_> = User::all(db) | ||||
|         .await | ||||
|         .into_iter() | ||||
|         .map(|u| async move { UserWithRoles::from_user(u, db).await }) | ||||
|         .collect(); | ||||
|  | ||||
|     let users: Vec<UserWithRoles> = join_all(user_futures).await; | ||||
|  | ||||
|     let roles = Role::all(db).await; | ||||
|  | ||||
|     let mut context = Context::new(); | ||||
|     if let Some(msg) = flash { | ||||
|         context.insert("flash", &msg.into_inner()); | ||||
|     } | ||||
|     context.insert("users", &users); | ||||
|     context.insert("loggedin_user", &admin.user); | ||||
|     context.insert("roles", &roles); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(admin.user, db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("admin/user/index", context.into_json()) | ||||
| } | ||||
| @@ -57,16 +75,13 @@ async fn delete(db: &State<SqlitePool>, _admin: AdminUser, user: i32) -> Flash<R | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(FromForm)] | ||||
| #[derive(FromForm, Debug)] | ||||
| pub struct UserEditForm { | ||||
|     pub(crate) id: i32, | ||||
|     pub(crate) is_guest: bool, | ||||
|     pub(crate) is_cox: bool, | ||||
|     pub(crate) is_admin: bool, | ||||
|     pub(crate) is_tech: bool, | ||||
|     pub(crate) dob: Option<String>, | ||||
|     pub(crate) weight: Option<String>, | ||||
|     pub(crate) sex: Option<String>, | ||||
|     pub(crate) roles: HashMap<String, String>, | ||||
| } | ||||
|  | ||||
| #[post("/user", data = "<data>")] | ||||
| @@ -91,7 +106,6 @@ async fn update( | ||||
| #[derive(FromForm)] | ||||
| struct UserAddForm<'r> { | ||||
|     name: &'r str, | ||||
|     is_guest: bool, | ||||
| } | ||||
|  | ||||
| #[post("/user/new", data = "<data>")] | ||||
| @@ -100,7 +114,7 @@ async fn create( | ||||
|     data: Form<UserAddForm<'_>>, | ||||
|     _admin: AdminUser, | ||||
| ) -> Flash<Redirect> { | ||||
|     if User::create(db, data.name, data.is_guest).await { | ||||
|     if User::create(db, data.name).await { | ||||
|         Flash::success(Redirect::to("/admin/user"), "Successfully created user") | ||||
|     } else { | ||||
|         Flash::error( | ||||
|   | ||||
| @@ -13,7 +13,7 @@ use crate::{ | ||||
|     model::{ | ||||
|         boat::Boat, | ||||
|         boatdamage::{BoatDamage, BoatDamageFixed, BoatDamageToAdd, BoatDamageVerified}, | ||||
|         user::{CoxUser, NonGuestUser, TechUser, User}, | ||||
|         user::{CoxUser, NonGuestUser, TechUser, User, UserWithRoles}, | ||||
|     }, | ||||
|     tera::log::KioskCookie, | ||||
| }; | ||||
| @@ -57,7 +57,10 @@ async fn index( | ||||
|  | ||||
|     context.insert("boatdamages", &boatdamages); | ||||
|     context.insert("boats", &boats); | ||||
|     context.insert("loggedin_user", &user.user); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(user.user, db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("boatdamages", context.into_json()) | ||||
| } | ||||
|   | ||||
| @@ -18,7 +18,7 @@ use tera::Context; | ||||
|  | ||||
| use crate::model::{ | ||||
|     log::Log, | ||||
|     user::{AdminUser, User}, | ||||
|     user::{AdminUser, User, UserWithRoles}, | ||||
| }; | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| @@ -51,7 +51,7 @@ async fn send(db: &State<SqlitePool>, _user: AdminUser) -> Template { | ||||
|  | ||||
|     Template::render( | ||||
|         "ergo.final", | ||||
|         context!(loggedin_user: &_user.user, thirty, dozen), | ||||
|         context!(loggedin_user: &UserWithRoles::from_user(_user.user, db).await, thirty, dozen), | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @@ -120,7 +120,7 @@ async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_ | ||||
|     if let Some(msg) = flash { | ||||
|         context.insert("flash", &msg.into_inner()); | ||||
|     } | ||||
|     context.insert("loggedin_user", &user); | ||||
|     context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); | ||||
|     context.insert("users", &users); | ||||
|     context.insert("thirty", &thirty); | ||||
|     context.insert("dozen", &dozen); | ||||
|   | ||||
| @@ -23,7 +23,7 @@ use crate::model::{ | ||||
|         LogbookUpdateError, | ||||
|     }, | ||||
|     logtype::LogType, | ||||
|     user::{NonGuestUser, User, UserWithWaterStatus}, | ||||
|     user::{NonGuestUser, User, UserWithRoles, UserWithWaterStatus}, | ||||
| }; | ||||
|  | ||||
| pub struct KioskCookie(String); | ||||
| @@ -76,7 +76,10 @@ async fn index( | ||||
|     context.insert("coxes", &coxes); | ||||
|     context.insert("users", &users); | ||||
|     context.insert("logtypes", &logtypes); | ||||
|     context.insert("loggedin_user", &user.user); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(user.user, db).await, | ||||
|     ); | ||||
|     context.insert("on_water", &on_water); | ||||
|     context.insert("distances", &distances); | ||||
|  | ||||
| @@ -87,7 +90,10 @@ async fn index( | ||||
| async fn show(db: &State<SqlitePool>, user: NonGuestUser) -> Template { | ||||
|     let logs = Logbook::completed(db).await; | ||||
|  | ||||
|     Template::render("log.completed", context!(logs, loggedin_user: &user.user)) | ||||
|     Template::render( | ||||
|         "log.completed", | ||||
|         context!(logs, loggedin_user: &UserWithRoles::from_user(user.user, db).await), | ||||
|     ) | ||||
| } | ||||
|  | ||||
| #[get("/show")] | ||||
| @@ -232,7 +238,7 @@ async fn create_kiosk( | ||||
|     ) | ||||
|     .await; | ||||
|  | ||||
|     create_logbook(db, data, &NonGuestUser::try_from(creator).unwrap()).await //TODO: fixme | ||||
|     create_logbook(db, data, &NonGuestUser { user: creator }).await //TODO: fixme | ||||
| } | ||||
|  | ||||
| async fn home_logbook( | ||||
| @@ -279,12 +285,11 @@ async fn home_kiosk( | ||||
|         db, | ||||
|         data, | ||||
|         logbook_id, | ||||
|         &NonGuestUser::try_from( | ||||
|             User::find_by_id(db, logbook.shipmaster as i32) | ||||
|         &NonGuestUser { | ||||
|             user: User::find_by_id(db, logbook.shipmaster as i32) | ||||
|                 .await | ||||
|                 .unwrap(), //TODO: fixme | ||||
|         ) | ||||
|         .unwrap(), | ||||
|         }, | ||||
|     ) | ||||
|     .await | ||||
| } | ||||
|   | ||||
| @@ -16,7 +16,7 @@ use crate::model::{ | ||||
|     log::Log, | ||||
|     tripdetails::TripDetails, | ||||
|     triptype::TripType, | ||||
|     user::User, | ||||
|     user::{User, UserWithRoles}, | ||||
|     usertrip::{UserTrip, UserTripDeleteError, UserTripError}, | ||||
| }; | ||||
|  | ||||
| @@ -47,7 +47,7 @@ async fn wikiauth(db: &State<SqlitePool>, login: Form<LoginForm<'_>>) -> String | ||||
| async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_>>) -> Template { | ||||
|     let mut context = Context::new(); | ||||
|  | ||||
|     if user.is_cox || user.is_admin { | ||||
|     if user.has_role(db, "cox").await || user.has_role(db, "admin").await { | ||||
|         let triptypes = TripType::all(db).await; | ||||
|         context.insert("trip_types", &triptypes); | ||||
|     } | ||||
| @@ -58,7 +58,7 @@ async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_ | ||||
|         context.insert("flash", &msg.into_inner()); | ||||
|     } | ||||
|     println!("{user:#?}"); | ||||
|     context.insert("loggedin_user", &user); | ||||
|     context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); | ||||
|     context.insert("days", &days); | ||||
|     Template::render("index", context.into_json()) | ||||
| } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ use sqlx::SqlitePool; | ||||
|  | ||||
| use crate::model::{ | ||||
|     stat::{self, Stat}, | ||||
|     user::NonGuestUser, | ||||
|     user::{NonGuestUser, UserWithRoles}, | ||||
| }; | ||||
|  | ||||
| use super::log::KioskCookie; | ||||
| @@ -16,7 +16,7 @@ async fn index_boat(db: &State<SqlitePool>, user: NonGuestUser, year: Option<i32 | ||||
|  | ||||
|     Template::render( | ||||
|         "stat.boats", | ||||
|         context!(loggedin_user: &user.user, stat, kiosk), | ||||
|         context!(loggedin_user: &UserWithRoles::from_user(user.user, db).await, stat, kiosk), | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @@ -41,7 +41,7 @@ async fn index(db: &State<SqlitePool>, user: NonGuestUser, year: Option<i32>) -> | ||||
|  | ||||
|     Template::render( | ||||
|         "stat.people", | ||||
|         context!(loggedin_user: &user.user, stat, personal, kiosk, guest_km), | ||||
|         context!(loggedin_user: &UserWithRoles::from_user(user.user, db).await, stat, personal, kiosk, guest_km), | ||||
|     ) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,14 @@ | ||||
| CREATE TABLE IF NOT EXISTS "role" ( | ||||
| 	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||
| 	"name" text NOT NULL UNIQUE | ||||
| ); | ||||
| INSERT INTO "role" (name) VALUES ('admin'); | ||||
| INSERT INTO "role" (name) VALUES ('cox'); | ||||
| INSERT INTO "role" (name) VALUES ('scheckbuch'); | ||||
| INSERT INTO "role" (name) VALUES ('tech'); | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS "user_role" ( | ||||
| 	"user_id" INTEGER NOT NULL REFERENCES user(id), | ||||
| 	"role_id" INTEGER NOT NULL REFERENCES role(id), | ||||
| 	CONSTRAINT unq UNIQUE (user_id, role_id)  | ||||
| ); | ||||
|   | ||||
| @@ -18,10 +18,6 @@ | ||||
| 						<label for="name" class="sr-only">Name</label> | ||||
| 						<input type="text" name="name" class="relative block rounded-md border-0 py-1.5 px-2 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-primary-600 sm:text-sm sm:leading-6 mb-2 md:mb-0" placeholder="Name"/> | ||||
| 					</div> | ||||
| 					<div class="flex items-center"> | ||||
| 						<label for="is_guest" class="flex items-center cursor-pointer hover:text-gray-100"><input type="checkbox" id="is_guest" name="is_guest" class="h-4 w-4 accent-gray-200 mr-2" checked="true"/> | ||||
| 							Scheckbuch</label> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div class="text-right"> | ||||
| @@ -53,10 +49,9 @@ | ||||
|                 {% endif %} | ||||
|               </div> | ||||
|               <div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-3"> | ||||
|                 {{ macros::checkbox(label='Scheckbuch', name='is_guest', id=loop.index , checked=user.is_guest) }} | ||||
|                 {{ macros::checkbox(label='Steuerberechtigter', name='is_cox', id=loop.index , checked=user.is_cox) }} | ||||
|                 {{ macros::checkbox(label='Technical', name='is_tech', id=loop.index , checked=user.is_tech) }} | ||||
|                 {{ macros::checkbox(label='Admin', name='is_admin', id=loop.index , checked=user.is_admin) }} | ||||
| 	      	{% for role in roles %} | ||||
|                 	{{ macros::checkbox(label=role.name, name="roles[" ~ role.id ~ "]", id=loop.index , checked=role.name in user.roles) }} | ||||
| 		{% endfor%} | ||||
|                 {{ macros::input(label='DOB', name='dob', id=loop.index, type="text", value=user.dob) }} | ||||
|                 {{ macros::input(label='Weight (kg)', name='weight', id=loop.index, type="text", value=user.weight) }} | ||||
|                 {{ macros::input(label='Sex', name='sex', id=loop.index, type="text", value=user.sex) }} | ||||
|   | ||||
| @@ -57,10 +57,10 @@ | ||||
|         {% if boatdamage.fixed_at %} | ||||
|           <small class="block text-gray-600 dark:text-gray-100">Repariert von {{ boatdamage.user_fixed.name }} am/um {{ boatdamage.fixed_at | date(format='%d.%m.%Y (%H:%M)') }}</small> | ||||
|         {% else %} | ||||
|           {% if loggedin_user.is_cox %} | ||||
|           {% if "cox" in loggedin_user.roles %} | ||||
|             <form action="/boatdamage/{{ boatdamage.id }}/fixed" method="post" class="flex justify-between mt-3"> | ||||
|               <input type="text" name="desc" value="{{ boatdamage.desc }}" class="grow input rounded-s" /> | ||||
|               {% if loggedin_user.is_tech %} | ||||
|               {% if "tech" in loggedin_user.roles %} | ||||
|                 <input type="submit" class="btn btn-primary" style="border-top-left-radius: 0; border-bottom-left-radius: 0;" value="Repariert und verifiziert" /> | ||||
|               {% else %} | ||||
|                 <input type="submit" class="btn btn-primary" style="border-top-left-radius: 0; border-bottom-left-radius: 0;" value="Repariert" /> | ||||
| @@ -72,7 +72,7 @@ | ||||
|         {% if boatdamage.verified_at %} | ||||
|           <small class="block text-gray-600 dark:text-gray-100">Verifziert von {{ boatdamage.user_verified.name }} am/um {{ boatdamage.verified_at | date(format='%d.%m.%Y (%H:%M)') }}</small> | ||||
|         {% else %} | ||||
|           {% if loggedin_user.is_tech and boatdamage.fixed_at  %} | ||||
|           {% if "tech" in loggedin_user.roles and boatdamage.fixed_at  %} | ||||
|             <form action="/boatdamage/{{ boatdamage.id }}/verified" method="post" class="flex justify-between mt-3"> | ||||
|               <input type="text" name="desc" value="{{ boatdamage.desc }}" class="grow input rounded-s"/> | ||||
|               <input type="submit" class="btn btn-dark" style="border-top-left-radius: 0; border-bottom-left-radius: 0;"  value="Verifiziert" /> | ||||
|   | ||||
| @@ -132,7 +132,7 @@ | ||||
|       </details> | ||||
|       </div> | ||||
|  | ||||
|       {% if loggedin_user.is_admin %} | ||||
|       {% if "admin" in loggedin_user.roles %} | ||||
|           <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow grid gap-3"> | ||||
|             <h2 class="h2">Update</h2> | ||||
|              | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| {% if loggedin_user.is_cox %} | ||||
| {% if "cox" in loggedin_user.roles %} | ||||
| 	<div class="sm:col-span-2 lg:col-span-3 grid md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3"> | ||||
| 		<button type="button" title="Toggle View" class="group btn btn-primary filter-trips-js" data-action="filter-days" id="filterdays-js" aria-pressed="false"> | ||||
| 			{% include "includes/funnel-icon" %} | ||||
|   | ||||
| @@ -85,7 +85,7 @@ | ||||
| 						{% set_global sel = true %} | ||||
| 					{% endif %} | ||||
| 				{% endfor %} | ||||
| 				<option value="{{ user.id }}" {% if sel %} selected {% endif %} {% if user.on_water %} disabled="disabled" {% endif %} data-custom-properties='{"is_cox": {{ user.is_cox }}, "steers": {{ user.id == steering_person_id }}, "cox_on_boat": {{ user.id == cox_on_boat}}}'> | ||||
| 				<option value="{{ user.id }}" {% if sel %} selected {% endif %} {% if user.on_water %} disabled="disabled" {% endif %} data-custom-properties='{"is_cox": {{ "cox" in user.roles }}, "steers": {{ user.id == steering_person_id }}, "cox_on_boat": {{ user.id == cox_on_boat}}}'> | ||||
| 					{{user.name}} | ||||
| 					{% if user.on_water %} | ||||
| 						(am Wasser) | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|           {% include "includes/question-icon" %} | ||||
|           <span class="sr-only">FAQs</span> | ||||
|         </a> | ||||
|         {% if loggedin_user.is_guest and loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %} | ||||
|         {% if "scheckbuch" in loggedin_user.roles and loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %} | ||||
|           <a | ||||
|             href="#" | ||||
|             class="inline-flex justify-center rounded-md bg-primary-600 mx-1 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" | ||||
| @@ -42,7 +42,7 @@ | ||||
|             </div> | ||||
|           </div> | ||||
| 	      {% endif %} | ||||
|         {% if not loggedin_user.is_guest %} | ||||
|         {% if "scheckbuch" not in loggedin_user.roles %} | ||||
|         <a | ||||
|           href="#" | ||||
|           class="inline-flex justify-center rounded-md bg-primary-600 mx-1 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" | ||||
| @@ -85,7 +85,7 @@ | ||||
|             > | ||||
|               Bootsauswertung | ||||
|             </a> | ||||
|             {% if loggedin_user.is_admin %} | ||||
|             {% if "admin" in loggedin_user.roles %} | ||||
|             <a | ||||
|               href="/admin/boat" | ||||
|               class="block w-100 py-2 hover:text-primary-600 border-t" | ||||
| @@ -102,7 +102,7 @@ | ||||
|           </div> | ||||
|         </div> | ||||
|         {% endif %} | ||||
| 	      {% if loggedin_user.is_admin %} | ||||
| 	      {% if "admin" in loggedin_user.roles %} | ||||
|         <a | ||||
|           href="/admin/user" | ||||
|           class="inline-flex justify-center rounded-md bg-primary-600 mx-1 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" | ||||
|   | ||||
| @@ -71,7 +71,7 @@ | ||||
| 												{# --- END Row Buttons --- #} | ||||
|  | ||||
| 												{# --- START Cox Buttons --- #} | ||||
| 												{% if loggedin_user.is_cox %} | ||||
| 												{% if "cox" in loggedin_user.roles %} | ||||
| 													{% set_global cur_user_participates = false %} | ||||
| 													{% for cox in planned_event.cox %} | ||||
| 														{% if cox.name == loggedin_user.name %} | ||||
| @@ -111,11 +111,11 @@ | ||||
| 												{# --- START List Rowers --- #} | ||||
| 												{% if planned_event.max_people > 0 %} | ||||
| 													{% set amount_cur_rower = planned_event.rower | length %} | ||||
| 													{{ macros::box(participants=planned_event.rower, empty_seats=planned_event.max_people - amount_cur_rower, bg='primary-100', color='black', trip_details_id=planned_event.trip_details_id, allow_removing=loggedin_user.is_admin) }} | ||||
| 													{{ macros::box(participants=planned_event.rower, empty_seats=planned_event.max_people - amount_cur_rower, bg='primary-100', color='black', trip_details_id=planned_event.trip_details_id, allow_removing="admin" in loggedin_user.roles) }} | ||||
| 												{% endif %} | ||||
| 												{# --- END List Rowers --- #} | ||||
|  | ||||
| 													{% if loggedin_user.is_admin %} | ||||
| 													{% if "admin" in loggedin_user.roles %} | ||||
| 														<form action="/join/{{ planned_event.trip_details_id }}" method="get" /> | ||||
| 															{{ macros::input(label='Gast', class="input rounded-t", name='user_note', type='text', required=true) }} | ||||
| 															<input value="Gast hinzufügen" class="btn btn-primary w-full rounded-t-none-important" type="submit"/> | ||||
| @@ -127,7 +127,7 @@ | ||||
| 													<div class="text-primary-900 bg-primary-50 text-center p-1 mb-4">Gäste willkommen!</div> | ||||
| 												{% endif %} | ||||
|  | ||||
| 												{% if loggedin_user.is_admin %} | ||||
| 												{% if "admin" in loggedin_user.roles %} | ||||
|  | ||||
| 													{# --- START Edit Form --- #} | ||||
| 													<div class="bg-gray-100 dark:bg-primary-900 p-3 mt-4 rounded-md"> | ||||
| @@ -258,9 +258,9 @@ | ||||
| 				</div> | ||||
|  | ||||
| 				{# --- START Add Buttons --- #} | ||||
| 				{% if loggedin_user.is_admin or loggedin_user.is_cox %} | ||||
| 					<div class="grid {% if loggedin_user.is_admin %} grid-cols-2 {% endif %} text-center"> | ||||
| 						{% if loggedin_user.is_admin %} | ||||
| 				{% if "admin" in loggedin_user.roles or "cox" in loggedin_user.roles %} | ||||
| 					<div class="grid {% if "admin" in loggedin_user.roles %} grid-cols-2 {% endif %} text-center"> | ||||
| 						{% if "admin" in loggedin_user.roles %} | ||||
| 							<a href="#" data-sidebar="true" data-trigger="sidebar" data-header="<strong>Event</strong> am {{ day.day| date(format='%d.%m.%Y') }} erstellen" data-day="{{ day.day }}" data-body="#addEventForm" class="relative inline-block w-full bg-primary-900 hover:bg-primary-950 focus:bg-primary-950 dark:bg-primary-950 text-white py-2 rounded-bl-md text-sm font-semibold"> | ||||
| 								<span class="absolute inset-y-0 left-0 flex items-center pl-3"> | ||||
| 									{% include "includes/plus-icon" %} | ||||
| @@ -269,8 +269,8 @@ | ||||
| 							</a> | ||||
| 						{% endif %} | ||||
|  | ||||
| 						{% if loggedin_user.is_cox%} | ||||
| 							<a href="#" data-sidebar="true" data-trigger="sidebar" data-header="<strong>Ausfahrt</strong> am {{ day.day| date(format='%d.%m.%Y') }} erstellen" data-day="{{ day.day }}" data-body="#sidebarForm" class="relative inline-block w-full py-2 text-primary-900 hover:text-primary-950 dark:bg-primary-600 dark:text-white dark:hover:bg-primary-500 dark:hover:text-white focus:text-primary-950 text-sm font-semibold bg-gray-100 hover:bg-gray-200 focus:bg-gray-200 {% if loggedin_user.is_admin %} rounded-br-md {% else %} rounded-b-md {% endif %}"> | ||||
| 						{% if "cox" in loggedin_user.roles %} | ||||
| 							<a href="#" data-sidebar="true" data-trigger="sidebar" data-header="<strong>Ausfahrt</strong> am {{ day.day| date(format='%d.%m.%Y') }} erstellen" data-day="{{ day.day }}" data-body="#sidebarForm" class="relative inline-block w-full py-2 text-primary-900 hover:text-primary-950 dark:bg-primary-600 dark:text-white dark:hover:bg-primary-500 dark:hover:text-white focus:text-primary-950 text-sm font-semibold bg-gray-100 hover:bg-gray-200 focus:bg-gray-200 {% if "admin" in loggedin_user.roles %} rounded-br-md {% else %} rounded-b-md {% endif %}"> | ||||
| 								<span class="absolute inset-y-0 left-0 flex items-center pl-3"> | ||||
| 									{% include "includes/plus-icon" %} | ||||
| 								</span> | ||||
| @@ -285,10 +285,10 @@ | ||||
| 	</div> | ||||
| </div> | ||||
|  | ||||
| {% if loggedin_user.is_cox %} | ||||
| {% if "cox" in loggedin_user.roles %} | ||||
| 	{% include "forms/trip" %} | ||||
| {% endif %} | ||||
|  | ||||
| {% if loggedin_user.is_admin %} | ||||
| {% if "admin" in loggedin_user.roles %} | ||||
| 	{% include "forms/event" %} | ||||
| {% endif %}{% endblock content %} | ||||
|   | ||||
| @@ -24,7 +24,7 @@ | ||||
| 			<div class="md:col-span-3 bg-white dark:bg-primary-900 rounded-md shadow"> | ||||
| 				<h2 class="h2">Neue Ausfahrt</h2> | ||||
| 				<div class="p-3"> | ||||
| 					{{ log::new(only_ones=loggedin_user.is_cox==false, shipmaster=loggedin_user.id) }} | ||||
| 					{{ log::new(only_ones="cox" not in loggedin_user.roles, shipmaster=loggedin_user.id) }} | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div class="bg-white dark:bg-primary-900 rounded-md shadow"> | ||||
| @@ -33,7 +33,7 @@ | ||||
| 				{% if on_water | length > 0 %} | ||||
| 					{% for log in on_water %} | ||||
| 						{% if log.shipmaster == loggedin_user.id %} | ||||
| 							{{ log::show(log=log, state="on_water", allowed_to_close=true, only_ones=loggedin_user.is_cox==false) }} | ||||
| 							{{ log::show(log=log, state="on_water", allowed_to_close=true, only_ones="cox" not in loggedin_user.roles) }} | ||||
| 						{% else %} | ||||
| 							{{ log::show(log=log, state="on_water", only_ones=true) }} | ||||
| 						{% endif %} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user