forked from Ruderverein-Donau-Linz/rowt
		
	limit users to proper role, Fixes #135
This commit is contained in:
		@@ -2,18 +2,26 @@ 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 "role" (name) VALUES ('Donau Linz');
 | 
			
		||||
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_role" (user_id, role_id) VALUES(1,5);
 | 
			
		||||
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_role" (user_id, role_id) VALUES(2,5);
 | 
			
		||||
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,5);
 | 
			
		||||
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,5);
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(4,2);
 | 
			
		||||
INSERT INTO "user" (name) VALUES('new');
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(5,5);
 | 
			
		||||
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,5);
 | 
			
		||||
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 "user_role" (user_id, role_id) VALUES(7,5);
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
 
 | 
			
		||||
@@ -264,6 +264,10 @@ ORDER BY departure DESC
 | 
			
		||||
            return Err(LogbookCreateError::BoatNotFound);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if boat.amount_seats == 1 && log.rowers.is_empty() {
 | 
			
		||||
            log.rowers = vec![created_by_user.id];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if boat.amount_seats == 1 {
 | 
			
		||||
            log.shipmaster = Some(log.rowers[0]);
 | 
			
		||||
            log.steering_person = Some(log.rowers[0]);
 | 
			
		||||
 
 | 
			
		||||
@@ -440,23 +440,20 @@ impl<'r> FromRequest<'r> for User {
 | 
			
		||||
                Ok(user_id) => {
 | 
			
		||||
                    let db = req.rocket().state::<SqlitePool>().unwrap();
 | 
			
		||||
                    let Some(user) = User::find_by_id(db, user_id).await else {
 | 
			
		||||
                        return Outcome::Error((Status::Unauthorized, LoginError::UserNotFound));
 | 
			
		||||
                        return Outcome::Error((Status::Forbidden, LoginError::UserNotFound));
 | 
			
		||||
                    };
 | 
			
		||||
                    if user.deleted {
 | 
			
		||||
                        return Outcome::Error((Status::Unauthorized, LoginError::UserDeleted));
 | 
			
		||||
                        return Outcome::Error((Status::Forbidden, LoginError::UserDeleted));
 | 
			
		||||
                    }
 | 
			
		||||
                    user.logged_in(db).await;
 | 
			
		||||
 | 
			
		||||
                    let mut cookie = Cookie::new("loggedin_user", format!("{}", user.id));
 | 
			
		||||
                    cookie.set_expires(OffsetDateTime::now_utc() + Duration::weeks(12));
 | 
			
		||||
                    cookie.set_expires(OffsetDateTime::now_utc() + Duration::weeks(2));
 | 
			
		||||
                    req.cookies().add_private(cookie);
 | 
			
		||||
 | 
			
		||||
                    Outcome::Success(user)
 | 
			
		||||
                }
 | 
			
		||||
                Err(_) => {
 | 
			
		||||
                    println!("{:?}", user_id.value());
 | 
			
		||||
                    Outcome::Error((Status::Unauthorized, LoginError::DeserializationError))
 | 
			
		||||
                }
 | 
			
		||||
                Err(_) => Outcome::Error((Status::Unauthorized, LoginError::DeserializationError)),
 | 
			
		||||
            },
 | 
			
		||||
            None => Outcome::Error((Status::Unauthorized, LoginError::NotLoggedIn)),
 | 
			
		||||
        }
 | 
			
		||||
@@ -487,7 +484,7 @@ impl<'r> FromRequest<'r> for TechUser {
 | 
			
		||||
                if user.has_role(db, "tech").await {
 | 
			
		||||
                    Outcome::Success(TechUser { user })
 | 
			
		||||
                } else {
 | 
			
		||||
                    Outcome::Error((Status::Unauthorized, LoginError::NotACox))
 | 
			
		||||
                    Outcome::Error((Status::Forbidden, LoginError::NotACox))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Outcome::Error(f) => Outcome::Error(f),
 | 
			
		||||
@@ -530,7 +527,7 @@ impl<'r> FromRequest<'r> for CoxUser {
 | 
			
		||||
                if user.has_role(db, "cox").await {
 | 
			
		||||
                    Outcome::Success(CoxUser { user })
 | 
			
		||||
                } else {
 | 
			
		||||
                    Outcome::Error((Status::Unauthorized, LoginError::NotACox))
 | 
			
		||||
                    Outcome::Error((Status::Forbidden, LoginError::NotACox))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Outcome::Error(f) => Outcome::Error(f),
 | 
			
		||||
@@ -555,7 +552,7 @@ impl<'r> FromRequest<'r> for AdminUser {
 | 
			
		||||
                if user.has_role(db, "admin").await {
 | 
			
		||||
                    Outcome::Success(AdminUser { user })
 | 
			
		||||
                } else {
 | 
			
		||||
                    Outcome::Error((Status::Unauthorized, LoginError::NotACox))
 | 
			
		||||
                    Outcome::Error((Status::Forbidden, LoginError::NotACox))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Outcome::Error(f) => Outcome::Error(f),
 | 
			
		||||
@@ -565,22 +562,65 @@ impl<'r> FromRequest<'r> for AdminUser {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct NonGuestUser {
 | 
			
		||||
    pub(crate) user: User,
 | 
			
		||||
}
 | 
			
		||||
pub struct AllowedForPlannedTripsUser(pub(crate) User);
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
impl<'r> FromRequest<'r> for NonGuestUser {
 | 
			
		||||
impl<'r> FromRequest<'r> for AllowedForPlannedTripsUser {
 | 
			
		||||
    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) => {
 | 
			
		||||
                if !user.has_role(db, "scheckbuch").await {
 | 
			
		||||
                    Outcome::Success(NonGuestUser { user })
 | 
			
		||||
                if user.has_role(db, "Donau Linz").await {
 | 
			
		||||
                    Outcome::Success(AllowedForPlannedTripsUser(user))
 | 
			
		||||
                } else if user.has_role(db, "scheckbuch").await {
 | 
			
		||||
                    Outcome::Success(AllowedForPlannedTripsUser(user))
 | 
			
		||||
                } else {
 | 
			
		||||
                    Outcome::Error((Status::Unauthorized, LoginError::NotACox))
 | 
			
		||||
                    Outcome::Error((Status::Forbidden, LoginError::NotACox))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Outcome::Error(f) => Outcome::Error(f),
 | 
			
		||||
            Outcome::Forward(f) => Outcome::Forward(f),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Into<User> for AllowedForPlannedTripsUser {
 | 
			
		||||
    fn into(self) -> User {
 | 
			
		||||
        self.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct DonauLinzUser(pub(crate) User);
 | 
			
		||||
 | 
			
		||||
impl Into<User> for DonauLinzUser {
 | 
			
		||||
    fn into(self) -> User {
 | 
			
		||||
        self.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Deref for DonauLinzUser {
 | 
			
		||||
    type Target = User;
 | 
			
		||||
 | 
			
		||||
    fn deref(&self) -> &Self::Target {
 | 
			
		||||
        &self.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
impl<'r> FromRequest<'r> for DonauLinzUser {
 | 
			
		||||
    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) => {
 | 
			
		||||
                if user.has_role(db, "Donau Linz").await {
 | 
			
		||||
                    Outcome::Success(DonauLinzUser(user))
 | 
			
		||||
                } else {
 | 
			
		||||
                    Outcome::Error((Status::Forbidden, LoginError::NotACox))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Outcome::Error(f) => Outcome::Error(f),
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ use crate::{
 | 
			
		||||
    model::{
 | 
			
		||||
        boat::Boat,
 | 
			
		||||
        boatdamage::{BoatDamage, BoatDamageFixed, BoatDamageToAdd, BoatDamageVerified},
 | 
			
		||||
        user::{CoxUser, NonGuestUser, TechUser, User, UserWithRoles},
 | 
			
		||||
        user::{CoxUser, DonauLinzUser, TechUser, User, UserWithRoles},
 | 
			
		||||
    },
 | 
			
		||||
    tera::log::KioskCookie,
 | 
			
		||||
};
 | 
			
		||||
@@ -45,7 +45,7 @@ async fn index_kiosk(
 | 
			
		||||
async fn index(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    flash: Option<FlashMessage<'_>>,
 | 
			
		||||
    user: NonGuestUser,
 | 
			
		||||
    user: DonauLinzUser,
 | 
			
		||||
) -> Template {
 | 
			
		||||
    let boatdamages = BoatDamage::all(db).await;
 | 
			
		||||
    let boats = Boat::all(db).await;
 | 
			
		||||
@@ -59,7 +59,7 @@ async fn index(
 | 
			
		||||
    context.insert("boats", &boats);
 | 
			
		||||
    context.insert(
 | 
			
		||||
        "loggedin_user",
 | 
			
		||||
        &UserWithRoles::from_user(user.user, db).await,
 | 
			
		||||
        &UserWithRoles::from_user(user.into(), db).await,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    Template::render("boatdamages", context.into_json())
 | 
			
		||||
@@ -76,13 +76,14 @@ pub struct FormBoatDamageToAdd<'r> {
 | 
			
		||||
async fn create<'r>(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    data: Form<FormBoatDamageToAdd<'r>>,
 | 
			
		||||
    user: NonGuestUser,
 | 
			
		||||
    user: DonauLinzUser,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
    let user: User = user.into();
 | 
			
		||||
    let boatdamage_to_add = BoatDamageToAdd {
 | 
			
		||||
        boat_id: data.boat_id,
 | 
			
		||||
        desc: data.desc,
 | 
			
		||||
        lock_boat: data.lock_boat,
 | 
			
		||||
        user_id_created: user.user.id as i32,
 | 
			
		||||
        user_id_created: user.id as i32,
 | 
			
		||||
    };
 | 
			
		||||
    match BoatDamage::create(db, boatdamage_to_add).await {
 | 
			
		||||
        Ok(_) => Flash::success(
 | 
			
		||||
 
 | 
			
		||||
@@ -391,7 +391,7 @@ mod test {
 | 
			
		||||
            .body("name=cox&password=cox"); // Add the form data to the request body;
 | 
			
		||||
        login.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        let req = client.get("/join/1");
 | 
			
		||||
        let req = client.get("/planned/join/1");
 | 
			
		||||
        let _ = req.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        let req = client.get("/cox/join/1");
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ use crate::model::{
 | 
			
		||||
        LogbookUpdateError,
 | 
			
		||||
    },
 | 
			
		||||
    logtype::LogType,
 | 
			
		||||
    user::{NonGuestUser, User, UserWithRoles, UserWithWaterStatus},
 | 
			
		||||
    user::{DonauLinzUser, User, UserWithRoles, UserWithWaterStatus},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub struct KioskCookie(String);
 | 
			
		||||
@@ -44,9 +44,9 @@ impl<'r> FromRequest<'r> for KioskCookie {
 | 
			
		||||
async fn index(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    flash: Option<FlashMessage<'_>>,
 | 
			
		||||
    user: NonGuestUser,
 | 
			
		||||
    user: DonauLinzUser,
 | 
			
		||||
) -> Template {
 | 
			
		||||
    let boats = Boat::for_user(db, &user.user).await;
 | 
			
		||||
    let boats = Boat::for_user(db, &user).await;
 | 
			
		||||
 | 
			
		||||
    let coxes: Vec<UserWithWaterStatus> = futures::future::join_all(
 | 
			
		||||
        User::cox(db)
 | 
			
		||||
@@ -78,7 +78,7 @@ async fn index(
 | 
			
		||||
    context.insert("logtypes", &logtypes);
 | 
			
		||||
    context.insert(
 | 
			
		||||
        "loggedin_user",
 | 
			
		||||
        &UserWithRoles::from_user(user.user, db).await,
 | 
			
		||||
        &UserWithRoles::from_user(user.into(), db).await,
 | 
			
		||||
    );
 | 
			
		||||
    context.insert("on_water", &on_water);
 | 
			
		||||
    context.insert("distances", &distances);
 | 
			
		||||
@@ -87,12 +87,12 @@ async fn index(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/show", rank = 2)]
 | 
			
		||||
async fn show(db: &State<SqlitePool>, user: NonGuestUser) -> Template {
 | 
			
		||||
async fn show(db: &State<SqlitePool>, user: DonauLinzUser) -> Template {
 | 
			
		||||
    let logs = Logbook::completed(db).await;
 | 
			
		||||
 | 
			
		||||
    Template::render(
 | 
			
		||||
        "log.completed",
 | 
			
		||||
        context!(logs, loggedin_user: &UserWithRoles::from_user(user.user, db).await),
 | 
			
		||||
        context!(logs, loggedin_user: &UserWithRoles::from_user(user.into(), db).await),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -166,12 +166,12 @@ async fn kiosk(
 | 
			
		||||
async fn create_logbook(
 | 
			
		||||
    db: &SqlitePool,
 | 
			
		||||
    data: Form<LogToAdd>,
 | 
			
		||||
    user: &NonGuestUser,
 | 
			
		||||
    user: &DonauLinzUser,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
    match Logbook::create(
 | 
			
		||||
        db,
 | 
			
		||||
        data.into_inner(),
 | 
			
		||||
        &user.user
 | 
			
		||||
        &user
 | 
			
		||||
    )
 | 
			
		||||
    .await
 | 
			
		||||
    {
 | 
			
		||||
@@ -197,14 +197,11 @@ async fn create_logbook(
 | 
			
		||||
async fn create(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    data: Form<LogToAdd>,
 | 
			
		||||
    user: NonGuestUser,
 | 
			
		||||
    user: DonauLinzUser,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
    Log::create(
 | 
			
		||||
        db,
 | 
			
		||||
        format!(
 | 
			
		||||
            "User {} tries to create log entry={:?}",
 | 
			
		||||
            user.user.name, data
 | 
			
		||||
        ),
 | 
			
		||||
        format!("User {} tries to create log entry={:?}", &user.name, data),
 | 
			
		||||
    )
 | 
			
		||||
    .await;
 | 
			
		||||
 | 
			
		||||
@@ -238,14 +235,14 @@ async fn create_kiosk(
 | 
			
		||||
    )
 | 
			
		||||
    .await;
 | 
			
		||||
 | 
			
		||||
    create_logbook(db, data, &NonGuestUser { user: creator }).await //TODO: fixme
 | 
			
		||||
    create_logbook(db, data, &DonauLinzUser(creator)).await //TODO: fixme
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn home_logbook(
 | 
			
		||||
    db: &SqlitePool,
 | 
			
		||||
    data: Form<LogToFinalize>,
 | 
			
		||||
    logbook_id: i32,
 | 
			
		||||
    user: &NonGuestUser,
 | 
			
		||||
    user: &DonauLinzUser,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
    let logbook: Option<Logbook> = Logbook::find_by_id(db, logbook_id).await;
 | 
			
		||||
    let Some(logbook) = logbook else {
 | 
			
		||||
@@ -255,7 +252,7 @@ async fn home_logbook(
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match logbook.home(db, &user.user, data.into_inner()).await {
 | 
			
		||||
    match logbook.home(db, &user, data.into_inner()).await {
 | 
			
		||||
        Ok(_) => Flash::success(Redirect::to("/log"), "Ausfahrt korrekt eingetragen"),
 | 
			
		||||
        Err(LogbookUpdateError::TooManyRowers(expected, actual)) => Flash::error(Redirect::to("/log"), format!("Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)")),
 | 
			
		||||
        Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday) => Flash::error(Redirect::to("/log"), "Nur Ausfahrten, die heute enden dürfen eingetragen werden. Für einen Nachtrag schreibe alle Daten Philipp (Tel. nr. siehe Signal oder it@rudernlinz.at)."),
 | 
			
		||||
@@ -285,11 +282,11 @@ async fn home_kiosk(
 | 
			
		||||
        db,
 | 
			
		||||
        data,
 | 
			
		||||
        logbook_id,
 | 
			
		||||
        &NonGuestUser {
 | 
			
		||||
            user: User::find_by_id(db, logbook.shipmaster as i32)
 | 
			
		||||
        &DonauLinzUser(
 | 
			
		||||
            User::find_by_id(db, logbook.shipmaster as i32)
 | 
			
		||||
                .await
 | 
			
		||||
                .unwrap(), //TODO: fixme
 | 
			
		||||
        },
 | 
			
		||||
                .unwrap(),
 | 
			
		||||
        ), //TODO: fixme
 | 
			
		||||
    )
 | 
			
		||||
    .await
 | 
			
		||||
}
 | 
			
		||||
@@ -299,13 +296,13 @@ async fn home(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    data: Form<LogToFinalize>,
 | 
			
		||||
    logbook_id: i32,
 | 
			
		||||
    user: NonGuestUser,
 | 
			
		||||
    user: DonauLinzUser,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
    Log::create(
 | 
			
		||||
        db,
 | 
			
		||||
        format!(
 | 
			
		||||
            "User {} tries to finish log entry {logbook_id} {data:?}",
 | 
			
		||||
            user.user.name
 | 
			
		||||
            &user.name
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    .await;
 | 
			
		||||
@@ -314,12 +311,12 @@ async fn home(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/<logbook_id>/delete", rank = 2)]
 | 
			
		||||
async fn delete(db: &State<SqlitePool>, logbook_id: i32, user: User) -> Flash<Redirect> {
 | 
			
		||||
async fn delete(db: &State<SqlitePool>, logbook_id: i32, user: DonauLinzUser) -> Flash<Redirect> {
 | 
			
		||||
    let logbook = Logbook::find_by_id(db, logbook_id).await;
 | 
			
		||||
    if let Some(logbook) = logbook {
 | 
			
		||||
        Log::create(
 | 
			
		||||
            db,
 | 
			
		||||
            format!("User {} tries to delete log entry {logbook_id}", user.name),
 | 
			
		||||
            format!("User {} tries to delete log entry {logbook_id}", &user.name),
 | 
			
		||||
        )
 | 
			
		||||
        .await;
 | 
			
		||||
        match logbook.delete(db, &user).await {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										262
									
								
								src/tera/mod.rs
									
									
									
									
									
								
							
							
						
						
									
										262
									
								
								src/tera/mod.rs
									
									
									
									
									
								
							@@ -8,17 +8,12 @@ use rocket::{
 | 
			
		||||
    response::{Flash, Redirect},
 | 
			
		||||
    routes, Build, FromForm, Rocket, State,
 | 
			
		||||
};
 | 
			
		||||
use rocket_dyn_templates::{tera::Context, Template};
 | 
			
		||||
use rocket_dyn_templates::Template;
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use sqlx::SqlitePool;
 | 
			
		||||
use tera::Context;
 | 
			
		||||
 | 
			
		||||
use crate::model::{
 | 
			
		||||
    log::Log,
 | 
			
		||||
    tripdetails::TripDetails,
 | 
			
		||||
    triptype::TripType,
 | 
			
		||||
    user::{User, UserWithRoles},
 | 
			
		||||
    usertrip::{UserTrip, UserTripDeleteError, UserTripError},
 | 
			
		||||
};
 | 
			
		||||
use crate::model::user::{User, UserWithRoles};
 | 
			
		||||
 | 
			
		||||
pub(crate) mod admin;
 | 
			
		||||
mod auth;
 | 
			
		||||
@@ -27,6 +22,7 @@ mod cox;
 | 
			
		||||
mod ergo;
 | 
			
		||||
mod log;
 | 
			
		||||
mod misc;
 | 
			
		||||
mod planned;
 | 
			
		||||
mod stat;
 | 
			
		||||
 | 
			
		||||
#[derive(FromForm, Debug)]
 | 
			
		||||
@@ -35,6 +31,16 @@ struct LoginForm<'r> {
 | 
			
		||||
    password: &'r str,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/")]
 | 
			
		||||
async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_>>) -> Template {
 | 
			
		||||
    let mut context = Context::new();
 | 
			
		||||
    if let Some(msg) = flash {
 | 
			
		||||
        context.insert("flash", &msg.into_inner());
 | 
			
		||||
    }
 | 
			
		||||
    context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await);
 | 
			
		||||
    Template::render("index", context.into_json())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[post("/", data = "<login>")]
 | 
			
		||||
async fn wikiauth(db: &State<SqlitePool>, login: Form<LoginForm<'_>>) -> String {
 | 
			
		||||
    match User::login(db, login.name, login.password).await {
 | 
			
		||||
@@ -43,164 +49,16 @@ async fn wikiauth(db: &State<SqlitePool>, login: Form<LoginForm<'_>>) -> String
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/")]
 | 
			
		||||
async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_>>) -> Template {
 | 
			
		||||
    let mut context = Context::new();
 | 
			
		||||
 | 
			
		||||
    if user.has_role(db, "cox").await || user.has_role(db, "admin").await {
 | 
			
		||||
        let triptypes = TripType::all(db).await;
 | 
			
		||||
        context.insert("trip_types", &triptypes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let days = user.get_days(db).await;
 | 
			
		||||
 | 
			
		||||
    if let Some(msg) = flash {
 | 
			
		||||
        context.insert("flash", &msg.into_inner());
 | 
			
		||||
    }
 | 
			
		||||
    context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await);
 | 
			
		||||
    context.insert("days", &days);
 | 
			
		||||
    Template::render("index", context.into_json())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/join/<trip_details_id>?<user_note>")]
 | 
			
		||||
async fn join(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    trip_details_id: i64,
 | 
			
		||||
    user: User,
 | 
			
		||||
    user_note: Option<String>,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
    let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
 | 
			
		||||
        return Flash::error(Redirect::to("/"), "Trip_details do not exist.");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match UserTrip::create(db, &user, &trip_details, user_note).await {
 | 
			
		||||
        Ok(_) => {
 | 
			
		||||
            Log::create(
 | 
			
		||||
                db,
 | 
			
		||||
                format!(
 | 
			
		||||
                    "User {} registered for trip_details.id={}",
 | 
			
		||||
                    user.name, trip_details_id
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
            Flash::success(Redirect::to("/"), "Erfolgreich angemeldet!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripError::EventAlreadyFull) => {
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Event bereits ausgebucht!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripError::AlreadyRegistered) => {
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Du nimmst bereits teil!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripError::AlreadyRegisteredAsCox) => {
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Du hilfst bereits als Steuerperson aus!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripError::CantRegisterAtOwnEvent) => Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            "Du kannst bei einer selbst ausgeschriebenen Fahrt nicht mitrudern ;)",
 | 
			
		||||
        ),
 | 
			
		||||
        Err(UserTripError::GuestNotAllowedForThisEvent) => Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            "Bei dieser Ausfahrt können leider keine Gäste mitfahren.",
 | 
			
		||||
        ),
 | 
			
		||||
        Err(UserTripError::NotAllowedToAddGuest) => Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            "Du darfst keine Gäste hinzufügen.",
 | 
			
		||||
        ),
 | 
			
		||||
        Err(UserTripError::DetailsLocked) => Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.",
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/remove/<trip_details_id>/<name>")]
 | 
			
		||||
async fn remove_guest(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    trip_details_id: i64,
 | 
			
		||||
    user: User,
 | 
			
		||||
    name: String,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
    let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
 | 
			
		||||
        return Flash::error(Redirect::to("/"), "TripDetailsId does not exist");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match UserTrip::delete(db, &user, &trip_details, Some(name)).await {
 | 
			
		||||
        Ok(_) => {
 | 
			
		||||
            Log::create(
 | 
			
		||||
                db,
 | 
			
		||||
                format!(
 | 
			
		||||
                    "User {} unregistered for trip_details.id={}",
 | 
			
		||||
                    user.name, trip_details_id
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
            Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripDeleteError::DetailsLocked) => {
 | 
			
		||||
            Log::create(
 | 
			
		||||
                db,
 | 
			
		||||
                format!(
 | 
			
		||||
                    "User {} tried to unregister for locked trip_details.id={}",
 | 
			
		||||
                    user.name, trip_details_id
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripDeleteError::GuestNotParticipating) => {
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Gast nicht angemeldet.")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripDeleteError::NotAllowedToDeleteGuest) => Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            "Keine Berechtigung um den Gast zu entfernen.",
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/remove/<trip_details_id>")]
 | 
			
		||||
async fn remove(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash<Redirect> {
 | 
			
		||||
    let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
 | 
			
		||||
        return Flash::error(Redirect::to("/"), "TripDetailsId does not exist");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match UserTrip::delete(db, &user, &trip_details, None).await {
 | 
			
		||||
        Ok(_) => {
 | 
			
		||||
            Log::create(
 | 
			
		||||
                db,
 | 
			
		||||
                format!(
 | 
			
		||||
                    "User {} unregistered for trip_details.id={}",
 | 
			
		||||
                    user.name, trip_details_id
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
            Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripDeleteError::DetailsLocked) => {
 | 
			
		||||
            Log::create(
 | 
			
		||||
                db,
 | 
			
		||||
                format!(
 | 
			
		||||
                    "User {} tried to unregister for locked trip_details.id={}",
 | 
			
		||||
                    user.name, trip_details_id
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.")
 | 
			
		||||
        }
 | 
			
		||||
        Err(_) => {
 | 
			
		||||
            panic!("Not possible to be here");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[catch(401)] //unauthorized
 | 
			
		||||
#[catch(401)] //Unauthorized
 | 
			
		||||
fn unauthorized_error() -> Redirect {
 | 
			
		||||
    Redirect::to("/auth")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[catch(403)] //forbidden
 | 
			
		||||
fn forbidden_error() -> Flash<Redirect> {
 | 
			
		||||
    Flash::error(Redirect::to("/"), "Keine Berechtigung für diese Aktion. Wenn du der Meinung bist, dass du das machen darfst, melde dich bitte bei it@rudernlinz.at.")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize)]
 | 
			
		||||
#[serde(crate = "rocket::serde")]
 | 
			
		||||
pub struct Config {
 | 
			
		||||
@@ -210,10 +68,11 @@ pub struct Config {
 | 
			
		||||
 | 
			
		||||
pub fn config(rocket: Rocket<Build>) -> Rocket<Build> {
 | 
			
		||||
    rocket
 | 
			
		||||
        .mount("/", routes![index, join, remove, remove_guest])
 | 
			
		||||
        .mount("/", routes![index])
 | 
			
		||||
        .mount("/auth", auth::routes())
 | 
			
		||||
        .mount("/wikiauth", routes![wikiauth])
 | 
			
		||||
        .mount("/log", log::routes())
 | 
			
		||||
        .mount("/planned", planned::routes())
 | 
			
		||||
        .mount("/ergo", ergo::routes())
 | 
			
		||||
        .mount("/stat", stat::routes())
 | 
			
		||||
        .mount("/boatdamage", boatdamage::routes())
 | 
			
		||||
@@ -221,7 +80,7 @@ pub fn config(rocket: Rocket<Build>) -> Rocket<Build> {
 | 
			
		||||
        .mount("/admin", admin::routes())
 | 
			
		||||
        .mount("/", misc::routes())
 | 
			
		||||
        .mount("/public", FileServer::from("static/"))
 | 
			
		||||
        .register("/", catchers![unauthorized_error])
 | 
			
		||||
        .register("/", catchers![unauthorized_error, forbidden_error])
 | 
			
		||||
        .attach(Template::fairing())
 | 
			
		||||
        .attach(AdHoc::config::<Config>())
 | 
			
		||||
}
 | 
			
		||||
@@ -255,7 +114,11 @@ mod test {
 | 
			
		||||
 | 
			
		||||
        assert_eq!(response.status(), Status::Ok);
 | 
			
		||||
 | 
			
		||||
        assert!(response.into_string().await.unwrap().contains("Ausfahrten"));
 | 
			
		||||
        assert!(response
 | 
			
		||||
            .into_string()
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .contains("Ruderassistent"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[sqlx::test]
 | 
			
		||||
@@ -274,75 +137,6 @@ mod test {
 | 
			
		||||
        assert_eq!(response.headers().get("Location").next(), Some("/auth"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[sqlx::test]
 | 
			
		||||
    fn test_join_and_remove() {
 | 
			
		||||
        let db = testdb!();
 | 
			
		||||
 | 
			
		||||
        let rocket = rocket::build().manage(db.clone());
 | 
			
		||||
        let rocket = crate::tera::config(rocket);
 | 
			
		||||
 | 
			
		||||
        let client = Client::tracked(rocket).await.unwrap();
 | 
			
		||||
        let login = client
 | 
			
		||||
            .post("/auth")
 | 
			
		||||
            .header(ContentType::Form) // Set the content type to form
 | 
			
		||||
            .body("name=rower&password=rower"); // Add the form data to the request body;
 | 
			
		||||
        login.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        let req = client.get("/join/1");
 | 
			
		||||
        let response = req.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        assert_eq!(response.status(), Status::SeeOther);
 | 
			
		||||
        assert_eq!(response.headers().get("Location").next(), Some("/"));
 | 
			
		||||
 | 
			
		||||
        let flash_cookie = response
 | 
			
		||||
            .cookies()
 | 
			
		||||
            .get("_flash")
 | 
			
		||||
            .expect("Expected flash cookie");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(flash_cookie.value(), "7:successErfolgreich angemeldet!");
 | 
			
		||||
 | 
			
		||||
        let req = client.get("/remove/1");
 | 
			
		||||
        let response = req.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        assert_eq!(response.status(), Status::SeeOther);
 | 
			
		||||
        assert_eq!(response.headers().get("Location").next(), Some("/"));
 | 
			
		||||
 | 
			
		||||
        let flash_cookie = response
 | 
			
		||||
            .cookies()
 | 
			
		||||
            .get("_flash")
 | 
			
		||||
            .expect("Expected flash cookie");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(flash_cookie.value(), "7:successErfolgreich abgemeldet!");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[sqlx::test]
 | 
			
		||||
    fn test_join_invalid_event() {
 | 
			
		||||
        let db = testdb!();
 | 
			
		||||
 | 
			
		||||
        let rocket = rocket::build().manage(db.clone());
 | 
			
		||||
        let rocket = crate::tera::config(rocket);
 | 
			
		||||
 | 
			
		||||
        let client = Client::tracked(rocket).await.unwrap();
 | 
			
		||||
        let login = client
 | 
			
		||||
            .post("/auth")
 | 
			
		||||
            .header(ContentType::Form) // Set the content type to form
 | 
			
		||||
            .body("name=rower&password=rower"); // Add the form data to the request body;
 | 
			
		||||
        login.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        let req = client.get("/join/9999");
 | 
			
		||||
        let response = req.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        assert_eq!(response.status(), Status::SeeOther);
 | 
			
		||||
        assert_eq!(response.headers().get("Location").next(), Some("/"));
 | 
			
		||||
 | 
			
		||||
        let flash_cookie = response
 | 
			
		||||
            .cookies()
 | 
			
		||||
            .get("_flash")
 | 
			
		||||
            .expect("Expected flash cookie");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(flash_cookie.value(), "5:errorTrip_details do not exist.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[sqlx::test]
 | 
			
		||||
    fn test_public() {
 | 
			
		||||
        let db = testdb!();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										273
									
								
								src/tera/planned.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								src/tera/planned.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,273 @@
 | 
			
		||||
use rocket::{
 | 
			
		||||
    get,
 | 
			
		||||
    request::FlashMessage,
 | 
			
		||||
    response::{Flash, Redirect},
 | 
			
		||||
    routes, Route, State,
 | 
			
		||||
};
 | 
			
		||||
use rocket_dyn_templates::Template;
 | 
			
		||||
use sqlx::SqlitePool;
 | 
			
		||||
use tera::Context;
 | 
			
		||||
 | 
			
		||||
use crate::model::{
 | 
			
		||||
    log::Log,
 | 
			
		||||
    tripdetails::TripDetails,
 | 
			
		||||
    triptype::TripType,
 | 
			
		||||
    user::{AllowedForPlannedTripsUser, User, UserWithRoles},
 | 
			
		||||
    usertrip::{UserTrip, UserTripDeleteError, UserTripError},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[get("/")]
 | 
			
		||||
async fn index(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    user: AllowedForPlannedTripsUser,
 | 
			
		||||
    flash: Option<FlashMessage<'_>>,
 | 
			
		||||
) -> Template {
 | 
			
		||||
    let user: User = user.into();
 | 
			
		||||
 | 
			
		||||
    let mut context = Context::new();
 | 
			
		||||
 | 
			
		||||
    if user.has_role(db, "cox").await || user.has_role(db, "admin").await {
 | 
			
		||||
        let triptypes = TripType::all(db).await;
 | 
			
		||||
        context.insert("trip_types", &triptypes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let days = user.get_days(db).await;
 | 
			
		||||
 | 
			
		||||
    if let Some(msg) = flash {
 | 
			
		||||
        context.insert("flash", &msg.into_inner());
 | 
			
		||||
    }
 | 
			
		||||
    context.insert(
 | 
			
		||||
        "loggedin_user",
 | 
			
		||||
        &UserWithRoles::from_user(user.into(), db).await,
 | 
			
		||||
    );
 | 
			
		||||
    context.insert("days", &days);
 | 
			
		||||
    Template::render("planned", context.into_json())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/join/<trip_details_id>?<user_note>")]
 | 
			
		||||
async fn join(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    trip_details_id: i64,
 | 
			
		||||
    user: AllowedForPlannedTripsUser,
 | 
			
		||||
    user_note: Option<String>,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
    let user: User = user.into();
 | 
			
		||||
 | 
			
		||||
    let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
 | 
			
		||||
        return Flash::error(Redirect::to("/"), "Trip_details do not exist.");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match UserTrip::create(db, &user, &trip_details, user_note).await {
 | 
			
		||||
        Ok(_) => {
 | 
			
		||||
            Log::create(
 | 
			
		||||
                db,
 | 
			
		||||
                format!(
 | 
			
		||||
                    "User {} registered for trip_details.id={}",
 | 
			
		||||
                    user.name, trip_details_id
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
            Flash::success(Redirect::to("/"), "Erfolgreich angemeldet!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripError::EventAlreadyFull) => {
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Event bereits ausgebucht!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripError::AlreadyRegistered) => {
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Du nimmst bereits teil!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripError::AlreadyRegisteredAsCox) => {
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Du hilfst bereits als Steuerperson aus!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripError::CantRegisterAtOwnEvent) => Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            "Du kannst bei einer selbst ausgeschriebenen Fahrt nicht mitrudern ;)",
 | 
			
		||||
        ),
 | 
			
		||||
        Err(UserTripError::GuestNotAllowedForThisEvent) => Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            "Bei dieser Ausfahrt können leider keine Gäste mitfahren.",
 | 
			
		||||
        ),
 | 
			
		||||
        Err(UserTripError::NotAllowedToAddGuest) => Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            "Du darfst keine Gäste hinzufügen.",
 | 
			
		||||
        ),
 | 
			
		||||
        Err(UserTripError::DetailsLocked) => Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.",
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/remove/<trip_details_id>/<name>")]
 | 
			
		||||
async fn remove_guest(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    trip_details_id: i64,
 | 
			
		||||
    user: AllowedForPlannedTripsUser,
 | 
			
		||||
    name: String,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
    let user: User = user.into();
 | 
			
		||||
 | 
			
		||||
    let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
 | 
			
		||||
        return Flash::error(Redirect::to("/"), "TripDetailsId does not exist");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match UserTrip::delete(db, &user, &trip_details, Some(name)).await {
 | 
			
		||||
        Ok(_) => {
 | 
			
		||||
            Log::create(
 | 
			
		||||
                db,
 | 
			
		||||
                format!(
 | 
			
		||||
                    "User {} unregistered for trip_details.id={}",
 | 
			
		||||
                    user.name, trip_details_id
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
            Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripDeleteError::DetailsLocked) => {
 | 
			
		||||
            Log::create(
 | 
			
		||||
                db,
 | 
			
		||||
                format!(
 | 
			
		||||
                    "User {} tried to unregister for locked trip_details.id={}",
 | 
			
		||||
                    user.name, trip_details_id
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripDeleteError::GuestNotParticipating) => {
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Gast nicht angemeldet.")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripDeleteError::NotAllowedToDeleteGuest) => Flash::error(
 | 
			
		||||
            Redirect::to("/"),
 | 
			
		||||
            "Keine Berechtigung um den Gast zu entfernen.",
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/remove/<trip_details_id>")]
 | 
			
		||||
async fn remove(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    trip_details_id: i64,
 | 
			
		||||
    user: AllowedForPlannedTripsUser,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
    let user: User = user.into();
 | 
			
		||||
 | 
			
		||||
    let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
 | 
			
		||||
        return Flash::error(Redirect::to("/"), "TripDetailsId does not exist");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match UserTrip::delete(db, &user, &trip_details, None).await {
 | 
			
		||||
        Ok(_) => {
 | 
			
		||||
            Log::create(
 | 
			
		||||
                db,
 | 
			
		||||
                format!(
 | 
			
		||||
                    "User {} unregistered for trip_details.id={}",
 | 
			
		||||
                    user.name, trip_details_id
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
            Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!")
 | 
			
		||||
        }
 | 
			
		||||
        Err(UserTripDeleteError::DetailsLocked) => {
 | 
			
		||||
            Log::create(
 | 
			
		||||
                db,
 | 
			
		||||
                format!(
 | 
			
		||||
                    "User {} tried to unregister for locked trip_details.id={}",
 | 
			
		||||
                    user.name, trip_details_id
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
            Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.")
 | 
			
		||||
        }
 | 
			
		||||
        Err(_) => {
 | 
			
		||||
            panic!("Not possible to be here");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn routes() -> Vec<Route> {
 | 
			
		||||
    routes![index, join, remove, remove_guest]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use rocket::{
 | 
			
		||||
        http::{ContentType, Status},
 | 
			
		||||
        local::asynchronous::Client,
 | 
			
		||||
    };
 | 
			
		||||
    use sqlx::SqlitePool;
 | 
			
		||||
 | 
			
		||||
    use crate::testdb;
 | 
			
		||||
 | 
			
		||||
    #[sqlx::test]
 | 
			
		||||
    fn test_join_and_remove() {
 | 
			
		||||
        let db = testdb!();
 | 
			
		||||
 | 
			
		||||
        let rocket = rocket::build().manage(db.clone());
 | 
			
		||||
        let rocket = crate::tera::config(rocket);
 | 
			
		||||
 | 
			
		||||
        let client = Client::tracked(rocket).await.unwrap();
 | 
			
		||||
        let login = client
 | 
			
		||||
            .post("/auth")
 | 
			
		||||
            .header(ContentType::Form) // Set the content type to form
 | 
			
		||||
            .body("name=rower&password=rower"); // Add the form data to the request body;
 | 
			
		||||
        login.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        let req = client.get("/planned/join/1");
 | 
			
		||||
        let response = req.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        assert_eq!(response.status(), Status::SeeOther);
 | 
			
		||||
        assert_eq!(response.headers().get("Location").next(), Some("/"));
 | 
			
		||||
 | 
			
		||||
        let flash_cookie = response
 | 
			
		||||
            .cookies()
 | 
			
		||||
            .get("_flash")
 | 
			
		||||
            .expect("Expected flash cookie");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(flash_cookie.value(), "7:successErfolgreich angemeldet!");
 | 
			
		||||
 | 
			
		||||
        let req = client.get("/planned/remove/1");
 | 
			
		||||
        let response = req.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        assert_eq!(response.status(), Status::SeeOther);
 | 
			
		||||
        assert_eq!(response.headers().get("Location").next(), Some("/"));
 | 
			
		||||
 | 
			
		||||
        let flash_cookie = response
 | 
			
		||||
            .cookies()
 | 
			
		||||
            .get("_flash")
 | 
			
		||||
            .expect("Expected flash cookie");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(flash_cookie.value(), "7:successErfolgreich abgemeldet!");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[sqlx::test]
 | 
			
		||||
    fn test_join_invalid_event() {
 | 
			
		||||
        let db = testdb!();
 | 
			
		||||
 | 
			
		||||
        let rocket = rocket::build().manage(db.clone());
 | 
			
		||||
        let rocket = crate::tera::config(rocket);
 | 
			
		||||
 | 
			
		||||
        let client = Client::tracked(rocket).await.unwrap();
 | 
			
		||||
        let login = client
 | 
			
		||||
            .post("/auth")
 | 
			
		||||
            .header(ContentType::Form) // Set the content type to form
 | 
			
		||||
            .body("name=rower&password=rower"); // Add the form data to the request body;
 | 
			
		||||
        login.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        let req = client.get("/planned/join/9999");
 | 
			
		||||
        let response = req.dispatch().await;
 | 
			
		||||
 | 
			
		||||
        assert_eq!(response.status(), Status::SeeOther);
 | 
			
		||||
        assert_eq!(response.headers().get("Location").next(), Some("/"));
 | 
			
		||||
 | 
			
		||||
        let flash_cookie = response
 | 
			
		||||
            .cookies()
 | 
			
		||||
            .get("_flash")
 | 
			
		||||
            .expect("Expected flash cookie");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(flash_cookie.value(), "5:errorTrip_details do not exist.");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,19 +4,19 @@ use sqlx::SqlitePool;
 | 
			
		||||
 | 
			
		||||
use crate::model::{
 | 
			
		||||
    stat::{self, Stat},
 | 
			
		||||
    user::{NonGuestUser, UserWithRoles},
 | 
			
		||||
    user::{DonauLinzUser, UserWithRoles},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::log::KioskCookie;
 | 
			
		||||
 | 
			
		||||
#[get("/boats?<year>", rank = 2)]
 | 
			
		||||
async fn index_boat(db: &State<SqlitePool>, user: NonGuestUser, year: Option<i32>) -> Template {
 | 
			
		||||
async fn index_boat(db: &State<SqlitePool>, user: DonauLinzUser, year: Option<i32>) -> Template {
 | 
			
		||||
    let stat = Stat::boats(db, year).await;
 | 
			
		||||
    let kiosk = false;
 | 
			
		||||
 | 
			
		||||
    Template::render(
 | 
			
		||||
        "stat.boats",
 | 
			
		||||
        context!(loggedin_user: &UserWithRoles::from_user(user.user, db).await, stat, kiosk),
 | 
			
		||||
        context!(loggedin_user: &UserWithRoles::from_user(user.into(), db).await, stat, kiosk),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -33,15 +33,15 @@ async fn index_boat_kiosk(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/?<year>", rank = 2)]
 | 
			
		||||
async fn index(db: &State<SqlitePool>, user: NonGuestUser, year: Option<i32>) -> Template {
 | 
			
		||||
async fn index(db: &State<SqlitePool>, user: DonauLinzUser, year: Option<i32>) -> Template {
 | 
			
		||||
    let stat = Stat::people(db, year).await;
 | 
			
		||||
    let guest_km = Stat::guest(db, year).await;
 | 
			
		||||
    let personal = stat::get_personal(db, &user.user).await;
 | 
			
		||||
    let personal = stat::get_personal(db, &user).await;
 | 
			
		||||
    let kiosk = false;
 | 
			
		||||
 | 
			
		||||
    Template::render(
 | 
			
		||||
        "stat.people",
 | 
			
		||||
        context!(loggedin_user: &UserWithRoles::from_user(user.user, db).await, stat, personal, kiosk, guest_km),
 | 
			
		||||
        context!(loggedin_user: &UserWithRoles::from_user(user.into(), db).await, stat, personal, kiosk, guest_km),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
        </a>
 | 
			
		||||
      </div>
 | 
			
		||||
  
 | 
			
		||||
      <div>
 | 
			
		||||
      <div><!--
 | 
			
		||||
        <a
 | 
			
		||||
          href="https://wiki.rudernlinz.at/ruderassistent#faq"
 | 
			
		||||
          target="_blank"
 | 
			
		||||
@@ -121,7 +121,7 @@
 | 
			
		||||
          </svg>
 | 
			
		||||
          <span class="sr-only">Userverwaltung</span>
 | 
			
		||||
        </a>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% endif %}-->
 | 
			
		||||
        <a
 | 
			
		||||
          href="/auth/logout"
 | 
			
		||||
          class="inline-flex justify-center rounded-md bg-gray-200 ml-1 px-3 py-2 text-sm font-semibold text-primary-950 hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer"
 | 
			
		||||
 
 | 
			
		||||
@@ -3,292 +3,86 @@
 | 
			
		||||
{% extends "base" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
	<div class="max-w-screen-xl w-full grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
 | 
			
		||||
	<div class="max-w-screen-lg w-full">
 | 
			
		||||
		{% if flash %}
 | 
			
		||||
			{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}
 | 
			
		||||
		{% endif %}
 | 
			
		||||
 | 
			
		||||
		<h1 class="h1 sm:col-span-2 lg:col-span-3">Ausfahrten</h1>
 | 
			
		||||
 | 
			
		||||
		{% include "includes/buttons" %}
 | 
			
		||||
 | 
			
		||||
		{% for day in days %}
 | 
			
		||||
			{% set amount_trips = day.planned_events | length + day.trips | length %}
 | 
			
		||||
			{% set_global day_cox_needed = false %}
 | 
			
		||||
			{% if day.planned_events | length > 0 %}
 | 
			
		||||
				{% for planned_event in day.planned_events %}
 | 
			
		||||
					{% if planned_event.cox_needed %}
 | 
			
		||||
						{% set_global day_cox_needed = true %}
 | 
			
		||||
					{% endif %}
 | 
			
		||||
				{% endfor %}
 | 
			
		||||
			{% endif %}
 | 
			
		||||
 | 
			
		||||
			<div class="bg-white dark:bg-primary-900 rounded-md flex justify-between flex-col shadow reset-js" style="min-height: 10rem;" data-trips="{{ amount_trips }}" data-month="{{ day.day| date(format='%m') }}" data-coxneeded="{{ day_cox_needed }}">
 | 
			
		||||
				<div>
 | 
			
		||||
					<h2 class="font-bold uppercase tracking-wide text-center rounded-t-md  {% if day.is_pinned %} text-white bg-primary-950 {% else %} text-primary-950 dark:text-white bg-gray-200 dark:bg-primary-950 bg-opacity-80 {% endif %} text-lg px-3 py-3 ">{{ day.day| date(format="%d.%m.%Y") }}
 | 
			
		||||
						<small class="inline-block ml-1 text-xs {% if day.is_pinned %} text-gray-200 {% else %} text-gray-500 dark:text-gray-100 {% endif %}">{{ day.day | date(format="%A", locale="de_AT") }}</small>
 | 
			
		||||
					</h2>
 | 
			
		||||
 | 
			
		||||
					{% if day.planned_events | length > 0 or  day.trips | length > 0 %}
 | 
			
		||||
						<div
 | 
			
		||||
							class="grid grid-cols-1 gap-3 mb-3">
 | 
			
		||||
 | 
			
		||||
							{# --- START Events --- #}
 | 
			
		||||
							{% if day.planned_events | length > 0 %}
 | 
			
		||||
								{% for planned_event in day.planned_events | sort(attribute="planned_starting_time") %}
 | 
			
		||||
									{% set amount_cur_cox = planned_event.cox | length %}
 | 
			
		||||
									{% set amount_cox_missing = planned_event.planned_amount_cox - amount_cur_cox %}
 | 
			
		||||
									<div class="pt-2 px-3 border-t border-gray-200" style="order: {{ planned_event.planned_starting_time | replace(from=":", to="") }}">
 | 
			
		||||
										<div class="flex justify-between items-center">
 | 
			
		||||
											<div class="mr-1">
 | 
			
		||||
												<strong class="text-primary-900 dark:text-white">
 | 
			
		||||
													{{ planned_event.planned_starting_time }}
 | 
			
		||||
													Uhr
 | 
			
		||||
												</strong>
 | 
			
		||||
												<small class="text-gray-600 dark:text-gray-100">({{ planned_event.name }}{% if planned_event.trip_type %} - {{ planned_event.trip_type.icon | safe }} {{ planned_event.trip_type.name }}{% endif %})</small><br/>
 | 
			
		||||
 | 
			
		||||
												<a href="#" data-sidebar="true" data-trigger="sidebar" data-header="<strong>{{ planned_event.planned_starting_time }} Uhr</strong> ({{ planned_event.name }}){% if planned_event.trip_type %}<small class='block'>{{ planned_event.trip_type.desc }}</small>{% endif %}{% if planned_event.notes %}<small class='block'>{{ planned_event.notes }}</small>{% endif %}" data-body="#event{{ planned_event.trip_details_id }}" class="inline-block link-primary mr-3">
 | 
			
		||||
													Details
 | 
			
		||||
												</a>
 | 
			
		||||
											</div>
 | 
			
		||||
											<div
 | 
			
		||||
												class="text-right grid gap-2">
 | 
			
		||||
												{# --- START Row Buttons --- #}
 | 
			
		||||
												{% set_global cur_user_participates = false %}
 | 
			
		||||
												{% for rower in planned_event.rower%}
 | 
			
		||||
													{% if rower.name == loggedin_user.name %}
 | 
			
		||||
														{% set_global cur_user_participates = true %}
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endfor %}
 | 
			
		||||
												{% if cur_user_participates %}
 | 
			
		||||
													<a href="/remove/{{ planned_event.trip_details_id }}" class="btn btn-attention btn-fw">Abmelden</a>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{% if planned_event.max_people > planned_event.rower | length %}
 | 
			
		||||
													{% if cur_user_participates == false %}
 | 
			
		||||
														<a href="/join/{{ planned_event.trip_details_id }}" class="btn btn-primary btn-fw" {% if planned_event.trip_type %} onclick="return confirm('{{ planned_event.trip_type.question  }}');" {% endif %}>Mitrudern</a>
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END Row Buttons --- #}
 | 
			
		||||
 | 
			
		||||
												{# --- START Cox Buttons --- #}
 | 
			
		||||
												{% if "cox" in loggedin_user.roles %}
 | 
			
		||||
													{% set_global cur_user_participates = false %}
 | 
			
		||||
													{% for cox in planned_event.cox %}
 | 
			
		||||
														{% if cox.name == loggedin_user.name %}
 | 
			
		||||
															{% set_global cur_user_participates = true %}
 | 
			
		||||
														{% endif %}
 | 
			
		||||
													{% endfor %}
 | 
			
		||||
													{% if cur_user_participates %}
 | 
			
		||||
														<a href="/cox/remove/{{ planned_event.id }}" class="block btn btn-attention btn-fw">
 | 
			
		||||
															{% include "includes/cox-icon" %}
 | 
			
		||||
															Abmelden
 | 
			
		||||
														</a>
 | 
			
		||||
													{% else %}
 | 
			
		||||
														<a href="/cox/join/{{ planned_event.id }}" class="block btn {% if amount_cox_missing > 0 %} btn-dark {% else %} btn-gray {% endif %} btn-fw" {% if planned_event.trip_type %} onclick="return confirm('{{ planned_event.trip_type.question  }}');" {% endif %}>
 | 
			
		||||
															{% include "includes/cox-icon" %}
 | 
			
		||||
															Steuern
 | 
			
		||||
														</a>
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END Cox Buttons --- #}
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
 | 
			
		||||
										{# --- START Sidebar Content --- #}
 | 
			
		||||
										<div class="hidden">
 | 
			
		||||
											<div
 | 
			
		||||
												id="event{{ planned_event.trip_details_id }}">
 | 
			
		||||
												{# --- START List Coxes --- #}
 | 
			
		||||
												{% if planned_event.planned_amount_cox > 0 %}
 | 
			
		||||
													{% if amount_cox_missing > 0 %}
 | 
			
		||||
														{{ macros::box(participants=planned_event.cox, empty_seats=planned_event.planned_amount_cox - amount_cur_cox, header='Noch benötigte Steuerleute:', text='Keine Steuerleute angemeldet') }}
 | 
			
		||||
													{% else %}
 | 
			
		||||
														{{ macros::box(participants=planned_event.cox, empty_seats="", header='Genügend Steuerleute haben sich angemeldet :-)', text='Keine Steuerleute angemeldet') }}
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END List Coxes --- #}
 | 
			
		||||
 | 
			
		||||
												{# --- 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="admin" in loggedin_user.roles) }}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END List Rowers --- #}
 | 
			
		||||
 | 
			
		||||
													{% 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"/>
 | 
			
		||||
														</form>
 | 
			
		||||
													{% endif %}
 | 
			
		||||
    <h1 class="h1">Ruderassistent</h1>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
												{% if planned_event.allow_guests %}
 | 
			
		||||
													<div class="text-primary-900 bg-primary-50 text-center p-1 mb-4">Gäste willkommen!</div>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
    <div class="grid gap-3">
 | 
			
		||||
      <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" role="alert">
 | 
			
		||||
        <h2 class="h2">Allgemein</h2>
 | 
			
		||||
         <div class="text-sm p-3">
 | 
			
		||||
          <ul class="list-disc ms-2">
 | 
			
		||||
            <li class="py-1"><a href="https://rudernlinz.at/termin" target="_blank" class="link-primary">FAQ (extern)</a></li>
 | 
			
		||||
          </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
												{% if "admin" in loggedin_user.roles %}
 | 
			
		||||
    {% if loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %}
 | 
			
		||||
        <div class="grid gap-3">
 | 
			
		||||
          <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" role="alert">
 | 
			
		||||
            <h2 class="h2">Ergo</h2>
 | 
			
		||||
             <div class="text-sm p-3">
 | 
			
		||||
              <ul class="list-disc ms-2">
 | 
			
		||||
                <li class="py-1"><a href="/ergo" class="link-primary">Ergo</a></li>
 | 
			
		||||
              </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
													{# --- START Edit Form --- #}
 | 
			
		||||
													<div class="bg-gray-100 dark:bg-primary-900 p-3 mt-4 rounded-md">
 | 
			
		||||
														<h3 class="text-primary-950 dark:text-white font-bold uppercase tracking-wide mb-2">Ausfahrt bearbeiten</h3>
 | 
			
		||||
														<form action="/admin/planned-event" method="post" class="grid gap-3">
 | 
			
		||||
															<input type="hidden" name="_method" value="put"/>
 | 
			
		||||
															<input type="hidden" name="id" value="{{ planned_event.id }}"/>
 | 
			
		||||
															{{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=planned_event.max_people, min='0') }}
 | 
			
		||||
															{{ macros::input(label='Anzahl Steuerleute', name='planned_amount_cox', type='number', value=planned_event.planned_amount_cox, required=true, min='0') }}
 | 
			
		||||
															{{ macros::checkbox(label='Immer anzeigen', name='always_show', id=planned_event.id,checked=planned_event.always_show) }}
 | 
			
		||||
															{{ macros::checkbox(label='Gesperrt', name='is_locked', id=planned_event.id,checked=planned_event.is_locked) }}
 | 
			
		||||
															{{ macros::input(label='Anmerkungen', name='notes', type='input', value=planned_event.notes) }}
 | 
			
		||||
    {% if "Donau Linz" in loggedin_user.roles %}
 | 
			
		||||
        <div class="grid gap-3">
 | 
			
		||||
          <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" role="alert">
 | 
			
		||||
            <h2 class="h2">Vereinsmitglieder</h2>
 | 
			
		||||
             <div class="text-sm p-3">
 | 
			
		||||
              <ul class="list-disc ms-2">
 | 
			
		||||
                <li class="py-1"><a href="/planned" class="link-primary">Geplante Ausfahrten</a></li>
 | 
			
		||||
                <li class="py-1"><a href="/log" class="link-primary">Ausfahrt eintragen</a></li>
 | 
			
		||||
                <li class="py-1"><a href="/log/show" class="link-primary">Logbuch</a></li>
 | 
			
		||||
                <li class="py-1"><a href="/stat" class="link-primary">Statstik</a></li>
 | 
			
		||||
                <li class="py-1"><a href="/stat/boats" class="link-primary">Bootsauswertung</a></li>
 | 
			
		||||
                <li class="py-1"><a href="/boatdamage" class="link-primary">Bootsschaden</a></li>
 | 
			
		||||
              </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
															<input value="Speichern" class="btn btn-primary" type="submit"/>
 | 
			
		||||
														</form>
 | 
			
		||||
													</div>
 | 
			
		||||
													{# --- END Edit Form --- #}
 | 
			
		||||
 | 
			
		||||
													{# --- START Delete Btn --- #}
 | 
			
		||||
													<div class="text-right">
 | 
			
		||||
														<a href="/admin/planned-event/{{ planned_event.id }}/delete" class="inline-block btn btn-alert">
 | 
			
		||||
															{% include "includes/delete-icon" %}
 | 
			
		||||
															Termin löschen
 | 
			
		||||
														</a>
 | 
			
		||||
													</div>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END Delete Btn --- #}
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
										{# --- END Sidebar Content --- #}
 | 
			
		||||
									</div>
 | 
			
		||||
								{% endfor %}
 | 
			
		||||
							{% endif %}
 | 
			
		||||
							{# --- END Events --- #}
 | 
			
		||||
    {% if "scheckbuch" in loggedin_user.roles %}
 | 
			
		||||
        <div class="grid gap-3">
 | 
			
		||||
          <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" role="alert">
 | 
			
		||||
            <h2 class="h2">Scheckbuch</h2>
 | 
			
		||||
             <div class="text-sm p-3">
 | 
			
		||||
              <ul class="list-disc ms-2">
 | 
			
		||||
                <li class="py-1"><a href="/planned" class="link-primary">Geplante Ausfahrten</a></li>
 | 
			
		||||
              </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
							{# --- START Trips --- #}
 | 
			
		||||
							{% if day.trips | length > 0 %}
 | 
			
		||||
								{% for trip in day.trips | sort(attribute="planned_starting_time") %}
 | 
			
		||||
									<div class="pt-2 px-3 reset-js border-t border-gray-200" style="order: {{ trip.planned_starting_time | replace(from=":", to="") }}" data-coxneeded="false">
 | 
			
		||||
										<div class="flex justify-between items-center">
 | 
			
		||||
											<div class="mr-1">
 | 
			
		||||
												{% if trip.max_people == 0 %}
 | 
			
		||||
													<strong class="text-[#f43f5e]">⚠
 | 
			
		||||
														{{ trip.planned_starting_time }}
 | 
			
		||||
														Uhr</strong>
 | 
			
		||||
													<small class="text-[#f43f5e]">(Absage
 | 
			
		||||
														{{ trip.cox_name }}
 | 
			
		||||
														{% if trip.trip_type %}
 | 
			
		||||
															-
 | 
			
		||||
															{{ trip.trip_type.icon | safe }}{{ trip.trip_type.name }}
 | 
			
		||||
														{% endif %})</small>
 | 
			
		||||
												{% else %}
 | 
			
		||||
													<strong class="text-primary-900 dark:text-white">{{ trip.planned_starting_time }}
 | 
			
		||||
														Uhr</strong>
 | 
			
		||||
													<small class="text-gray-600 dark:text-gray-100">({{ trip.cox_name }}{% if trip.trip_type %} - {{ trip.trip_type.icon | safe }} {{ trip.trip_type.name }}{% endif %})</small>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												<br/>
 | 
			
		||||
												<a href="#" data-sidebar="true" data-trigger="sidebar" data-header="<strong>{% if trip.max_people == 0 %}⚠ {% endif %}{{ trip.planned_starting_time }} Uhr</strong> ({{ trip.cox_name }}){% if trip.trip_type %}<small class='block'>{{ trip.trip_type.desc }}</small>{% endif %}{% if trip.notes %}<small class='block'>{{ trip.notes }}</small>{% endif %}" data-body="#trip{{ trip.trip_details_id }}" class="inline-block link-primary mr-3">
 | 
			
		||||
													Details
 | 
			
		||||
												</a>
 | 
			
		||||
											</div>
 | 
			
		||||
    {% if "admin" in loggedin_user.roles %}
 | 
			
		||||
        <div class="grid gap-3">
 | 
			
		||||
          <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" role="alert">
 | 
			
		||||
            <h2 class="h2">Admin</h2>
 | 
			
		||||
             <div class="text-sm p-3">
 | 
			
		||||
              <ul class="list-disc ms-2">
 | 
			
		||||
                <li class="py-1"><a href="/admin/boat" class="link-primary">Boote</a></li>
 | 
			
		||||
                <li class="py-1"><a href="/admin/user" class="link-primary">User</a></li>
 | 
			
		||||
                <li class="py-1"><a href="/admin/mail" class="link-primary">Mail</a></li>
 | 
			
		||||
              </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
											<div>
 | 
			
		||||
												{% set_global cur_user_participates = false %}
 | 
			
		||||
												{% for rower in trip.rower %}
 | 
			
		||||
													{% if rower.name == loggedin_user.name %}
 | 
			
		||||
														{% set_global cur_user_participates = true %}
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endfor %}
 | 
			
		||||
												{% if cur_user_participates %}
 | 
			
		||||
													<a href="/remove/{{ trip.trip_details_id }}" class="btn btn-attention btn-fw">Abmelden</a>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{% if trip.max_people > trip.rower | length and trip.cox_id != loggedin_user.id and cur_user_participates == false%}
 | 
			
		||||
													<a href="/join/{{ trip.trip_details_id }}" class="btn btn-primary btn-fw" {% if trip.trip_type %} onclick="return confirm('{{ trip.trip_type.question  }}');" {% endif %}>Mitrudern</a>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
										{# --- START Sidebar Content --- #}
 | 
			
		||||
										<div class="hidden">
 | 
			
		||||
											<div id="trip{{ trip.trip_details_id }}">
 | 
			
		||||
												{% if trip.max_people == 0 %}
 | 
			
		||||
													{# --- border-[#f43f5e] bg-[#f43f5e] --- #}
 | 
			
		||||
													{{ macros::box(participants=trip.rower,bg='[#f43f5e]',header='Absage') }}
 | 
			
		||||
												{% else %}
 | 
			
		||||
													{% set amount_cur_rower = trip.rower | length %}
 | 
			
		||||
													{{ macros::box(participants=trip.rower, empty_seats=trip.max_people - amount_cur_rower, bg='primary-100', color='black', trip_details_id=trip.trip_details_id, allow_removing=loggedin_user.id == trip.cox_id) }}
 | 
			
		||||
													{% if trip.cox_id == loggedin_user.id %}
 | 
			
		||||
														<form action="/join/{{ trip.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"/>
 | 
			
		||||
														</form>
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
 | 
			
		||||
												{# --- START Edit Form --- #}
 | 
			
		||||
												{% if trip.cox_id == loggedin_user.id %}
 | 
			
		||||
													<div class="bg-gray-100 dark:bg-primary-900 p-3 mt-4 rounded-md">
 | 
			
		||||
														<h3 class="text-primary-950 dark:text-white font-bold uppercase tracking-wide mb-2">Ausfahrt bearbeiten</h3>
 | 
			
		||||
														<form action="/cox/trip/{{ trip.id }}" method="post" class="grid gap-3">
 | 
			
		||||
															{{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=trip.max_people, min='0') }}
 | 
			
		||||
															{{ macros::input(label='Anmerkungen', name='notes', type='input', value=trip.notes) }}
 | 
			
		||||
															{{ macros::checkbox(label='Immer anzeigen', name='always_show', id=trip.id,checked=trip.always_show) }}
 | 
			
		||||
															{{ macros::checkbox(label='Gesperrt', name='is_locked', id=trip.id,checked=trip.is_locked) }}
 | 
			
		||||
															{{ macros::select(label='Typ', name='trip_type', data=trip_types, default='Reguläre Ausfahrt', selected_id=trip.trip_type_id) }}
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
															<input value="Speichern" class="btn btn-primary" type="submit"/>
 | 
			
		||||
														</form>
 | 
			
		||||
													</div>
 | 
			
		||||
													{% if trip.rower | length == 0 %}
 | 
			
		||||
														<div class="text-right mt-6">
 | 
			
		||||
															<a href="/cox/remove/trip/{{ trip.id }}" class="inline-block btn btn-alert">
 | 
			
		||||
																{% include "includes/delete-icon" %}
 | 
			
		||||
																Termin löschen
 | 
			
		||||
															</a>
 | 
			
		||||
														</div>
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END Edit Form --- #}
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
										{# --- END Sidebar Content --- #}
 | 
			
		||||
									</div>
 | 
			
		||||
								{% endfor %}
 | 
			
		||||
							{% endif %}
 | 
			
		||||
							{# --- END Trips --- #}
 | 
			
		||||
						</div>
 | 
			
		||||
					{% endif %}
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				{# --- START Add Buttons --- #}
 | 
			
		||||
				{% 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" %}
 | 
			
		||||
								</span>
 | 
			
		||||
								Event
 | 
			
		||||
							</a>
 | 
			
		||||
						{% 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>
 | 
			
		||||
								Ausfahrt
 | 
			
		||||
							</a>
 | 
			
		||||
						{% endif %}
 | 
			
		||||
					</div>
 | 
			
		||||
				{% endif %}
 | 
			
		||||
				{# --- END Add Buttons --- #}
 | 
			
		||||
			</div>
 | 
			
		||||
		{% endfor %}
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% if "cox" in loggedin_user.roles %}
 | 
			
		||||
	{% include "forms/trip" %}
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
{% if "admin" in loggedin_user.roles %}
 | 
			
		||||
	{% include "forms/event" %}
 | 
			
		||||
{% endif %}{% endblock content %}
 | 
			
		||||
{% endblock content%}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										294
									
								
								templates/planned.html.tera
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								templates/planned.html.tera
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,294 @@
 | 
			
		||||
{% import "includes/macros" as macros %}
 | 
			
		||||
 | 
			
		||||
{% extends "base" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
	<div class="max-w-screen-xl w-full grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
 | 
			
		||||
		{% if flash %}
 | 
			
		||||
			{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}
 | 
			
		||||
		{% endif %}
 | 
			
		||||
 | 
			
		||||
		<h1 class="h1 sm:col-span-2 lg:col-span-3">Ausfahrten</h1>
 | 
			
		||||
 | 
			
		||||
		{% include "includes/buttons" %}
 | 
			
		||||
 | 
			
		||||
		{% for day in days %}
 | 
			
		||||
			{% set amount_trips = day.planned_events | length + day.trips | length %}
 | 
			
		||||
			{% set_global day_cox_needed = false %}
 | 
			
		||||
			{% if day.planned_events | length > 0 %}
 | 
			
		||||
				{% for planned_event in day.planned_events %}
 | 
			
		||||
					{% if planned_event.cox_needed %}
 | 
			
		||||
						{% set_global day_cox_needed = true %}
 | 
			
		||||
					{% endif %}
 | 
			
		||||
				{% endfor %}
 | 
			
		||||
			{% endif %}
 | 
			
		||||
 | 
			
		||||
			<div class="bg-white dark:bg-primary-900 rounded-md flex justify-between flex-col shadow reset-js" style="min-height: 10rem;" data-trips="{{ amount_trips }}" data-month="{{ day.day| date(format='%m') }}" data-coxneeded="{{ day_cox_needed }}">
 | 
			
		||||
				<div>
 | 
			
		||||
					<h2 class="font-bold uppercase tracking-wide text-center rounded-t-md  {% if day.is_pinned %} text-white bg-primary-950 {% else %} text-primary-950 dark:text-white bg-gray-200 dark:bg-primary-950 bg-opacity-80 {% endif %} text-lg px-3 py-3 ">{{ day.day| date(format="%d.%m.%Y") }}
 | 
			
		||||
						<small class="inline-block ml-1 text-xs {% if day.is_pinned %} text-gray-200 {% else %} text-gray-500 dark:text-gray-100 {% endif %}">{{ day.day | date(format="%A", locale="de_AT") }}</small>
 | 
			
		||||
					</h2>
 | 
			
		||||
 | 
			
		||||
					{% if day.planned_events | length > 0 or  day.trips | length > 0 %}
 | 
			
		||||
						<div
 | 
			
		||||
							class="grid grid-cols-1 gap-3 mb-3">
 | 
			
		||||
 | 
			
		||||
							{# --- START Events --- #}
 | 
			
		||||
							{% if day.planned_events | length > 0 %}
 | 
			
		||||
								{% for planned_event in day.planned_events | sort(attribute="planned_starting_time") %}
 | 
			
		||||
									{% set amount_cur_cox = planned_event.cox | length %}
 | 
			
		||||
									{% set amount_cox_missing = planned_event.planned_amount_cox - amount_cur_cox %}
 | 
			
		||||
									<div class="pt-2 px-3 border-t border-gray-200" style="order: {{ planned_event.planned_starting_time | replace(from=":", to="") }}">
 | 
			
		||||
										<div class="flex justify-between items-center">
 | 
			
		||||
											<div class="mr-1">
 | 
			
		||||
												<strong class="text-primary-900 dark:text-white">
 | 
			
		||||
													{{ planned_event.planned_starting_time }}
 | 
			
		||||
													Uhr
 | 
			
		||||
												</strong>
 | 
			
		||||
												<small class="text-gray-600 dark:text-gray-100">({{ planned_event.name }}{% if planned_event.trip_type %} - {{ planned_event.trip_type.icon | safe }} {{ planned_event.trip_type.name }}{% endif %})</small><br/>
 | 
			
		||||
 | 
			
		||||
												<a href="#" data-sidebar="true" data-trigger="sidebar" data-header="<strong>{{ planned_event.planned_starting_time }} Uhr</strong> ({{ planned_event.name }}){% if planned_event.trip_type %}<small class='block'>{{ planned_event.trip_type.desc }}</small>{% endif %}{% if planned_event.notes %}<small class='block'>{{ planned_event.notes }}</small>{% endif %}" data-body="#event{{ planned_event.trip_details_id }}" class="inline-block link-primary mr-3">
 | 
			
		||||
													Details
 | 
			
		||||
												</a>
 | 
			
		||||
											</div>
 | 
			
		||||
											<div
 | 
			
		||||
												class="text-right grid gap-2">
 | 
			
		||||
												{# --- START Row Buttons --- #}
 | 
			
		||||
												{% set_global cur_user_participates = false %}
 | 
			
		||||
												{% for rower in planned_event.rower%}
 | 
			
		||||
													{% if rower.name == loggedin_user.name %}
 | 
			
		||||
														{% set_global cur_user_participates = true %}
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endfor %}
 | 
			
		||||
												{% if cur_user_participates %}
 | 
			
		||||
													<a href="/planned/remove/{{ planned_event.trip_details_id }}" class="btn btn-attention btn-fw">Abmelden</a>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{% if planned_event.max_people > planned_event.rower | length %}
 | 
			
		||||
													{% if cur_user_participates == false %}
 | 
			
		||||
														<a href="/planned/join/{{ planned_event.trip_details_id }}" class="btn btn-primary btn-fw" {% if planned_event.trip_type %} onclick="return confirm('{{ planned_event.trip_type.question  }}');" {% endif %}>Mitrudern</a>
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END Row Buttons --- #}
 | 
			
		||||
 | 
			
		||||
												{# --- START Cox Buttons --- #}
 | 
			
		||||
												{% if "cox" in loggedin_user.roles %}
 | 
			
		||||
													{% set_global cur_user_participates = false %}
 | 
			
		||||
													{% for cox in planned_event.cox %}
 | 
			
		||||
														{% if cox.name == loggedin_user.name %}
 | 
			
		||||
															{% set_global cur_user_participates = true %}
 | 
			
		||||
														{% endif %}
 | 
			
		||||
													{% endfor %}
 | 
			
		||||
													{% if cur_user_participates %}
 | 
			
		||||
														<a href="/cox/remove/{{ planned_event.id }}" class="block btn btn-attention btn-fw">
 | 
			
		||||
															{% include "includes/cox-icon" %}
 | 
			
		||||
															Abmelden
 | 
			
		||||
														</a>
 | 
			
		||||
													{% else %}
 | 
			
		||||
														<a href="/cox/join/{{ planned_event.id }}" class="block btn {% if amount_cox_missing > 0 %} btn-dark {% else %} btn-gray {% endif %} btn-fw" {% if planned_event.trip_type %} onclick="return confirm('{{ planned_event.trip_type.question  }}');" {% endif %}>
 | 
			
		||||
															{% include "includes/cox-icon" %}
 | 
			
		||||
															Steuern
 | 
			
		||||
														</a>
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END Cox Buttons --- #}
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
 | 
			
		||||
										{# --- START Sidebar Content --- #}
 | 
			
		||||
										<div class="hidden">
 | 
			
		||||
											<div
 | 
			
		||||
												id="event{{ planned_event.trip_details_id }}">
 | 
			
		||||
												{# --- START List Coxes --- #}
 | 
			
		||||
												{% if planned_event.planned_amount_cox > 0 %}
 | 
			
		||||
													{% if amount_cox_missing > 0 %}
 | 
			
		||||
														{{ macros::box(participants=planned_event.cox, empty_seats=planned_event.planned_amount_cox - amount_cur_cox, header='Noch benötigte Steuerleute:', text='Keine Steuerleute angemeldet') }}
 | 
			
		||||
													{% else %}
 | 
			
		||||
														{{ macros::box(participants=planned_event.cox, empty_seats="", header='Genügend Steuerleute haben sich angemeldet :-)', text='Keine Steuerleute angemeldet') }}
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END List Coxes --- #}
 | 
			
		||||
 | 
			
		||||
												{# --- 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="admin" in loggedin_user.roles) }}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END List Rowers --- #}
 | 
			
		||||
 | 
			
		||||
													{% 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"/>
 | 
			
		||||
														</form>
 | 
			
		||||
													{% endif %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
												{% if planned_event.allow_guests %}
 | 
			
		||||
													<div class="text-primary-900 bg-primary-50 text-center p-1 mb-4">Gäste willkommen!</div>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
 | 
			
		||||
												{% if "admin" in loggedin_user.roles %}
 | 
			
		||||
 | 
			
		||||
													{# --- START Edit Form --- #}
 | 
			
		||||
													<div class="bg-gray-100 dark:bg-primary-900 p-3 mt-4 rounded-md">
 | 
			
		||||
														<h3 class="text-primary-950 dark:text-white font-bold uppercase tracking-wide mb-2">Ausfahrt bearbeiten</h3>
 | 
			
		||||
														<form action="/admin/planned-event" method="post" class="grid gap-3">
 | 
			
		||||
															<input type="hidden" name="_method" value="put"/>
 | 
			
		||||
															<input type="hidden" name="id" value="{{ planned_event.id }}"/>
 | 
			
		||||
															{{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=planned_event.max_people, min='0') }}
 | 
			
		||||
															{{ macros::input(label='Anzahl Steuerleute', name='planned_amount_cox', type='number', value=planned_event.planned_amount_cox, required=true, min='0') }}
 | 
			
		||||
															{{ macros::checkbox(label='Immer anzeigen', name='always_show', id=planned_event.id,checked=planned_event.always_show) }}
 | 
			
		||||
															{{ macros::checkbox(label='Gesperrt', name='is_locked', id=planned_event.id,checked=planned_event.is_locked) }}
 | 
			
		||||
															{{ macros::input(label='Anmerkungen', name='notes', type='input', value=planned_event.notes) }}
 | 
			
		||||
 | 
			
		||||
															<input value="Speichern" class="btn btn-primary" type="submit"/>
 | 
			
		||||
														</form>
 | 
			
		||||
													</div>
 | 
			
		||||
													{# --- END Edit Form --- #}
 | 
			
		||||
 | 
			
		||||
													{# --- START Delete Btn --- #}
 | 
			
		||||
													<div class="text-right">
 | 
			
		||||
														<a href="/admin/planned-event/{{ planned_event.id }}/delete" class="inline-block btn btn-alert">
 | 
			
		||||
															{% include "includes/delete-icon" %}
 | 
			
		||||
															Termin löschen
 | 
			
		||||
														</a>
 | 
			
		||||
													</div>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END Delete Btn --- #}
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
										{# --- END Sidebar Content --- #}
 | 
			
		||||
									</div>
 | 
			
		||||
								{% endfor %}
 | 
			
		||||
							{% endif %}
 | 
			
		||||
							{# --- END Events --- #}
 | 
			
		||||
 | 
			
		||||
							{# --- START Trips --- #}
 | 
			
		||||
							{% if day.trips | length > 0 %}
 | 
			
		||||
								{% for trip in day.trips | sort(attribute="planned_starting_time") %}
 | 
			
		||||
									<div class="pt-2 px-3 reset-js border-t border-gray-200" style="order: {{ trip.planned_starting_time | replace(from=":", to="") }}" data-coxneeded="false">
 | 
			
		||||
										<div class="flex justify-between items-center">
 | 
			
		||||
											<div class="mr-1">
 | 
			
		||||
												{% if trip.max_people == 0 %}
 | 
			
		||||
													<strong class="text-[#f43f5e]">⚠
 | 
			
		||||
														{{ trip.planned_starting_time }}
 | 
			
		||||
														Uhr</strong>
 | 
			
		||||
													<small class="text-[#f43f5e]">(Absage
 | 
			
		||||
														{{ trip.cox_name }}
 | 
			
		||||
														{% if trip.trip_type %}
 | 
			
		||||
															-
 | 
			
		||||
															{{ trip.trip_type.icon | safe }}{{ trip.trip_type.name }}
 | 
			
		||||
														{% endif %})</small>
 | 
			
		||||
												{% else %}
 | 
			
		||||
													<strong class="text-primary-900 dark:text-white">{{ trip.planned_starting_time }}
 | 
			
		||||
														Uhr</strong>
 | 
			
		||||
													<small class="text-gray-600 dark:text-gray-100">({{ trip.cox_name }}{% if trip.trip_type %} - {{ trip.trip_type.icon | safe }} {{ trip.trip_type.name }}{% endif %})</small>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												<br/>
 | 
			
		||||
												<a href="#" data-sidebar="true" data-trigger="sidebar" data-header="<strong>{% if trip.max_people == 0 %}⚠ {% endif %}{{ trip.planned_starting_time }} Uhr</strong> ({{ trip.cox_name }}){% if trip.trip_type %}<small class='block'>{{ trip.trip_type.desc }}</small>{% endif %}{% if trip.notes %}<small class='block'>{{ trip.notes }}</small>{% endif %}" data-body="#trip{{ trip.trip_details_id }}" class="inline-block link-primary mr-3">
 | 
			
		||||
													Details
 | 
			
		||||
												</a>
 | 
			
		||||
											</div>
 | 
			
		||||
 | 
			
		||||
											<div>
 | 
			
		||||
												{% set_global cur_user_participates = false %}
 | 
			
		||||
												{% for rower in trip.rower %}
 | 
			
		||||
													{% if rower.name == loggedin_user.name %}
 | 
			
		||||
														{% set_global cur_user_participates = true %}
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endfor %}
 | 
			
		||||
												{% if cur_user_participates %}
 | 
			
		||||
													<a href="/remove/{{ trip.trip_details_id }}" class="btn btn-attention btn-fw">Abmelden</a>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{% if trip.max_people > trip.rower | length and trip.cox_id != loggedin_user.id and cur_user_participates == false%}
 | 
			
		||||
													<a href="/join/{{ trip.trip_details_id }}" class="btn btn-primary btn-fw" {% if trip.trip_type %} onclick="return confirm('{{ trip.trip_type.question  }}');" {% endif %}>Mitrudern</a>
 | 
			
		||||
												{% endif %}
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
										{# --- START Sidebar Content --- #}
 | 
			
		||||
										<div class="hidden">
 | 
			
		||||
											<div id="trip{{ trip.trip_details_id }}">
 | 
			
		||||
												{% if trip.max_people == 0 %}
 | 
			
		||||
													{# --- border-[#f43f5e] bg-[#f43f5e] --- #}
 | 
			
		||||
													{{ macros::box(participants=trip.rower,bg='[#f43f5e]',header='Absage') }}
 | 
			
		||||
												{% else %}
 | 
			
		||||
													{% set amount_cur_rower = trip.rower | length %}
 | 
			
		||||
													{{ macros::box(participants=trip.rower, empty_seats=trip.max_people - amount_cur_rower, bg='primary-100', color='black', trip_details_id=trip.trip_details_id, allow_removing=loggedin_user.id == trip.cox_id) }}
 | 
			
		||||
													{% if trip.cox_id == loggedin_user.id %}
 | 
			
		||||
														<form action="/join/{{ trip.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"/>
 | 
			
		||||
														</form>
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
 | 
			
		||||
												{# --- START Edit Form --- #}
 | 
			
		||||
												{% if trip.cox_id == loggedin_user.id %}
 | 
			
		||||
													<div class="bg-gray-100 dark:bg-primary-900 p-3 mt-4 rounded-md">
 | 
			
		||||
														<h3 class="text-primary-950 dark:text-white font-bold uppercase tracking-wide mb-2">Ausfahrt bearbeiten</h3>
 | 
			
		||||
														<form action="/cox/trip/{{ trip.id }}" method="post" class="grid gap-3">
 | 
			
		||||
															{{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=trip.max_people, min='0') }}
 | 
			
		||||
															{{ macros::input(label='Anmerkungen', name='notes', type='input', value=trip.notes) }}
 | 
			
		||||
															{{ macros::checkbox(label='Immer anzeigen', name='always_show', id=trip.id,checked=trip.always_show) }}
 | 
			
		||||
															{{ macros::checkbox(label='Gesperrt', name='is_locked', id=trip.id,checked=trip.is_locked) }}
 | 
			
		||||
															{{ macros::select(label='Typ', name='trip_type', data=trip_types, default='Reguläre Ausfahrt', selected_id=trip.trip_type_id) }}
 | 
			
		||||
 | 
			
		||||
															<input value="Speichern" class="btn btn-primary" type="submit"/>
 | 
			
		||||
														</form>
 | 
			
		||||
													</div>
 | 
			
		||||
													{% if trip.rower | length == 0 %}
 | 
			
		||||
														<div class="text-right mt-6">
 | 
			
		||||
															<a href="/cox/remove/trip/{{ trip.id }}" class="inline-block btn btn-alert">
 | 
			
		||||
																{% include "includes/delete-icon" %}
 | 
			
		||||
																Termin löschen
 | 
			
		||||
															</a>
 | 
			
		||||
														</div>
 | 
			
		||||
													{% endif %}
 | 
			
		||||
												{% endif %}
 | 
			
		||||
												{# --- END Edit Form --- #}
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
										{# --- END Sidebar Content --- #}
 | 
			
		||||
									</div>
 | 
			
		||||
								{% endfor %}
 | 
			
		||||
							{% endif %}
 | 
			
		||||
							{# --- END Trips --- #}
 | 
			
		||||
						</div>
 | 
			
		||||
					{% endif %}
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				{# --- START Add Buttons --- #}
 | 
			
		||||
				{% 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" %}
 | 
			
		||||
								</span>
 | 
			
		||||
								Event
 | 
			
		||||
							</a>
 | 
			
		||||
						{% 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>
 | 
			
		||||
								Ausfahrt
 | 
			
		||||
							</a>
 | 
			
		||||
						{% endif %}
 | 
			
		||||
					</div>
 | 
			
		||||
				{% endif %}
 | 
			
		||||
				{# --- END Add Buttons --- #}
 | 
			
		||||
			</div>
 | 
			
		||||
		{% endfor %}
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% if "cox" in loggedin_user.roles %}
 | 
			
		||||
	{% include "forms/trip" %}
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
{% if "admin" in loggedin_user.roles %}
 | 
			
		||||
	{% include "forms/event" %}
 | 
			
		||||
{% endif %}{% endblock content %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user