use rocket::serde::{Deserialize, Serialize}; use rocket::FromForm; use sqlx::{FromRow, SqlitePool}; #[derive(FromRow, Debug, Serialize, Deserialize)] pub struct Boat { pub id: i64, pub name: String, pub amount_seats: i64, pub location_id: i64, pub owner: Option, pub year_built: Option, pub boatbuilder: Option, #[serde(default = "bool::default")] default_shipmaster_only_steering: bool, #[serde(default = "bool::default")] skull: bool, #[serde(default = "bool::default")] external: bool, } #[derive(Serialize, Deserialize)] pub enum BoatDamage { None, Light, Locked, } #[derive(Serialize, Deserialize)] pub struct BoatWithDetails { #[serde(flatten)] boat: Boat, damage: BoatDamage, on_water: bool, } #[derive(FromForm)] pub struct BoatToAdd<'r> { pub name: &'r str, pub amount_seats: i64, pub year_built: Option, pub boatbuilder: Option<&'r str>, pub default_shipmaster_only_steering: bool, pub skull: bool, pub external: bool, pub location_id: Option, pub owner: Option, } #[derive(FromForm)] pub struct BoatToUpdate<'r> { pub id: i32, pub name: &'r str, pub amount_seats: i64, pub year_built: Option, pub boatbuilder: Option<&'r str>, pub default_shipmaster_only_steering: bool, pub skull: bool, pub external: bool, pub location_id: Option, pub owner: Option, } impl Boat { pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option { sqlx::query_as!( Self, "SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, skull, external FROM boat WHERE id like ?", id ) .fetch_one(db) .await .ok() } pub async fn is_locked(&self, db: &SqlitePool) -> bool { sqlx::query!("SELECT * FROM boat_damage WHERE boat_id=? AND lock_boat=true AND user_id_verified is null", self.id).fetch_optional(db).await.unwrap().is_some() } pub async fn has_minor_damage(&self, db: &SqlitePool) -> bool { sqlx::query!("SELECT * FROM boat_damage WHERE boat_id=? AND lock_boat=false AND user_id_verified is null", self.id).fetch_optional(db).await.unwrap().is_some() } pub async fn on_water(&self, db: &SqlitePool) -> bool { sqlx::query!( "SELECT * FROM logbook WHERE boat_id=? AND arrival is null", self.id ) .fetch_optional(db) .await .unwrap() .is_some() } pub async fn all(db: &SqlitePool) -> Vec { let boats = sqlx::query_as!( Boat, " SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, skull, external FROM boat ORDER BY amount_seats DESC " ) .fetch_all(db) .await .unwrap(); //TODO: fixme let mut res = Vec::new(); for boat in boats { let mut damage = BoatDamage::None; if boat.has_minor_damage(db).await { damage = BoatDamage::Light; } if boat.is_locked(db).await { damage = BoatDamage::Locked; } res.push(BoatWithDetails { damage, on_water: boat.on_water(db).await, boat, }) } res } pub async fn create(db: &SqlitePool, boat: BoatToAdd<'_>) -> bool { sqlx::query!( "INSERT INTO boat(name, amount_seats, year_built, boatbuilder, default_shipmaster_only_steering, skull, external, location_id, owner) VALUES (?,?,?,?,?,?,?,?,?)", boat.name, boat.amount_seats, boat.year_built, boat.boatbuilder, boat.default_shipmaster_only_steering, boat.skull, boat.external, boat.location_id, boat.owner ) .execute(db) .await.is_ok() } pub async fn update(&self, db: &SqlitePool, boat: BoatToUpdate<'_>) -> bool { sqlx::query!( "UPDATE boat SET name=?, amount_seats=?, year_built=?, boatbuilder=?, default_shipmaster_only_steering=?, skull=?, external=?, location_id=?, owner=? WHERE id=?", boat.name, boat.amount_seats, boat.year_built, boat.boatbuilder, boat.default_shipmaster_only_steering, boat.skull, boat.external, boat.location_id, boat.owner, self.id ) .execute(db) .await .is_ok() } pub async fn delete(&self, db: &SqlitePool) { sqlx::query!("DELETE FROM boat WHERE id=?", self.id) .execute(db) .await .unwrap(); //Okay, because we can only create a Boat of a valid id } } #[cfg(test)] mod test { use crate::{ model::boat::{Boat, BoatToAdd}, testdb, }; use sqlx::SqlitePool; #[sqlx::test] fn test_find_correct_id() { let pool = testdb!(); let boat = Boat::find_by_id(&pool, 1).await.unwrap(); assert_eq!(boat.id, 1); } #[sqlx::test] fn test_find_wrong_id() { let pool = testdb!(); let boat = Boat::find_by_id(&pool, 1337).await; assert!(boat.is_none()); } #[sqlx::test] fn test_all() { let pool = testdb!(); let res = Boat::all(&pool).await; assert!(res.len() > 3); } #[sqlx::test] fn test_succ_create() { let pool = testdb!(); assert_eq!( Boat::create( &pool, BoatToAdd { name: "new-boat-name".into(), amount_seats: 42, year_built: None, boatbuilder: "Best Boatbuilder".into(), default_shipmaster_only_steering: true, skull: true, external: false, location_id: Some(1), owner: None } ) .await, true ); } #[sqlx::test] fn test_duplicate_name_create() { let pool = testdb!(); assert_eq!( Boat::create( &pool, BoatToAdd { name: "Haichenbach".into(), amount_seats: 42, year_built: None, boatbuilder: "Best Boatbuilder".into(), default_shipmaster_only_steering: true, skull: true, external: false, location_id: Some(1), owner: None } ) .await, false ); } }