use crate::model::{boat::Boat, user::User}; use chrono::NaiveDateTime; use rocket::serde::{Deserialize, Serialize}; use rocket::FromForm; use sqlx::{FromRow, SqlitePool}; use super::log::Log; use super::notification::Notification; use super::role::Role; #[derive(FromRow, Debug, Serialize, Deserialize)] pub struct BoatDamage { pub id: i64, pub boat_id: i64, pub desc: String, pub user_id_created: i64, pub created_at: NaiveDateTime, pub user_id_fixed: Option, pub fixed_at: Option, pub user_id_verified: Option, pub verified_at: Option, pub lock_boat: bool, } #[derive(FromRow, Debug, Serialize, Deserialize)] pub struct BoatDamageWithDetails { #[serde(flatten)] boat_damage: BoatDamage, user_created: User, user_fixed: Option, user_verified: Option, boat: Boat, verified: bool, } #[derive(Debug)] pub struct BoatDamageToAdd<'r> { pub boat_id: i64, pub desc: &'r str, pub user_id_created: i32, pub lock_boat: bool, } #[derive(FromForm, Debug)] pub struct BoatDamageFixed<'r> { pub desc: &'r str, pub user_id_fixed: i32, } #[derive(FromForm, Debug)] pub struct BoatDamageVerified<'r> { pub desc: &'r str, pub user_id_verified: i32, } impl BoatDamage { pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option { sqlx::query_as!( Self, "SELECT id, boat_id, desc, user_id_created, created_at, user_id_fixed, fixed_at, user_id_verified, verified_at, lock_boat FROM boat_damage WHERE id like ?", id ) .fetch_one(db) .await .ok() } pub async fn all(db: &SqlitePool) -> Vec { let boatdamages = sqlx::query_as!( BoatDamage, " SELECT id, boat_id, desc, user_id_created, created_at, user_id_fixed, fixed_at, user_id_verified, verified_at, lock_boat FROM boat_damage WHERE ( verified_at IS NULL OR verified_at >= datetime('now', '-30 days') ) ORDER BY created_at DESC " ) .fetch_all(db) .await .unwrap(); //TODO: fixme let mut res = Vec::new(); for boat_damage in boatdamages { let user_fixed = match boat_damage.user_id_fixed { Some(id) => { let user = User::find_by_id(db, id as i32).await; Some(user.unwrap()) } None => None, }; let user_verified = match boat_damage.user_id_verified { Some(id) => { let user = User::find_by_id(db, id as i32).await; Some(user.unwrap()) } None => None, }; res.push(BoatDamageWithDetails { boat: Boat::find_by_id(db, boat_damage.boat_id as i32) .await .unwrap(), user_created: User::find_by_id(db, boat_damage.user_id_created as i32) .await .unwrap(), user_fixed, verified: user_verified.is_some(), user_verified, boat_damage, }); } res } pub async fn create(db: &SqlitePool, boatdamage: BoatDamageToAdd<'_>) -> Result<(), String> { Log::create(db, format!("New boat damage: {boatdamage:?}")).await; let Some(boat) = Boat::find_by_id(db, boatdamage.boat_id as i32).await else { return Err("Boot gibt's ned".into()); }; let was_unusable_before = boat.is_locked(db).await; sqlx::query!( "INSERT INTO boat_damage(boat_id, desc, user_id_created, lock_boat) VALUES (?,?,?, ?)", boatdamage.boat_id, boatdamage.desc, boatdamage.user_id_created, boatdamage.lock_boat ) .execute(db) .await .map_err(|e| e.to_string())?; if !was_unusable_before && boat.is_locked(db).await { let cox = Role::find_by_name(db, "cox").await.unwrap(); Notification::create_for_role(db, &cox, &format!("Liebe Steuerberechtigte, bitte beachten, dass {} bis auf weiteres aufgrund von Reparaturarbeiten gesperrt ist.", boat.name), "Boot gesperrt", None, None).await; } let technicals = User::all_with_role(db, &Role::find_by_name(db, "tech").await.unwrap()).await; for technical in technicals { if technical.id as i32 != boatdamage.user_id_created { Notification::create( db, &technical, &format!( "{} hat einen neuen Bootschaden für Boot '{}' angelegt: {}", User::find_by_id(db, boatdamage.user_id_created) .await .unwrap() .name, boat.name, boatdamage.desc ), "Neuer Bootsschaden angelegt", None, None, ) .await; } } Notification::create( db, &User::find_by_id(db, boatdamage.user_id_created) .await .unwrap(), &format!( "Du hat einen neuen Bootschaden für Boot '{}' angelegt: {}", Boat::find_by_id(db, boatdamage.boat_id as i32) .await .unwrap() .name, boatdamage.desc ), "Neuer Bootsschaden angelegt", None, None, ) .await; Ok(()) } pub async fn fixed( &self, db: &SqlitePool, boat_damage: BoatDamageFixed<'_>, ) -> Result<(), String> { Log::create(db, format!("Fixed boat damage: {boat_damage:?}")).await; let boat = Boat::find_by_id(db, self.boat_id as i32).await.unwrap(); sqlx::query!( "UPDATE boat_damage SET desc=?, user_id_fixed=?, fixed_at=CURRENT_TIMESTAMP WHERE id=?", boat_damage.desc, boat_damage.user_id_fixed, self.id ) .execute(db) .await .map_err(|e| e.to_string())?; let user = User::find_by_id(db, boat_damage.user_id_fixed) .await .unwrap(); if user.has_role(db, "tech").await { return self .verified( db, BoatDamageVerified { desc: boat_damage.desc, user_id_verified: user.id as i32, }, ) .await; } let technicals = User::all_with_role(db, &Role::find_by_name(db, "tech").await.unwrap()).await; for technical in technicals { if technical.id as i32 != boat_damage.user_id_fixed { Notification::create( db, &technical, &format!( "{} hat den Bootschaden '{}' beim Boot '{}' repariert. Könntest du das bei Gelegenheit verifizieren?", User::find_by_id(db, boat_damage.user_id_fixed) .await .unwrap() .name, boat_damage.desc, boat.name, ), "Bootsschaden repariert", None,None ) .await; } } if boat_damage.user_id_fixed != self.user_id_created as i32 { let user_fixed = User::find_by_id(db, boat_damage.user_id_fixed) .await .unwrap(); let user_created = User::find_by_id(db, self.user_id_created as i32) .await .unwrap(); // Boatdamage is also directly verified, if a tech has repaired it. We don't want to // send 2 notifications. if !user_fixed.has_role(db, "tech").await { Notification::create( db, &user_created, &format!( "{} hat den von dir eingetragenen Bootschaden '{}' beim Boot '{}' repariert. Dieser muss nun noch von unseren Bootswarten bestätigt werden.", user_fixed.name, boat_damage.desc, boat.name, ), "Bootsschaden repariert", None,None ) .await; } } Ok(()) } pub async fn verified( &self, db: &SqlitePool, boat_form: BoatDamageVerified<'_>, ) -> Result<(), String> { if let Some(verifier) = User::find_by_id(db, boat_form.user_id_verified).await { if !verifier.has_role(db, "tech").await { Log::create(db, format!("User {verifier:?} tried to verify boat {boat_form:?}. The user is no tech. Manually craftted request?")).await; return Err("You are not allowed to verify the boat!".into()); } } else { Log::create(db, format!("Someone tried to verify the boat {boat_form:?} with user_id={} which does not exist. Manually craftted request?", boat_form.user_id_verified)).await; return Err("Could not find user".into()); } let Some(boat) = Boat::find_by_id(db, self.boat_id as i32).await else { return Err("Boot gibt's ned".into()); }; let was_unusable_before = boat.is_locked(db).await; Log::create(db, format!("Verified boat damage: {boat_form:?}")).await; sqlx::query!( "UPDATE boat_damage SET desc=?, user_id_verified=?, verified_at=CURRENT_TIMESTAMP WHERE id=?", boat_form.desc, boat_form.user_id_verified, self.id ) .execute(db) .await.map_err(|e| e.to_string())?; if boat_form.user_id_verified != self.user_id_created as i32 { let user_verified = User::find_by_id(db, boat_form.user_id_verified) .await .unwrap(); let user_created = User::find_by_id(db, self.user_id_created as i32) .await .unwrap(); if user_verified.id == self.user_id_fixed.unwrap() { Notification::create( db, &user_created, &format!( "{} hat den von dir eingetragenen Bootschaden '{}' beim Boot '{}' repariert und verifiziert.", user_verified.name, self.desc, boat.name, ), "Bootsschaden repariert & verifiziert", None, None ) .await; } else { Notification::create( db, &user_created, &format!( "{} hat verifiziert, dass der von dir eingetragenen Bootschaden '{}' beim Boot '{}' korrekt repariert wurde.", user_verified.name, self.desc, boat.name, ), "Bootsschaden verifiziert", None, None ).await; } } if was_unusable_before && !boat.is_locked(db).await { let cox = Role::find_by_name(db, "cox").await.unwrap(); Notification::create_for_role(db, &cox, &format!("Liebe Steuerberechtigte, {} wurde repariert und freut sich ab sofort wieder gerudert zu werden :-)", boat.name), "Boot repariert", None, None).await; } Ok(()) } }