2023-04-04 15:16:21 +02:00
use rocket ::{
2023-04-05 19:24:02 +02:00
catch , catchers ,
2023-05-30 14:12:08 +02:00
fairing ::AdHoc ,
2023-10-24 12:36:14 +02:00
form ::Form ,
2023-04-05 19:24:02 +02:00
fs ::FileServer ,
2023-10-24 12:36:14 +02:00
get , post ,
2023-04-04 15:16:21 +02:00
request ::FlashMessage ,
response ::{ Flash , Redirect } ,
2023-10-24 12:36:14 +02:00
routes , Build , FromForm , Rocket , State ,
2023-04-04 15:16:21 +02:00
} ;
2023-04-04 15:38:47 +02:00
use rocket_dyn_templates ::{ tera ::Context , Template } ;
2023-05-30 14:12:08 +02:00
use serde ::Deserialize ;
2023-04-03 16:11:26 +02:00
use sqlx ::SqlitePool ;
2023-04-05 22:01:50 +02:00
use crate ::model ::{
2023-04-18 12:10:11 +02:00
log ::Log ,
2023-04-28 19:08:17 +02:00
tripdetails ::TripDetails ,
2023-04-28 21:19:51 +02:00
triptype ::TripType ,
2023-12-23 21:27:52 +01:00
user ::{ User , UserWithRoles } ,
2023-08-09 11:54:18 +02:00
usertrip ::{ UserTrip , UserTripDeleteError , UserTripError } ,
2023-04-05 22:01:50 +02:00
} ;
2023-04-03 22:03:45 +02:00
2023-11-02 12:15:10 +01:00
pub ( crate ) mod admin ;
2023-04-03 16:11:26 +02:00
mod auth ;
2023-08-02 14:29:19 +02:00
mod boatdamage ;
2023-04-04 15:16:21 +02:00
mod cox ;
2023-11-02 12:15:10 +01:00
mod ergo ;
2023-07-23 12:17:57 +02:00
mod log ;
2023-05-24 15:36:38 +02:00
mod misc ;
2023-07-24 20:56:46 +02:00
mod stat ;
2023-03-26 14:40:56 +02:00
2023-10-24 13:14:26 +02:00
#[ derive(FromForm, Debug) ]
2023-10-24 12:36:14 +02:00
struct LoginForm < ' r > {
name : & ' r str ,
password : & ' r str ,
}
#[ post( " / " , data = " <login> " ) ]
async fn wikiauth ( db : & State < SqlitePool > , login : Form < LoginForm < '_ > > ) -> String {
match User ::login ( db , login . name , login . password ) . await {
Ok ( _ ) = > " SUCC " . into ( ) ,
Err ( _ ) = > " FAIL " . into ( ) ,
}
}
2023-04-04 15:42:26 +02:00
#[ get( " / " ) ]
async fn index ( db : & State < SqlitePool > , user : User , flash : Option < FlashMessage < '_ > > ) -> Template {
2023-04-28 21:19:51 +02:00
let mut context = Context ::new ( ) ;
2023-12-23 21:27:52 +01:00
if user . has_role ( db , " cox " ) . await | | user . has_role ( db , " admin " ) . await {
2023-04-28 21:19:51 +02:00
let triptypes = TripType ::all ( db ) . await ;
context . insert ( " trip_types " , & triptypes ) ;
}
2023-04-04 15:38:47 +02:00
2023-06-08 17:23:23 +02:00
let days = user . get_days ( db ) . await ;
2023-04-04 15:16:21 +02:00
if let Some ( msg ) = flash {
context . insert ( " flash " , & msg . into_inner ( ) ) ;
}
2023-12-23 21:27:52 +01:00
context . insert ( " loggedin_user " , & UserWithRoles ::from_user ( user , db ) . await ) ;
2023-04-04 15:16:21 +02:00
context . insert ( " days " , & days ) ;
Template ::render ( " index " , context . into_json ( ) )
}
2023-08-09 22:30:37 +02:00
#[ get( " /join/<trip_details_id>?<user_note> " ) ]
async fn join (
db : & State < SqlitePool > ,
trip_details_id : i64 ,
user : User ,
user_note : Option < String > ,
) -> Flash < Redirect > {
2023-07-31 21:07:01 +02:00
let Some ( trip_details ) = TripDetails ::find_by_id ( db , trip_details_id ) . await else {
return Flash ::error ( Redirect ::to ( " / " ) , " Trip_details do not exist. " ) ;
} ;
2023-05-30 14:47:44 +02:00
2023-08-09 22:30:37 +02:00
match UserTrip ::create ( db , & user , & trip_details , user_note ) . await {
2023-04-18 12:10:11 +02:00
Ok ( _ ) = > {
Log ::create (
db ,
format! (
" User {} registered for trip_details.id={} " ,
user . name , trip_details_id
) ,
)
. await ;
Flash ::success ( Redirect ::to ( " / " ) , " Erfolgreich angemeldet! " )
}
2023-04-05 22:01:50 +02:00
Err ( UserTripError ::EventAlreadyFull ) = > {
Flash ::error ( Redirect ::to ( " / " ) , " Event bereits ausgebucht! " )
}
Err ( UserTripError ::AlreadyRegistered ) = > {
Flash ::error ( Redirect ::to ( " / " ) , " Du nimmst bereits teil! " )
}
Err ( UserTripError ::AlreadyRegisteredAsCox ) = > {
Flash ::error ( Redirect ::to ( " / " ) , " Du hilfst bereits als Steuerperson aus! " )
}
2023-04-28 19:08:17 +02:00
Err ( UserTripError ::CantRegisterAtOwnEvent ) = > Flash ::error (
Redirect ::to ( " / " ) ,
" Du kannst bei einer selbst ausgeschriebenen Fahrt nicht mitrudern ;) " ,
) ,
2023-04-29 18:57:01 +02:00
Err ( UserTripError ::GuestNotAllowedForThisEvent ) = > Flash ::error (
Redirect ::to ( " / " ) ,
" Bei dieser Ausfahrt können leider keine Gäste mitfahren. " ,
) ,
2023-08-09 22:30:37 +02:00
Err ( UserTripError ::NotAllowedToAddGuest ) = > Flash ::error (
Redirect ::to ( " / " ) ,
" Du darfst keine Gäste hinzufügen. " ,
) ,
2023-08-09 11:54:18 +02:00
Err ( UserTripError ::DetailsLocked ) = > Flash ::error (
Redirect ::to ( " / " ) ,
" Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst. " ,
) ,
2023-04-04 15:16:21 +02:00
}
}
2023-08-09 22:30:37 +02:00
#[ get( " /remove/<trip_details_id>/<name> " ) ]
async fn remove_guest (
db : & State < SqlitePool > ,
trip_details_id : i64 ,
user : User ,
name : String ,
) -> Flash < Redirect > {
let Some ( trip_details ) = TripDetails ::find_by_id ( db , trip_details_id ) . await else {
return Flash ::error ( Redirect ::to ( " / " ) , " TripDetailsId does not exist " ) ;
} ;
match UserTrip ::delete ( db , & user , & trip_details , Some ( name ) ) . await {
Ok ( _ ) = > {
Log ::create (
db ,
format! (
" User {} unregistered for trip_details.id={} " ,
user . name , trip_details_id
) ,
)
. await ;
Flash ::success ( Redirect ::to ( " / " ) , " Erfolgreich abgemeldet! " )
}
Err ( UserTripDeleteError ::DetailsLocked ) = > {
Log ::create (
db ,
format! (
" User {} tried to unregister for locked trip_details.id={} " ,
user . name , trip_details_id
) ,
)
. await ;
Flash ::error ( Redirect ::to ( " / " ) , " Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst. " )
}
Err ( UserTripDeleteError ::GuestNotParticipating ) = > {
Flash ::error ( Redirect ::to ( " / " ) , " Gast nicht angemeldet. " )
}
Err ( UserTripDeleteError ::NotAllowedToDeleteGuest ) = > Flash ::error (
Redirect ::to ( " / " ) ,
" Keine Berechtigung um den Gast zu entfernen. " ,
) ,
}
}
2023-04-04 15:16:21 +02:00
#[ get( " /remove/<trip_details_id> " ) ]
async fn remove ( db : & State < SqlitePool > , trip_details_id : i64 , user : User ) -> Flash < Redirect > {
2023-05-30 14:47:44 +02:00
let Some ( trip_details ) = TripDetails ::find_by_id ( db , trip_details_id ) . await else {
2023-07-31 21:07:01 +02:00
return Flash ::error ( Redirect ::to ( " / " ) , " TripDetailsId does not exist " ) ;
} ;
2023-04-28 19:08:17 +02:00
2023-08-09 22:30:37 +02:00
match UserTrip ::delete ( db , & user , & trip_details , None ) . await {
2023-08-09 11:54:18 +02:00
Ok ( _ ) = > {
Log ::create (
db ,
format! (
" User {} unregistered for trip_details.id={} " ,
user . name , trip_details_id
) ,
)
. await ;
2023-04-04 15:16:21 +02:00
2023-08-09 11:54:18 +02:00
Flash ::success ( Redirect ::to ( " / " ) , " Erfolgreich abgemeldet! " )
}
Err ( UserTripDeleteError ::DetailsLocked ) = > {
Log ::create (
db ,
format! (
" User {} tried to unregister for locked trip_details.id={} " ,
user . name , trip_details_id
) ,
)
. await ;
2023-04-18 12:10:11 +02:00
2023-08-09 11:54:18 +02:00
Flash ::error ( Redirect ::to ( " / " ) , " Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst. " )
}
2023-08-09 22:30:37 +02:00
Err ( _ ) = > {
panic! ( " Not possible to be here " ) ;
}
2023-08-09 11:54:18 +02:00
}
2023-03-26 14:40:56 +02:00
}
2023-04-03 22:03:45 +02:00
#[ catch(401) ] //unauthorized
fn unauthorized_error ( ) -> Redirect {
Redirect ::to ( " /auth " )
}
2023-05-30 14:12:08 +02:00
#[ derive(Deserialize) ]
#[ serde(crate = " rocket::serde " ) ]
pub struct Config {
rss_key : String ,
2024-01-01 15:50:06 +01:00
smtp_pw : String ,
2023-05-30 14:12:08 +02:00
}
2023-07-16 18:33:17 +02:00
pub fn config ( rocket : Rocket < Build > ) -> Rocket < Build > {
rocket
2023-08-09 22:30:37 +02:00
. mount ( " / " , routes! [ index , join , remove , remove_guest ] )
2023-04-03 16:11:26 +02:00
. mount ( " /auth " , auth ::routes ( ) )
2023-10-24 12:36:14 +02:00
. mount ( " /wikiauth " , routes! [ wikiauth ] )
2023-07-23 12:17:57 +02:00
. mount ( " /log " , log ::routes ( ) )
2023-11-02 12:15:10 +01:00
. mount ( " /ergo " , ergo ::routes ( ) )
2023-07-24 20:56:46 +02:00
. mount ( " /stat " , stat ::routes ( ) )
2023-08-02 14:29:19 +02:00
. mount ( " /boatdamage " , boatdamage ::routes ( ) )
2023-04-04 15:16:21 +02:00
. mount ( " /cox " , cox ::routes ( ) )
2023-04-04 10:44:14 +02:00
. mount ( " /admin " , admin ::routes ( ) )
2023-05-24 15:36:38 +02:00
. mount ( " / " , misc ::routes ( ) )
2023-04-10 15:15:16 +02:00
. mount ( " /public " , FileServer ::from ( " static/ " ) )
2023-04-03 22:03:45 +02:00
. register ( " / " , catchers! [ unauthorized_error ] )
2023-03-26 16:58:45 +02:00
. attach ( Template ::fairing ( ) )
2023-05-30 14:12:08 +02:00
. attach ( AdHoc ::config ::< Config > ( ) )
2023-03-26 14:40:56 +02:00
}
2023-07-22 12:24:29 +02:00
#[ cfg(test) ]
mod test {
use rocket ::{
http ::{ ContentType , Status } ,
local ::asynchronous ::Client ,
} ;
use sqlx ::SqlitePool ;
use crate ::testdb ;
#[ sqlx::test ]
fn test_index ( ) {
let db = testdb! ( ) ;
let rocket = rocket ::build ( ) . manage ( db . clone ( ) ) ;
let rocket = crate ::tera ::config ( rocket ) ;
let client = Client ::tracked ( rocket ) . await . unwrap ( ) ;
let login = client
. post ( " /auth " )
. header ( ContentType ::Form ) // Set the content type to form
. body ( " name=cox&password=cox " ) ; // Add the form data to the request body;
login . dispatch ( ) . await ;
let req = client . get ( " / " ) ;
let response = req . dispatch ( ) . await ;
assert_eq! ( response . status ( ) , Status ::Ok ) ;
assert! ( response . into_string ( ) . await . unwrap ( ) . contains ( " Ausfahrten " ) ) ;
}
#[ sqlx::test ]
fn test_without_login ( ) {
let db = testdb! ( ) ;
let rocket = rocket ::build ( ) . manage ( db . clone ( ) ) ;
let rocket = crate ::tera ::config ( rocket ) ;
let client = Client ::tracked ( rocket ) . await . unwrap ( ) ;
let req = client . get ( " / " ) ;
let response = req . dispatch ( ) . await ;
assert_eq! ( response . status ( ) , Status ::SeeOther ) ;
assert_eq! ( response . headers ( ) . get ( " Location " ) . next ( ) , Some ( " /auth " ) ) ;
}
#[ sqlx::test ]
fn test_join_and_remove ( ) {
let db = testdb! ( ) ;
let rocket = rocket ::build ( ) . manage ( db . clone ( ) ) ;
let rocket = crate ::tera ::config ( rocket ) ;
let client = Client ::tracked ( rocket ) . await . unwrap ( ) ;
let login = client
. post ( " /auth " )
. header ( ContentType ::Form ) // Set the content type to form
. body ( " name=rower&password=rower " ) ; // Add the form data to the request body;
login . dispatch ( ) . await ;
let req = client . get ( " /join/1 " ) ;
let response = req . dispatch ( ) . await ;
assert_eq! ( response . status ( ) , Status ::SeeOther ) ;
assert_eq! ( response . headers ( ) . get ( " Location " ) . next ( ) , Some ( " / " ) ) ;
let flash_cookie = response
. cookies ( )
. get ( " _flash " )
. expect ( " Expected flash cookie " ) ;
assert_eq! ( flash_cookie . value ( ) , " 7:successErfolgreich angemeldet! " ) ;
let req = client . get ( " /remove/1 " ) ;
let response = req . dispatch ( ) . await ;
assert_eq! ( response . status ( ) , Status ::SeeOther ) ;
assert_eq! ( response . headers ( ) . get ( " Location " ) . next ( ) , Some ( " / " ) ) ;
let flash_cookie = response
. cookies ( )
. get ( " _flash " )
. expect ( " Expected flash cookie " ) ;
assert_eq! ( flash_cookie . value ( ) , " 7:successErfolgreich abgemeldet! " ) ;
}
#[ sqlx::test ]
fn test_join_invalid_event ( ) {
let db = testdb! ( ) ;
let rocket = rocket ::build ( ) . manage ( db . clone ( ) ) ;
let rocket = crate ::tera ::config ( rocket ) ;
let client = Client ::tracked ( rocket ) . await . unwrap ( ) ;
let login = client
. post ( " /auth " )
. header ( ContentType ::Form ) // Set the content type to form
. body ( " name=rower&password=rower " ) ; // Add the form data to the request body;
login . dispatch ( ) . await ;
let req = client . get ( " /join/9999 " ) ;
let response = req . dispatch ( ) . await ;
assert_eq! ( response . status ( ) , Status ::SeeOther ) ;
assert_eq! ( response . headers ( ) . get ( " Location " ) . next ( ) , Some ( " / " ) ) ;
let flash_cookie = response
. cookies ( )
. get ( " _flash " )
. expect ( " Expected flash cookie " ) ;
assert_eq! ( flash_cookie . value ( ) , " 5:errorTrip_details do not exist. " ) ;
}
2023-07-31 21:07:01 +02:00
#[ sqlx::test ]
fn test_public ( ) {
let db = testdb! ( ) ;
let rocket = rocket ::build ( ) . manage ( db . clone ( ) ) ;
let rocket = crate ::tera ::config ( rocket ) ;
let client = Client ::tracked ( rocket ) . await . unwrap ( ) ;
let req = client . get ( " /public/main.css " ) ;
let response = req . dispatch ( ) . await ;
assert_eq! ( response . status ( ) , Status ::Ok ) ;
let req = client . get ( " /public/main.js " ) ;
let response = req . dispatch ( ) . await ;
assert_eq! ( response . status ( ) , Status ::Ok ) ;
}
2023-07-22 12:24:29 +02:00
}