2023-07-30 20:40:15 +02:00
use chrono ::{ Local , NaiveDateTime , TimeZone } ;
2023-07-25 13:22:11 +02:00
use rocket ::FromForm ;
2023-07-24 13:01:39 +02:00
use serde ::Serialize ;
2023-07-25 13:22:11 +02:00
use sqlx ::{ FromRow , Sqlite , SqlitePool , Transaction } ;
2023-07-23 12:17:57 +02:00
2023-07-24 13:01:39 +02:00
use super ::{ boat ::Boat , rower ::Rower , user ::User } ;
2023-07-23 16:49:14 +02:00
2023-07-24 13:01:39 +02:00
#[ derive(FromRow, Serialize, Clone) ]
2023-07-23 12:17:57 +02:00
pub struct Logbook {
pub id : i64 ,
pub boat_id : i64 ,
pub shipmaster : i64 ,
#[ serde(default = " bool::default " ) ]
pub shipmaster_only_steering : bool ,
pub departure : String , //TODO: Switch to chrono::nativedatetime
pub arrival : Option < String > , //TODO: Switch to chrono::nativedatetime
pub destination : Option < String > ,
pub distance_in_km : Option < i64 > ,
pub comments : Option < String > ,
pub logtype : Option < i64 > ,
}
2023-07-25 13:22:11 +02:00
#[ derive(FromForm) ]
pub struct LogToAdd {
pub boat_id : i32 ,
pub shipmaster : i64 ,
pub shipmaster_only_steering : bool ,
pub departure : String ,
pub arrival : Option < String > ,
pub destination : Option < String > ,
pub distance_in_km : Option < i64 > ,
pub comments : Option < String > ,
pub logtype : Option < i64 > ,
pub rower : Vec < i64 > ,
}
#[ derive(FromForm) ]
pub struct LogToFinalize {
pub destination : String ,
pub distance_in_km : i64 ,
pub comments : Option < String > ,
pub logtype : Option < i64 > ,
pub rower : Vec < i64 > ,
}
2023-07-24 13:01:39 +02:00
#[ derive(Serialize) ]
pub struct LogbookWithBoatAndRowers {
#[ serde(flatten) ]
pub logbook : Logbook ,
pub boat : Boat ,
pub shipmaster_user : User ,
pub rowers : Vec < User > ,
2023-07-30 20:40:15 +02:00
pub departure_timestamp : i64 ,
pub arrival_timestamp : Option < i64 > ,
2023-07-23 12:17:57 +02:00
}
2023-07-23 16:49:14 +02:00
pub enum LogbookUpdateError {
NotYourEntry ,
2023-07-24 20:56:46 +02:00
TooManyRowers ( usize , usize ) ,
2023-07-23 16:49:14 +02:00
}
pub enum LogbookCreateError {
BoatAlreadyOnWater ,
2023-07-23 19:45:48 +02:00
BoatLocked ,
2023-07-24 13:01:39 +02:00
BoatNotFound ,
TooManyRowers ( usize , usize ) ,
2023-07-27 15:00:52 +02:00
ShipmasterAlreadyOnWater ,
2023-07-27 15:24:29 +02:00
RowerAlreadyOnWater ( User ) ,
2023-07-23 16:49:14 +02:00
}
2023-07-23 12:17:57 +02:00
impl Logbook {
2023-07-23 16:49:14 +02:00
pub async fn find_by_id ( db : & SqlitePool , id : i32 ) -> Option < Self > {
sqlx ::query_as! (
Self ,
"
SELECT id , boat_id , shipmaster , shipmaster_only_steering , departure , arrival , destination , distance_in_km , comments , logtype
FROM logbook
WHERE id like ?
" ,
id
)
. fetch_one ( db )
. await
. ok ( )
}
2023-07-24 13:01:39 +02:00
pub async fn on_water ( db : & SqlitePool ) -> Vec < LogbookWithBoatAndRowers > {
let logs = sqlx ::query_as! (
Logbook ,
2023-07-23 12:17:57 +02:00
"
2023-07-24 13:01:39 +02:00
SELECT id , boat_id , shipmaster , shipmaster_only_steering , departure , arrival , destination , distance_in_km , comments , logtype
2023-07-23 12:17:57 +02:00
FROM logbook
WHERE arrival is null
ORDER BY departure DESC
"
)
. fetch_all ( db )
. await
2023-07-24 13:01:39 +02:00
. unwrap ( ) ; //TODO: fixme
let mut ret = Vec ::new ( ) ;
for log in logs {
2023-07-30 20:40:15 +02:00
let date_time_naive =
NaiveDateTime ::parse_from_str ( & log . departure , " %Y-%m-%d %H:%M:%S " ) . unwrap ( ) ;
let date_time = Local
. from_local_datetime ( & date_time_naive )
. single ( )
. unwrap ( ) ;
2023-07-24 13:01:39 +02:00
ret . push ( LogbookWithBoatAndRowers {
rowers : Rower ::for_log ( db , & log ) . await ,
boat : Boat ::find_by_id ( db , log . boat_id as i32 ) . await . unwrap ( ) ,
shipmaster_user : User ::find_by_id ( db , log . shipmaster as i32 ) . await . unwrap ( ) ,
logbook : log ,
2023-07-30 20:40:15 +02:00
arrival_timestamp : None , //TODO: send arrival timestmap
departure_timestamp : date_time . timestamp ( ) ,
2023-07-25 13:32:20 +02:00
} ) ;
2023-07-24 13:01:39 +02:00
}
ret
2023-07-23 12:17:57 +02:00
}
2023-07-24 13:01:39 +02:00
pub async fn completed ( db : & SqlitePool ) -> Vec < LogbookWithBoatAndRowers > {
let logs = sqlx ::query_as! (
Logbook ,
2023-07-23 12:17:57 +02:00
"
2023-07-24 13:01:39 +02:00
SELECT id , boat_id , shipmaster , shipmaster_only_steering , departure , arrival , destination , distance_in_km , comments , logtype
2023-07-23 12:17:57 +02:00
FROM logbook
WHERE arrival is not null
2023-07-24 13:01:39 +02:00
ORDER BY departure DESC
2023-07-23 12:17:57 +02:00
"
)
. fetch_all ( db )
. await
2023-07-24 13:01:39 +02:00
. unwrap ( ) ; //TODO: fixme
let mut ret = Vec ::new ( ) ;
for log in logs {
ret . push ( LogbookWithBoatAndRowers {
rowers : Rower ::for_log ( db , & log ) . await ,
boat : Boat ::find_by_id ( db , log . boat_id as i32 ) . await . unwrap ( ) ,
shipmaster_user : User ::find_by_id ( db , log . shipmaster as i32 ) . await . unwrap ( ) ,
logbook : log ,
2023-07-30 20:40:15 +02:00
arrival_timestamp : None ,
departure_timestamp : 0 ,
2023-07-25 13:32:20 +02:00
} ) ;
2023-07-24 13:01:39 +02:00
}
ret
2023-07-23 12:17:57 +02:00
}
2023-07-25 13:22:11 +02:00
pub async fn create ( db : & SqlitePool , log : LogToAdd ) -> Result < ( ) , LogbookCreateError > {
2023-07-25 13:32:20 +02:00
let Some ( boat ) = Boat ::find_by_id ( db , log . boat_id ) . await else {
return Err ( LogbookCreateError ::BoatNotFound ) ;
} ;
2023-07-24 13:01:39 +02:00
if boat . is_locked ( db ) . await {
return Err ( LogbookCreateError ::BoatLocked ) ;
}
if boat . on_water ( db ) . await {
return Err ( LogbookCreateError ::BoatAlreadyOnWater ) ;
}
2023-07-30 20:40:15 +02:00
if ( User ::find_by_id ( db , log . shipmaster as i32 ) . await . unwrap ( ) )
. on_water ( db )
. await
{
2023-07-27 15:24:29 +02:00
return Err ( LogbookCreateError ::ShipmasterAlreadyOnWater ) ;
2023-07-27 15:00:52 +02:00
}
2023-07-30 20:40:15 +02:00
2023-07-25 13:22:11 +02:00
if log . rower . len ( ) > boat . amount_seats as usize - 1 {
2023-07-24 13:01:39 +02:00
return Err ( LogbookCreateError ::TooManyRowers (
boat . amount_seats as usize ,
2023-07-25 13:22:11 +02:00
log . rower . len ( ) + 1 ,
2023-07-24 13:01:39 +02:00
) ) ;
}
2023-07-27 15:24:29 +02:00
for rower in & log . rower {
let user = User ::find_by_id ( db , * rower as i32 ) . await . unwrap ( ) ;
if user . on_water ( db ) . await {
return Err ( LogbookCreateError ::RowerAlreadyOnWater ( user ) ) ;
}
}
2023-07-25 13:22:11 +02:00
let mut tx = db . begin ( ) . await . unwrap ( ) ;
let departure = NaiveDateTime ::parse_from_str ( & log . departure , " %Y-%m-%dT%H:%M " ) . unwrap ( ) ;
let arrival = log
. arrival
. map ( | a | NaiveDateTime ::parse_from_str ( & a , " %Y-%m-%dT%H:%M " ) . unwrap ( ) ) ;
2023-07-24 13:01:39 +02:00
let inserted_row = sqlx ::query! (
" INSERT INTO logbook(boat_id, shipmaster, shipmaster_only_steering, departure, arrival, destination, distance_in_km, comments, logtype) VALUES (?,?,?,?,?,?,?,?,?) RETURNING id " ,
2023-07-25 13:22:11 +02:00
log . boat_id ,
log . shipmaster ,
log . shipmaster_only_steering ,
departure ,
arrival ,
log . destination ,
log . distance_in_km ,
log . comments ,
log . logtype
2023-07-23 12:17:57 +02:00
)
2023-07-25 13:22:11 +02:00
. fetch_one ( & mut tx )
2023-07-24 13:01:39 +02:00
. await . unwrap ( ) ;
2023-07-25 13:22:11 +02:00
for rower in & log . rower {
Rower ::create ( & mut tx , inserted_row . id , * rower ) . await ;
2023-07-24 13:01:39 +02:00
}
2023-07-25 13:22:11 +02:00
tx . commit ( ) . await . unwrap ( ) ;
2023-07-23 16:49:14 +02:00
Ok ( ( ) )
2023-07-23 12:17:57 +02:00
}
2023-07-23 16:49:14 +02:00
2023-07-30 20:40:15 +02:00
pub async fn distances ( db : & SqlitePool ) -> Vec < ( String , i64 ) > {
2023-07-26 12:56:19 +02:00
let result = sqlx ::query! ( " SELECT destination, distance_in_km FROM logbook WHERE id IN (SELECT MIN(id) FROM logbook GROUP BY destination) AND destination IS NOT NULL AND distance_in_km IS NOT NULL; " )
. fetch_all ( db )
. await
. unwrap ( ) ;
2023-07-30 20:40:15 +02:00
result
. into_iter ( )
. filter_map ( | r | {
if let ( Some ( destination ) , Some ( distance_in_km ) ) = ( r . destination , r . distance_in_km )
{
Some ( ( destination , distance_in_km ) )
} else {
None
}
} )
. collect ( )
2023-07-26 12:56:19 +02:00
}
2023-07-25 13:22:11 +02:00
async fn remove_rowers ( & self , db : & mut Transaction < '_ , Sqlite > ) {
2023-07-24 20:56:46 +02:00
sqlx ::query! ( " DELETE FROM rower WHERE logbook_id=? " , self . id )
. execute ( db )
. await
. unwrap ( ) ;
}
2023-07-23 16:49:14 +02:00
pub async fn home (
& self ,
db : & SqlitePool ,
user : & User ,
2023-07-25 13:22:11 +02:00
log : LogToFinalize ,
2023-07-23 16:49:14 +02:00
) -> Result < ( ) , LogbookUpdateError > {
if user . id ! = self . shipmaster {
return Err ( LogbookUpdateError ::NotYourEntry ) ;
}
2023-07-24 20:56:46 +02:00
let boat = Boat ::find_by_id ( db , self . boat_id as i32 ) . await . unwrap ( ) ; //ok
2023-07-25 13:22:11 +02:00
if log . rower . len ( ) > boat . amount_seats as usize - 1 {
2023-07-24 20:56:46 +02:00
return Err ( LogbookUpdateError ::TooManyRowers (
boat . amount_seats as usize ,
2023-07-25 13:22:11 +02:00
log . rower . len ( ) + 1 ,
2023-07-24 20:56:46 +02:00
) ) ;
}
2023-07-23 16:49:14 +02:00
let arrival = format! ( " {} " , chrono ::offset ::Local ::now ( ) . format ( " %Y-%m-%d %H:%M " ) ) ;
2023-07-25 13:22:11 +02:00
let mut tx = db . begin ( ) . await . unwrap ( ) ;
2023-07-23 16:49:14 +02:00
sqlx ::query! (
" UPDATE logbook SET destination=?, distance_in_km=?, comments=?, logtype=?, arrival=? WHERE id=? " ,
2023-07-25 13:22:11 +02:00
log . destination ,
log . distance_in_km ,
log . comments ,
log . logtype ,
2023-07-23 16:49:14 +02:00
arrival ,
self . id
)
2023-07-25 13:22:11 +02:00
. execute ( & mut tx )
2023-07-23 16:49:14 +02:00
. await . unwrap ( ) ; //TODO: fixme
2023-07-24 20:56:46 +02:00
2023-07-25 13:22:11 +02:00
self . remove_rowers ( & mut tx ) . await ;
2023-07-24 20:56:46 +02:00
2023-07-25 13:22:11 +02:00
for rower in & log . rower {
Rower ::create ( & mut tx , self . id , * rower ) . await ;
2023-07-24 20:56:46 +02:00
}
2023-07-25 13:22:11 +02:00
tx . commit ( ) . await . unwrap ( ) ;
2023-07-23 16:49:14 +02:00
Ok ( ( ) )
}
2023-07-23 12:17:57 +02:00
// 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 User of a valid id
// }
}
//
//#[cfg(test)]
//mod test {
// use crate::{model::boat::Boat, 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,
// "new-boat-name".into(),
// 42,
// None,
// "Best Boatbuilder".into(),
// true,
// true,
// false,
// Some(1),
// None
// )
// .await,
// true
// );
// }
//
// #[sqlx::test]
// fn test_duplicate_name_create() {
// let pool = testdb!();
//
// assert_eq!(
// Boat::create(
// &pool,
// "Haichenbach".into(),
// 42,
// None,
// "Best Boatbuilder".into(),
// true,
// true,
// false,
// Some(1),
// None
// )
// .await,
// false
// );
// }
//}