Compare commits
	
		
			2 Commits
		
	
	
		
			e4da952a62
			...
			108242139a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 108242139a | |||
| c7d7d0ca83 | 
@@ -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,10 +276,11 @@ mod test {
 | 
			
		||||
    fn test_new_own() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();
 | 
			
		||||
@@ -301,10 +302,11 @@ mod test {
 | 
			
		||||
    fn test_new_succ_join() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox2".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox2".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap();
 | 
			
		||||
@@ -316,10 +318,11 @@ mod test {
 | 
			
		||||
    fn test_new_failed_join_already_cox() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox2".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox2".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap();
 | 
			
		||||
@@ -332,10 +335,11 @@ mod test {
 | 
			
		||||
    fn test_succ_update_own() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let trip = Trip::find_by_id(&pool, 1).await.unwrap();
 | 
			
		||||
@@ -354,10 +358,11 @@ mod test {
 | 
			
		||||
    fn test_succ_update_own_with_triptype() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let trip = Trip::find_by_id(&pool, 1).await.unwrap();
 | 
			
		||||
@@ -377,10 +382,11 @@ mod test {
 | 
			
		||||
    fn test_fail_update_own_not_your_trip() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox2".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox2".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let trip = Trip::find_by_id(&pool, 1).await.unwrap();
 | 
			
		||||
@@ -397,10 +403,11 @@ mod test {
 | 
			
		||||
    fn test_succ_delete_by_planned_event() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap();
 | 
			
		||||
@@ -419,10 +426,11 @@ mod test {
 | 
			
		||||
    fn test_succ_delete() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let trip = Trip::find_by_id(&pool, 1).await.unwrap();
 | 
			
		||||
@@ -436,10 +444,11 @@ mod test {
 | 
			
		||||
    fn test_fail_delete_diff_cox() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox2".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox2".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let trip = Trip::find_by_id(&pool, 1).await.unwrap();
 | 
			
		||||
@@ -457,10 +466,11 @@ mod test {
 | 
			
		||||
    fn test_fail_delete_someone_registered() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .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,12 +267,8 @@ 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,
 | 
			
		||||
        )
 | 
			
		||||
    pub async fn create(db: &SqlitePool, name: &str) -> bool {
 | 
			
		||||
        sqlx::query!("INSERT INTO USER(name) VALUES (?)", name)
 | 
			
		||||
            .execute(db)
 | 
			
		||||
            .await
 | 
			
		||||
            .is_ok()
 | 
			
		||||
@@ -221,11 +276,7 @@ ORDER BY last_access DESC
 | 
			
		||||
 | 
			
		||||
    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,10 +211,11 @@ mod test {
 | 
			
		||||
    fn test_fail_create_is_cox_planned_event() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
 | 
			
		||||
        let cox: CoxUser = User::find_by_name(&pool, "cox".into())
 | 
			
		||||
        let cox = CoxUser::new(
 | 
			
		||||
            &pool,
 | 
			
		||||
            User::find_by_name(&pool, "cox".into()).await.unwrap(),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .try_into()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let planned_event = PlannedEvent::find_by_id(&pool, 1).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