2023-07-30 20:19:25 +02:00
use rocket ::serde ::{ Deserialize , Serialize } ;
2023-07-25 13:22:11 +02:00
use rocket ::FromForm ;
2023-07-22 13:57:17 +02:00
use sqlx ::{ FromRow , SqlitePool } ;
2023-09-24 22:17:28 +02:00
use super ::user ::User ;
2023-07-22 13:57:17 +02:00
#[ 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 < i64 > ,
pub year_built : Option < i64 > ,
pub boatbuilder : Option < String > ,
#[ serde(default = " bool::default " ) ]
default_shipmaster_only_steering : bool ,
#[ serde(default = " bool::default " ) ]
skull : bool ,
#[ serde(default = " bool::default " ) ]
external : bool ,
}
2023-07-30 20:19:25 +02:00
#[ derive(Serialize, Deserialize) ]
2023-07-30 20:25:59 +02:00
#[ serde(rename_all = " lowercase " ) ]
2023-07-30 20:13:00 +02:00
pub enum BoatDamage {
None ,
Light ,
Locked ,
}
2023-07-30 20:19:25 +02:00
#[ derive(Serialize, Deserialize) ]
2023-07-30 20:13:00 +02:00
pub struct BoatWithDetails {
#[ serde(flatten) ]
boat : Boat ,
damage : BoatDamage ,
on_water : bool ,
}
2023-07-25 13:22:11 +02:00
#[ derive(FromForm) ]
pub struct BoatToAdd < ' r > {
pub name : & ' r str ,
pub amount_seats : i64 ,
pub year_built : Option < i64 > ,
pub boatbuilder : Option < & ' r str > ,
pub default_shipmaster_only_steering : bool ,
pub skull : bool ,
pub external : bool ,
pub location_id : Option < i64 > ,
pub owner : Option < i64 > ,
}
#[ derive(FromForm) ]
pub struct BoatToUpdate < ' r > {
pub name : & ' r str ,
pub amount_seats : i64 ,
pub year_built : Option < i64 > ,
pub boatbuilder : Option < & ' r str > ,
pub default_shipmaster_only_steering : bool ,
pub skull : bool ,
pub external : bool ,
2023-07-31 16:25:07 +02:00
pub location_id : i64 ,
pub owner_id : Option < i64 > ,
2023-07-25 13:22:11 +02:00
}
2023-07-22 13:57:17 +02:00
impl Boat {
pub async fn find_by_id ( db : & SqlitePool , id : i32 ) -> Option < Self > {
2023-08-05 13:21:35 +02:00
sqlx ::query_as! ( Self , " SELECT * FROM boat WHERE id like ? " , id )
. fetch_one ( db )
. await
. ok ( )
}
pub async fn find_by_name ( db : & SqlitePool , name : String ) -> Option < Self > {
sqlx ::query_as! ( Self , " SELECT * FROM boat WHERE name like ? " , name )
. fetch_one ( db )
. await
. ok ( )
2023-07-22 13:57:17 +02:00
}
2023-07-24 13:01:39 +02:00
2023-10-01 15:53:45 +02:00
pub async fn shipmaster_allowed ( & self , user : & User ) -> bool {
if let Some ( owner_id ) = self . owner {
return owner_id = = user . id ;
}
if self . amount_seats = = 1 {
return true ;
}
user . is_cox
}
2023-07-24 13:01:39 +02:00
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 ( )
}
2023-07-30 20:21:59 +02:00
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 ( )
}
2023-07-24 13:01:39 +02:00
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 ( )
}
2023-08-05 12:59:02 +02:00
async fn boats_to_details ( db : & SqlitePool , boats : Vec < Boat > ) -> Vec < BoatWithDetails > {
2023-07-30 20:13:00 +02:00
let mut res = Vec ::new ( ) ;
for boat in boats {
2023-07-30 20:21:59 +02:00
let mut damage = BoatDamage ::None ;
if boat . has_minor_damage ( db ) . await {
damage = BoatDamage ::Light ;
}
if boat . is_locked ( db ) . await {
damage = BoatDamage ::Locked ;
}
2023-07-30 20:13:00 +02:00
res . push ( BoatWithDetails {
damage ,
on_water : boat . on_water ( db ) . await ,
boat ,
2023-09-06 14:39:36 +02:00
} ) ;
2023-07-30 20:13:00 +02:00
}
res
2023-07-22 13:57:17 +02:00
}
2023-08-05 12:59:02 +02:00
pub async fn all ( db : & SqlitePool ) -> Vec < BoatWithDetails > {
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
Self ::boats_to_details ( db , boats ) . await
}
2023-09-24 22:17:28 +02:00
pub async fn for_user ( db : & SqlitePool , user : & User ) -> Vec < BoatWithDetails > {
if user . is_admin {
return Self ::all ( db ) . await ;
}
2023-10-02 12:39:05 +02:00
let boats = if user . is_cox {
2023-10-01 13:48:21 +02:00
boats = sqlx ::query_as! (
2023-09-24 22:17:28 +02:00
Boat ,
"
SELECT id , name , amount_seats , location_id , owner , year_built , boatbuilder , default_shipmaster_only_steering , skull , external
FROM boat
WHERE owner is null or owner = ?
ORDER BY amount_seats DESC
" ,
user . id
)
. fetch_all ( db )
. await
2023-10-02 12:39:05 +02:00
. unwrap ( ) //TODO: fixme
2023-10-01 13:48:21 +02:00
} else {
2023-10-02 12:39:05 +02:00
sqlx ::query_as! (
2023-10-01 13:48:21 +02:00
Boat ,
"
SELECT id , name , amount_seats , location_id , owner , year_built , boatbuilder , default_shipmaster_only_steering , skull , external
FROM boat
WHERE owner = ? OR ( owner is null and amount_seats = 1 )
ORDER BY amount_seats DESC
" ,
user . id
)
. fetch_all ( db )
. await
2023-10-02 12:39:05 +02:00
. unwrap ( ) //TODO: fixme
} ;
2023-09-24 22:17:28 +02:00
Self ::boats_to_details ( db , boats ) . await
}
2023-08-05 12:59:02 +02:00
pub async fn all_at_location ( db : & SqlitePool , location : String ) -> Vec < BoatWithDetails > {
let boats = sqlx ::query_as! (
Boat ,
"
SELECT boat . id , boat . name , amount_seats , location_id , owner , year_built , boatbuilder , default_shipmaster_only_steering , skull , external
FROM boat
INNER JOIN location ON boat . location_id = location . id
WHERE location . name = ?
ORDER BY amount_seats DESC
" ,
location
)
. fetch_all ( db )
. await
. unwrap ( ) ; //TODO: fixme
Self ::boats_to_details ( db , boats ) . await
}
2023-07-31 19:38:53 +02:00
pub async fn create ( db : & SqlitePool , boat : BoatToAdd < '_ > ) -> Result < ( ) , String > {
2023-07-22 13:57:17 +02:00
sqlx ::query! (
2023-07-22 16:36:02 +02:00
" INSERT INTO boat(name, amount_seats, year_built, boatbuilder, default_shipmaster_only_steering, skull, external, location_id, owner) VALUES (?,?,?,?,?,?,?,?,?) " ,
2023-07-25 13:22:11 +02:00
boat . name ,
boat . amount_seats ,
boat . year_built ,
boat . boatbuilder ,
boat . default_shipmaster_only_steering ,
boat . skull ,
boat . external ,
boat . location_id ,
boat . owner
2023-07-22 13:57:17 +02:00
)
. execute ( db )
2023-07-31 19:38:53 +02:00
. await . map_err ( | e | e . to_string ( ) ) ? ;
Ok ( ( ) )
2023-07-22 13:57:17 +02:00
}
2023-07-31 16:25:07 +02:00
pub async fn update ( & self , db : & SqlitePool , boat : BoatToUpdate < '_ > ) -> Result < ( ) , String > {
2023-07-22 15:34:42 +02:00
sqlx ::query! (
" UPDATE boat SET name=?, amount_seats=?, year_built=?, boatbuilder=?, default_shipmaster_only_steering=?, skull=?, external=?, location_id=?, owner=? WHERE id=? " ,
2023-07-25 13:22:11 +02:00
boat . name ,
boat . amount_seats ,
boat . year_built ,
boat . boatbuilder ,
boat . default_shipmaster_only_steering ,
boat . skull ,
boat . external ,
boat . location_id ,
2023-07-31 16:25:07 +02:00
boat . owner_id ,
2023-07-22 15:34:42 +02:00
self . id
)
. execute ( db )
2023-07-31 16:25:07 +02:00
. await . map_err ( | e | e . to_string ( ) ) ? ;
Ok ( ( ) )
2023-07-22 15:34:42 +02:00
}
2023-07-22 13:57:17 +02:00
pub async fn delete ( & self , db : & SqlitePool ) {
sqlx ::query! ( " DELETE FROM boat WHERE id=? " , self . id )
. execute ( db )
. await
2023-07-25 13:22:11 +02:00
. unwrap ( ) ; //Okay, because we can only create a Boat of a valid id
2023-07-22 13:57:17 +02:00
}
}
2023-07-22 15:34:42 +02:00
#[ cfg(test) ]
mod test {
2023-07-25 13:22:11 +02:00
use crate ::{
2023-07-31 16:33:44 +02:00
model ::boat ::{ Boat , BoatToAdd } ,
2023-07-25 13:22:11 +02:00
testdb ,
} ;
2023-07-22 15:34:42 +02:00
use sqlx ::SqlitePool ;
2023-07-31 16:25:07 +02:00
use super ::BoatToUpdate ;
2023-07-22 15:34:42 +02:00
#[ 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 ,
2023-07-25 13:22:11 +02:00
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
}
2023-07-22 15:34:42 +02:00
)
. await ,
2023-07-31 19:38:53 +02:00
Ok ( ( ) )
2023-07-22 15:34:42 +02:00
) ;
}
#[ sqlx::test ]
fn test_duplicate_name_create ( ) {
let pool = testdb! ( ) ;
assert_eq! (
Boat ::create (
& pool ,
2023-07-25 13:22:11 +02:00
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
}
2023-07-22 15:34:42 +02:00
)
. await ,
2023-07-31 19:38:53 +02:00
Err (
" error returned from database: (code: 2067) UNIQUE constraint failed: boat.name "
. into ( )
)
2023-07-22 15:34:42 +02:00
) ;
}
2023-07-31 16:25:07 +02:00
#[ sqlx::test ]
fn test_is_locked ( ) {
let pool = testdb! ( ) ;
let res = Boat ::find_by_id ( & pool , 5 )
. await
. unwrap ( )
. is_locked ( & pool )
. await ;
assert_eq! ( res , true ) ;
}
#[ sqlx::test ]
fn test_is_not_locked ( ) {
let pool = testdb! ( ) ;
let res = Boat ::find_by_id ( & pool , 4 )
. await
. unwrap ( )
. is_locked ( & pool )
. await ;
assert_eq! ( res , false ) ;
}
#[ sqlx::test ]
fn test_is_not_locked_no_damage ( ) {
let pool = testdb! ( ) ;
let res = Boat ::find_by_id ( & pool , 3 )
. await
. unwrap ( )
. is_locked ( & pool )
. await ;
assert_eq! ( res , false ) ;
}
#[ sqlx::test ]
fn test_has_minor_damage ( ) {
let pool = testdb! ( ) ;
let res = Boat ::find_by_id ( & pool , 4 )
. await
. unwrap ( )
. has_minor_damage ( & pool )
. await ;
assert_eq! ( res , true ) ;
}
#[ sqlx::test ]
fn test_has_no_minor_damage ( ) {
let pool = testdb! ( ) ;
let res = Boat ::find_by_id ( & pool , 5 )
. await
. unwrap ( )
. has_minor_damage ( & pool )
. await ;
assert_eq! ( res , false ) ;
}
#[ sqlx::test ]
fn test_on_water ( ) {
let pool = testdb! ( ) ;
let res = Boat ::find_by_id ( & pool , 2 )
. await
. unwrap ( )
. on_water ( & pool )
. await ;
assert_eq! ( res , true ) ;
}
#[ sqlx::test ]
fn test_not_on_water ( ) {
let pool = testdb! ( ) ;
let res = Boat ::find_by_id ( & pool , 4 )
. await
. unwrap ( )
. on_water ( & pool )
. await ;
assert_eq! ( res , false ) ;
}
#[ sqlx::test ]
fn test_succ_update ( ) {
let pool = testdb! ( ) ;
let boat = Boat ::find_by_id ( & pool , 1 ) . await . unwrap ( ) ;
let update = BoatToUpdate {
name : " my-new-boat-name " ,
amount_seats : 3 ,
year_built : None ,
boatbuilder : None ,
default_shipmaster_only_steering : false ,
skull : true ,
external : false ,
location_id : 1 ,
owner_id : None ,
} ;
boat . update ( & pool , update ) . await . unwrap ( ) ;
let boat = Boat ::find_by_id ( & pool , 1 ) . await . unwrap ( ) ;
assert_eq! ( boat . name , " my-new-boat-name " ) ;
}
#[ sqlx::test ]
fn test_failed_update ( ) {
let pool = testdb! ( ) ;
let boat = Boat ::find_by_id ( & pool , 1 ) . await . unwrap ( ) ;
let update = BoatToUpdate {
name : " my-new-boat-name " ,
amount_seats : 3 ,
year_built : None ,
boatbuilder : None ,
default_shipmaster_only_steering : false ,
skull : true ,
external : false ,
location_id : 999 ,
owner_id : None ,
} ;
match boat . update ( & pool , update ) . await {
Ok ( _ ) = > panic! ( " Update with invalid location should not succeed " ) ,
Err ( e ) = > assert_eq! (
e ,
" error returned from database: (code: 787) FOREIGN KEY constraint failed "
) ,
} ;
let boat = Boat ::find_by_id ( & pool , 1 ) . await . unwrap ( ) ;
assert_eq! ( boat . name , " Haichenbach " ) ;
}
2023-07-22 15:34:42 +02:00
}