2024-04-16 08:52:37 +02:00
use std ::{ fs ::OpenOptions , io ::Write } ;
use chrono ::Local ;
2023-04-04 15:16:21 +02:00
use rocket ::{
2023-04-05 19:24:02 +02:00
catch , catchers ,
2024-04-16 08:52:37 +02:00
fairing ::{ AdHoc , Fairing , Info , Kind } ,
2023-10-24 12:36:14 +02:00
form ::Form ,
2023-04-05 19:24:02 +02:00
fs ::FileServer ,
2024-01-22 22:08:05 +01:00
get ,
http ::Cookie ,
post ,
2023-04-04 15:16:21 +02:00
request ::FlashMessage ,
response ::{ Flash , Redirect } ,
2024-01-22 22:08:05 +01:00
routes ,
time ::{ Duration , OffsetDateTime } ,
2024-04-16 08:52:37 +02:00
Build , Data , FromForm , Request , Rocket , State ,
2023-04-04 15:16:21 +02:00
} ;
2024-01-10 14:08:15 +01:00
use rocket_dyn_templates ::Template ;
2023-05-30 14:12:08 +02:00
use serde ::Deserialize ;
2023-04-03 16:11:26 +02:00
use sqlx ::SqlitePool ;
2024-01-10 14:08:15 +01:00
use tera ::Context ;
2023-04-03 16:11:26 +02:00
2024-03-20 16:19:12 +01:00
use crate ::model ::{
2024-05-16 22:35:26 +02:00
logbook ::Logbook ,
2024-03-20 16:19:12 +01:00
notification ::Notification ,
2024-04-06 18:27:20 +02:00
role ::Role ,
2024-05-16 22:35:26 +02:00
user ::{ User , UserWithDetails , SCHECKBUCH } ,
2024-03-20 16:19:12 +01: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 ;
2024-03-08 13:13:20 +01:00
pub ( crate ) mod board ;
2023-08-02 14:29:19 +02:00
mod boatdamage ;
2024-04-23 22:23:24 +02:00
pub ( crate ) mod boatreservation ;
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 ;
2024-03-20 16:19:12 +01:00
mod notification ;
2024-01-10 14:08:15 +01:00
mod planned ;
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 ,
}
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-04-04 15:16:21 +02:00
if let Some ( msg ) = flash {
context . insert ( " flash " , & msg . into_inner ( ) ) ;
}
2024-01-19 10:10:23 +01:00
2024-05-16 22:35:26 +02:00
if user . has_role ( db , " scheckbuch " ) . await {
let last_trips = Logbook ::completed_with_user ( db , & user ) . await ;
context . insert ( " last_trips " , & last_trips ) ;
}
2024-03-20 16:19:12 +01:00
context . insert ( " notifications " , & Notification ::for_user ( db , & user ) . await ) ;
2024-05-06 12:17:03 +02:00
context . insert ( " loggedin_user " , & UserWithDetails ::from_user ( user , db ) . await ) ;
2024-05-16 22:35:26 +02:00
context . insert ( " costs_scheckbuch " , & SCHECKBUCH ) ;
2023-04-04 15:16:21 +02:00
Template ::render ( " index " , context . into_json ( ) )
}
2024-05-17 12:26:10 +02:00
#[ get( " /impressum " ) ]
async fn impressum ( db : & State < SqlitePool > , user : Option < User > ) -> Template {
let mut context = Context ::new ( ) ;
if let Some ( user ) = user {
context . insert ( " loggedin_user " , & UserWithDetails ::from_user ( user , db ) . await ) ;
}
Template ::render ( " impressum " , context . into_json ( ) )
}
2024-04-06 18:27:20 +02:00
#[ get( " /steering " ) ]
async fn steering ( db : & State < SqlitePool > , user : User , flash : Option < FlashMessage < '_ > > ) -> Template {
let mut context = Context ::new ( ) ;
if let Some ( msg ) = flash {
context . insert ( " flash " , & msg . into_inner ( ) ) ;
}
let bootskundige =
User ::all_with_role ( db , & Role ::find_by_name ( db , " Bootsführer " ) . await . unwrap ( ) ) . await ;
let mut coxes = User ::all_with_role ( db , & Role ::find_by_name ( db , " cox " ) . await . unwrap ( ) ) . await ;
2024-04-15 23:26:52 +02:00
coxes . retain ( | user | ! bootskundige . contains ( user ) ) ; // Remove bootskundige from coxes list
coxes . retain ( | user | user . name ! = " Externe Steuerperson " ) ;
2024-04-06 18:27:20 +02:00
context . insert ( " coxes " , & coxes ) ;
context . insert ( " bootskundige " , & bootskundige ) ;
2024-05-06 12:17:03 +02:00
context . insert ( " loggedin_user " , & UserWithDetails ::from_user ( user , db ) . await ) ;
2024-04-06 18:27:20 +02:00
Template ::render ( " steering " , context . into_json ( ) )
}
2024-01-10 14:08:15 +01:00
#[ 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-08-09 11:54:18 +02:00
}
2023-03-26 14:40:56 +02:00
}
2024-01-10 14:08:15 +01:00
#[ catch(401) ] //Unauthorized
2024-01-22 22:08:05 +01:00
fn unauthorized_error ( req : & Request ) -> Redirect {
// Save the URL the user tried to access, to be able to go there once logged in
let mut redirect_cookie = Cookie ::new ( " redirect_url " , format! ( " {} " , req . uri ( ) ) ) ;
println! ( " {} " , req . uri ( ) ) ;
redirect_cookie . set_expires ( OffsetDateTime ::now_utc ( ) + Duration ::hours ( 1 ) ) ;
req . cookies ( ) . add_private ( redirect_cookie ) ;
2023-04-03 22:03:45 +02:00
Redirect ::to ( " /auth " )
}
2024-01-10 14:08:15 +01:00
#[ catch(403) ] //forbidden
fn forbidden_error ( ) -> Flash < Redirect > {
Flash ::error ( Redirect ::to ( " / " ) , " Keine Berechtigung für diese Aktion. Wenn du der Meinung bist, dass du das machen darfst, melde dich bitte bei it@rudernlinz.at. " )
}
2024-04-30 21:35:14 +02:00
struct Usage { }
2024-04-16 08:52:37 +02:00
#[ rocket::async_trait ]
impl Fairing for Usage {
fn info ( & self ) -> Info {
Info {
name : " Usage stats of website " ,
kind : Kind ::Request ,
}
}
// Increment the counter for `GET` and `POST` requests.
async fn on_request ( & self , req : & mut Request < '_ > , _ : & mut Data < '_ > ) {
let timestamp = Local ::now ( ) . format ( " %Y-%m-%dT%H:%M:%S " ) ;
let user = match req . cookies ( ) . get_private ( " loggedin_user " ) {
Some ( user_id ) = > match user_id . value ( ) . parse ::< i32 > ( ) {
Ok ( user_id ) = > {
let db = req . rocket ( ) . state ::< SqlitePool > ( ) . unwrap ( ) ;
if let Some ( user ) = User ::find_by_id ( db , user_id ) . await {
2024-04-16 09:59:39 +02:00
format! ( " User: {} " , user . name )
2024-04-16 08:52:37 +02:00
} else {
format! ( " USER ID {user_id} NOT EXISTS " )
}
}
Err ( _ ) = > format! ( " INVALID USER ID ( {user_id} ) " ) ,
} ,
None = > " NOT LOGGED IN " . to_string ( ) ,
} ;
let uri = req . uri ( ) . to_string ( ) ;
2024-04-16 09:59:39 +02:00
if ! uri . ends_with ( " .css " )
& & ! uri . ends_with ( " .js " )
& & ! uri . ends_with ( " .ico " )
& & ! uri . ends_with ( " .json " )
2024-04-17 12:21:57 +02:00
& & ! uri . ends_with ( " .png " )
2024-04-16 09:59:39 +02:00
{
2024-04-16 08:52:37 +02:00
let config = req . rocket ( ) . state ::< Config > ( ) . unwrap ( ) ;
2024-04-16 09:06:24 +02:00
let Ok ( mut file ) = OpenOptions ::new ( )
2024-04-16 08:52:37 +02:00
. append ( true )
. open ( config . usage_log_path . clone ( ) )
2024-04-16 09:06:24 +02:00
else {
eprintln! (
" File {} can't be found, not saving usage logs " ,
config . usage_log_path . clone ( )
) ;
return ;
} ;
2024-04-16 08:52:37 +02:00
if let Err ( e ) = writeln! ( file , " {timestamp};{user};{uri} " ) {
eprintln! ( " Couldn't write to file: {} " , e ) ;
}
}
}
}
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 ,
2024-04-16 08:52:37 +02:00
usage_log_path : String ,
2024-05-16 14:41:15 +02:00
pub openweathermap_key : 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
2024-05-17 12:26:10 +02:00
. mount ( " / " , routes! [ index , steering , impressum ] )
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 ( ) )
2024-01-10 14:08:15 +01:00
. mount ( " /planned " , planned ::routes ( ) )
2023-11-02 12:15:10 +01:00
. mount ( " /ergo " , ergo ::routes ( ) )
2024-03-20 16:19:12 +01:00
. mount ( " /notification " , notification ::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 ( ) )
2024-03-30 01:36:37 +01:00
. mount ( " /boatreservation " , boatreservation ::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 ( ) )
2024-03-08 13:13:20 +01:00
. mount ( " /board " , board ::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/ " ) )
2024-01-10 14:08:15 +01:00
. register ( " / " , catchers! [ unauthorized_error , forbidden_error ] )
2023-03-26 16:58:45 +02:00
. attach ( Template ::fairing ( ) )
2023-05-30 14:12:08 +02:00
. attach ( AdHoc ::config ::< Config > ( ) )
2024-04-30 21:35:14 +02:00
. attach ( Usage { } )
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 ) ;
2024-01-10 14:08:15 +01:00
assert! ( response
. into_string ( )
. await
. unwrap ( )
. contains ( " Ruderassistent " ) ) ;
2023-07-22 12:24:29 +02:00
}
#[ 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 " ) ) ;
}
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
}