diff --git a/seeds.sql b/seeds.sql index b1e3009..0331795 100644 --- a/seeds.sql +++ b/seeds.sql @@ -9,6 +9,7 @@ INSERT INTO "role" (name) VALUES ('paid'); INSERT INTO "role" (name) VALUES ('Vorstand'); INSERT INTO "role" (name) VALUES ('Bootsführer'); INSERT INTO "role" (name) VALUES ('schnupperant'); +INSERT INTO "role" (name) VALUES ('kassier'); 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); diff --git a/src/model/user.rs b/src/model/user.rs index 981261c..c13535a 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -918,266 +918,82 @@ impl<'r> FromRequest<'r> for User { } } -pub struct TechUser { - pub(crate) user: User, -} +/// Creates a struct named $name. Allows to be created from a user, if one of the specified $roles are active for the user. +macro_rules! special_user { + ($name:ident, $($role:tt)*) => { + #[derive(Debug)] + pub struct $name { + pub(crate) user: User, + } -impl Deref for TechUser { - type Target = User; + impl Deref for $name { + type Target = User; + fn deref(&self) -> &Self::Target { + &self.user + } + } - fn deref(&self) -> &Self::Target { - &self.user - } -} + impl $name { + pub fn into_inner(self) -> User { + self.user + } + } -#[async_trait] -impl<'r> FromRequest<'r> for TechUser { - type Error = LoginError; - - async fn from_request(req: &'r Request<'_>) -> request::Outcome { - let db = req.rocket().state::().unwrap(); - - match User::from_request(req).await { - Outcome::Success(user) => { - if user.has_role(db, "tech").await { - Outcome::Success(TechUser { user }) - } else { - Outcome::Error((Status::Forbidden, LoginError::NotACox)) + #[async_trait] + impl<'r> FromRequest<'r> for $name { + type Error = LoginError; + async fn from_request(req: &'r Request<'_>) -> request::Outcome { + let db = req.rocket().state::().unwrap(); + match User::from_request(req).await { + Outcome::Success(user) => { + if special_user!(@check_roles user, db, $($role)*) { + Outcome::Success($name { user }) + } else { + Outcome::Forward(Status::Forbidden) + } + } + Outcome::Error(f) => Outcome::Error(f), + Outcome::Forward(f) => Outcome::Forward(f), } } - Outcome::Error(f) => Outcome::Error(f), - Outcome::Forward(f) => Outcome::Forward(f), } - } -} -pub struct CoxUser { - pub(crate) user: User, -} - -impl Deref for CoxUser { - type Target = User; - - fn deref(&self) -> &Self::Target { - &self.user - } -} - -impl CoxUser { - pub async fn new(db: &SqlitePool, user: User) -> Option { - if user.has_role(db, "cox").await { - Some(CoxUser { user }) - } else { - None - } - } -} - -#[async_trait] -impl<'r> FromRequest<'r> for CoxUser { - type Error = LoginError; - - async fn from_request(req: &'r Request<'_>) -> request::Outcome { - let db = req.rocket().state::().unwrap(); - - match User::from_request(req).await { - Outcome::Success(user) => { - if user.has_role(db, "cox").await { - Outcome::Success(CoxUser { user }) + impl $name { + pub async fn new(db: &SqlitePool, user: User) -> Option { + if special_user!(@check_roles user, db, $($role)*) { + Some($name { user }) } else { - Outcome::Error((Status::Forbidden, LoginError::NotACox)) + None } } - Outcome::Error(f) => Outcome::Error(f), - Outcome::Forward(f) => Outcome::Forward(f), } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct AdminUser { - pub(crate) user: User, -} - -#[async_trait] -impl<'r> FromRequest<'r> for AdminUser { - type Error = LoginError; - - async fn from_request(req: &'r Request<'_>) -> request::Outcome { - let db = req.rocket().state::().unwrap(); - match User::from_request(req).await { - Outcome::Success(user) => { - if user.has_role(db, "admin").await { - Outcome::Success(AdminUser { user }) - } else { - Outcome::Forward(Status::Forbidden) + }; + (@check_roles $user:ident, $db:ident, $(+$role:expr),* $(,-$neg_role:expr)*) => { + { + let mut has_positive_role = false; + $( + if $user.has_role($db, $role).await { + has_positive_role = true; } - } - Outcome::Error(f) => Outcome::Error(f), - Outcome::Forward(f) => Outcome::Forward(f), + )* + has_positive_role + $( + && !$user.has_role($db, $neg_role).await + )* } - } + }; } -#[derive(Debug, Serialize, Deserialize)] -pub struct AllowedForPlannedTripsUser(pub(crate) User); +special_user!(TechUser, +"tech"); +special_user!(CoxUser, +"cox"); +special_user!(AdminUser, +"admin"); +special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch"); +special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); +special_user!(SchnupperBetreuerUser, +"schnupper-betreuer"); +special_user!(VorstandUser, +"Vorstand"); +special_user!(EventUser, +"manage_events"); +special_user!(AllowedToEditPaymentStatusUser, +"kassier", +"admin"); -#[async_trait] -impl<'r> FromRequest<'r> for AllowedForPlannedTripsUser { - type Error = LoginError; - - async fn from_request(req: &'r Request<'_>) -> request::Outcome { - let db = req.rocket().state::().unwrap(); - match User::from_request(req).await { - Outcome::Success(user) => { - if user.has_role(db, "Donau Linz").await | user.has_role(db, "scheckbuch").await { - Outcome::Success(AllowedForPlannedTripsUser(user)) - } else { - Outcome::Error((Status::Forbidden, LoginError::NotACox)) - } - } - Outcome::Error(f) => Outcome::Error(f), - Outcome::Forward(f) => Outcome::Forward(f), - } - } -} - -impl From for User { - fn from(val: AllowedForPlannedTripsUser) -> Self { - val.0 - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct DonauLinzUser(pub(crate) User); - -impl From for User { - fn from(val: DonauLinzUser) -> Self { - val.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 { - let db = req.rocket().state::().unwrap(); - match User::from_request(req).await { - Outcome::Success(user) => { - if user.has_role(db, "Donau Linz").await - && !user.has_role(db, "Unterstützend").await - && !user.has_role(db, "Förderndes Mitglied").await - { - Outcome::Success(DonauLinzUser(user)) - } else { - Outcome::Error((Status::Forbidden, LoginError::NotACox)) - } - } - Outcome::Error(f) => Outcome::Error(f), - Outcome::Forward(f) => Outcome::Forward(f), - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct SchnupperBetreuerUser(pub(crate) User); - -impl From for User { - fn from(val: SchnupperBetreuerUser) -> Self { - val.0 - } -} - -impl Deref for SchnupperBetreuerUser { - type Target = User; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[async_trait] -impl<'r> FromRequest<'r> for SchnupperBetreuerUser { - type Error = LoginError; - - async fn from_request(req: &'r Request<'_>) -> request::Outcome { - let db = req.rocket().state::().unwrap(); - match User::from_request(req).await { - Outcome::Success(user) => { - if user.has_role(db, "schnupper-betreuer").await { - Outcome::Success(SchnupperBetreuerUser(user)) - } else { - Outcome::Forward(Status::Forbidden) - } - } - Outcome::Error(f) => Outcome::Error(f), - Outcome::Forward(f) => Outcome::Forward(f), - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct VorstandUser(pub(crate) User); - -impl From for User { - fn from(val: VorstandUser) -> Self { - val.0 - } -} - -impl Deref for VorstandUser { - type Target = User; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[async_trait] -impl<'r> FromRequest<'r> for VorstandUser { - type Error = LoginError; - - async fn from_request(req: &'r Request<'_>) -> request::Outcome { - let db = req.rocket().state::().unwrap(); - match User::from_request(req).await { - Outcome::Success(user) => { - if user.has_role(db, "Vorstand").await { - Outcome::Success(VorstandUser(user)) - } else { - Outcome::Forward(Status::Forbidden) - } - } - Outcome::Error(f) => Outcome::Error(f), - Outcome::Forward(f) => Outcome::Forward(f), - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct EventUser(pub(crate) User); - -impl From for User { - fn from(val: EventUser) -> Self { - val.0 - } -} - -impl Deref for EventUser { - type Target = User; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} #[derive(FromRow, Serialize, Deserialize, Clone, Debug)] pub struct UserWithRolesAndMembershipPdf { #[serde(flatten)] @@ -1221,25 +1037,6 @@ impl UserWithMembershipPdf { } } -#[async_trait] -impl<'r> FromRequest<'r> for EventUser { - type Error = LoginError; - - async fn from_request(req: &'r Request<'_>) -> request::Outcome { - let db = req.rocket().state::().unwrap(); - match User::from_request(req).await { - Outcome::Success(user) => { - if user.has_role(db, "manage_events").await { - Outcome::Success(EventUser(user)) - } else { - Outcome::Error((Status::Forbidden, LoginError::NotACox)) - } - } - Outcome::Error(f) => Outcome::Error(f), - Outcome::Forward(f) => Outcome::Forward(f), - } - } -} #[cfg(test)] mod test { use std::collections::HashMap; diff --git a/src/tera/admin/mail.rs b/src/tera/admin/mail.rs index df1bf0a..dd9c8d3 100644 --- a/src/tera/admin/mail.rs +++ b/src/tera/admin/mail.rs @@ -27,7 +27,7 @@ async fn index( context.insert( "loggedin_user", - &UserWithDetails::from_user(admin.0, db).await, + &UserWithDetails::from_user(admin.user, db).await, ); context.insert("roles", &roles); diff --git a/src/tera/admin/notification.rs b/src/tera/admin/notification.rs index 065f426..fe6578b 100644 --- a/src/tera/admin/notification.rs +++ b/src/tera/admin/notification.rs @@ -27,7 +27,7 @@ async fn index( } context.insert( "loggedin_user", - &UserWithDetails::from_user(user.0, db).await, + &UserWithDetails::from_user(user.user, db).await, ); let users: Vec = User::all(db) diff --git a/src/tera/admin/schnupper.rs b/src/tera/admin/schnupper.rs index 8548f7b..9a751e1 100644 --- a/src/tera/admin/schnupper.rs +++ b/src/tera/admin/schnupper.rs @@ -29,7 +29,7 @@ async fn index( context.insert("schnupperanten", &users); context.insert( "loggedin_user", - &UserWithDetails::from_user(user.into(), db).await, + &UserWithDetails::from_user(user.user, db).await, ); Template::render("admin/schnupper/index", context.into_json()) diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index 012df89..0cf1e8a 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -7,8 +7,8 @@ use crate::{ logbook::Logbook, role::Role, user::{ - AdminUser, User, UserWithDetails, UserWithMembershipPdf, UserWithRolesAndMembershipPdf, - VorstandUser, + AdminUser, AllowedToEditPaymentStatusUser, User, UserWithDetails, + UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, }, }, tera::Config, @@ -54,7 +54,7 @@ async fn index( .map(|u| async move { UserWithRolesAndMembershipPdf::from_user(db, u).await }) .collect(); - let user: User = user.into(); + let user: User = user.into_inner(); let allowed_to_edit = user.has_role(db, "admin").await; let users: Vec = join_all(user_futures).await; @@ -110,7 +110,7 @@ async fn index_admin( #[get("/user/fees")] async fn fees( db: &State, - admin: VorstandUser, + user: AllowedToEditPaymentStatusUser, flash: Option>, ) -> Template { let mut context = Context::new(); @@ -130,7 +130,7 @@ async fn fees( } context.insert( "loggedin_user", - &UserWithDetails::from_user(admin.into(), db).await, + &UserWithDetails::from_user(user.into_inner(), db).await, ); Template::render("admin/user/fees", context.into_json()) @@ -161,7 +161,7 @@ async fn scheckbuch( } context.insert( "loggedin_user", - &UserWithDetails::from_user(user.into(), db).await, + &UserWithDetails::from_user(user.into_inner(), db).await, ); Template::render("admin/user/scheckbuch", context.into_json()) @@ -170,7 +170,7 @@ async fn scheckbuch( #[get("/user/fees/paid?")] async fn fees_paid( db: &State, - admin: AdminUser, + calling_user: AllowedToEditPaymentStatusUser, user_ids: Vec, referer: Referer, ) -> Flash { @@ -181,7 +181,10 @@ async fn fees_paid( if user.has_role(db, "paid").await { Log::create( db, - format!("{} set fees NOT paid for '{}'", admin.user.name, user.name), + format!( + "{} set fees NOT paid for '{}'", + calling_user.user.name, user.name + ), ) .await; user.remove_role(db, &Role::find_by_name(db, "paid").await.unwrap()) @@ -189,7 +192,10 @@ async fn fees_paid( } else { Log::create( db, - format!("{} set fees paid for '{}'", admin.user.name, user.name), + format!( + "{} set fees paid for '{}'", + calling_user.user.name, user.name + ), ) .await; user.add_role(db, &Role::find_by_name(db, "paid").await.unwrap()) diff --git a/src/tera/board/boathouse.rs b/src/tera/board/boathouse.rs index 105e32b..1c10408 100644 --- a/src/tera/board/boathouse.rs +++ b/src/tera/board/boathouse.rs @@ -39,7 +39,7 @@ async fn index( context.insert( "loggedin_user", - &UserWithDetails::from_user(admin.into(), db).await, + &UserWithDetails::from_user(admin.into_inner(), db).await, ); Template::render("board/boathouse", context.into_json()) diff --git a/src/tera/boatdamage.rs b/src/tera/boatdamage.rs index c718664..246e5c6 100644 --- a/src/tera/boatdamage.rs +++ b/src/tera/boatdamage.rs @@ -59,7 +59,7 @@ async fn index( context.insert("boats", &boats); context.insert( "loggedin_user", - &UserWithDetails::from_user(user.into(), db).await, + &UserWithDetails::from_user(user.into_inner(), db).await, ); Template::render("boatdamages", context.into_json()) @@ -78,7 +78,7 @@ async fn create<'r>( data: Form>, user: DonauLinzUser, ) -> Flash { - let user: User = user.into(); + let user: User = user.into_inner(); let boatdamage_to_add = BoatDamageToAdd { boat_id: data.boat_id, desc: data.desc, diff --git a/src/tera/boatreservation.rs b/src/tera/boatreservation.rs index a011160..a739dfc 100644 --- a/src/tera/boatreservation.rs +++ b/src/tera/boatreservation.rs @@ -75,7 +75,7 @@ async fn index( context.insert("user", &User::all(db).await); context.insert( "loggedin_user", - &UserWithDetails::from_user(user.into(), db).await, + &UserWithDetails::from_user(user.into_inner(), db).await, ); Template::render("boatreservations", context.into_json()) @@ -97,7 +97,7 @@ async fn create<'r>( data: Form>, user: DonauLinzUser, ) -> Flash { - let user_applicant: User = user.into(); + let user_applicant: User = user.into_inner(); let boat = Boat::find_by_id(db, data.boat_id as i32).await.unwrap(); let boatreservation_to_add = BoatReservationToAdd { boat: &boat, diff --git a/src/tera/log.rs b/src/tera/log.rs index 1939faa..98e49d5 100644 --- a/src/tera/log.rs +++ b/src/tera/log.rs @@ -96,7 +96,7 @@ async fn index( context.insert("logtypes", &logtypes); context.insert( "loggedin_user", - &UserWithDetails::from_user(user.into(), db).await, + &UserWithDetails::from_user(user.into_inner(), db).await, ); context.insert("on_water", &on_water); context.insert("distances", &distances); @@ -110,7 +110,7 @@ async fn show(db: &State, user: DonauLinzUser) -> Template { Template::render( "log.completed", - context!(logs, loggedin_user: &UserWithDetails::from_user(user.into(), db).await), + context!(logs, loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await), ) } @@ -287,7 +287,8 @@ async fn create_kiosk( ) .await; - create_logbook(db, data, &DonauLinzUser(creator)).await //TODO: fixme + create_logbook(db, data, &DonauLinzUser::new(db, creator).await.unwrap()).await + //TODO: fixme } #[post("/update", data = "")] @@ -302,7 +303,7 @@ async fn update( return Flash::error(Redirect::to("/log"), &format!("Logbucheintrag kann nicht bearbeitet werden, da es einen Logbuch-Eintrag mit ID={} nicht gibt", data.id)); }; - match logbook.update(db, data.clone(), &user.0).await { + match logbook.update(db, data.clone(), &user.user).await { Ok(()) => { Log::create( db, @@ -372,11 +373,14 @@ async fn home_kiosk( db, data, logbook_id, - &DonauLinzUser( + &DonauLinzUser::new( + db, User::find_by_id(db, logbook.shipmaster as i32) .await .unwrap(), - ), //TODO: fixme + ) + .await + .unwrap(), ) .await } diff --git a/src/tera/planned.rs b/src/tera/planned.rs index 296d8f5..7bf5f4d 100644 --- a/src/tera/planned.rs +++ b/src/tera/planned.rs @@ -22,7 +22,7 @@ async fn index( user: AllowedForPlannedTripsUser, flash: Option>, ) -> Template { - let user: User = user.into(); + let user: User = user.into_inner(); let mut context = Context::new(); @@ -50,7 +50,7 @@ async fn join( user: AllowedForPlannedTripsUser, user_note: Option, ) -> Flash { - let user: User = user.into(); + let user: User = user.into_inner(); let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { return Flash::error(Redirect::to("/"), "Trip_details do not exist."); @@ -113,7 +113,7 @@ async fn remove_guest( user: AllowedForPlannedTripsUser, name: String, ) -> Flash { - let user: User = user.into(); + let user: User = user.into_inner(); let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { return Flash::error(Redirect::to("/planned"), "TripDetailsId does not exist"); @@ -160,7 +160,7 @@ async fn remove( trip_details_id: i64, user: AllowedForPlannedTripsUser, ) -> Flash { - let user: User = user.into(); + let user: User = user.into_inner(); let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { return Flash::error(Redirect::to("/planned"), "TripDetailsId does not exist"); diff --git a/src/tera/stat.rs b/src/tera/stat.rs index 375ee8d..5c7217c 100644 --- a/src/tera/stat.rs +++ b/src/tera/stat.rs @@ -16,7 +16,7 @@ async fn index_boat(db: &State, user: DonauLinzUser) -> Template { Template::render( "stat.boats", - context!(loggedin_user: &UserWithDetails::from_user(user.into(), db).await, stat, kiosk), + context!(loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await, stat, kiosk), ) } @@ -38,7 +38,7 @@ async fn index(db: &State, user: DonauLinzUser, year: Option) - Template::render( "stat.people", - context!(loggedin_user: &UserWithDetails::from_user(user.into(), db).await, stat, personal, kiosk, guest_km, club_km), + context!(loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await, stat, personal, kiosk, guest_km, club_km), ) } diff --git a/src/tera/trailerreservation.rs b/src/tera/trailerreservation.rs index 98c343e..4746ec9 100644 --- a/src/tera/trailerreservation.rs +++ b/src/tera/trailerreservation.rs @@ -59,7 +59,7 @@ async fn index( context.insert("user", &User::all(db).await); context.insert( "loggedin_user", - &UserWithDetails::from_user(user.into(), db).await, + &UserWithDetails::from_user(user.into_inner(), db).await, ); Template::render("trailerreservations", context.into_json()) @@ -81,7 +81,7 @@ async fn create<'r>( data: Form>, user: DonauLinzUser, ) -> Flash { - let user_applicant: User = user.into(); + let user_applicant: User = user.into_inner(); let trailer = Trailer::find_by_id(db, data.trailer_id as i32) .await .unwrap(); diff --git a/templates/admin/user/fees.html.tera b/templates/admin/user/fees.html.tera index a6a18f2..11ee23f 100644 --- a/templates/admin/user/fees.html.tera +++ b/templates/admin/user/fees.html.tera @@ -32,7 +32,7 @@ {% if not loop.last %}+{% endif %} {% endfor %} - {% if "admin" in loggedin_user.roles %} + {% if "admin" in loggedin_user.roles or "kassier" in loggedin_user.roles %} Zahlungsstatus ändern {% endif %} diff --git a/templates/admin/user/scheckbuch.html.tera b/templates/admin/user/scheckbuch.html.tera index 08a6409..3b1aac8 100644 --- a/templates/admin/user/scheckbuch.html.tera +++ b/templates/admin/user/scheckbuch.html.tera @@ -31,7 +31,7 @@
  • {{ log::show_old(log=trip, state="completed", only_ones=false, index=loop.index) }}
  • {% endfor %} - {% if "admin" in loggedin_user.roles %} + {% if "admin" in loggedin_user.roles or "kassier" in loggedin_user.roles %} Zahlungsstatus ändern {% endif %}