@@ -2,24 +2,25 @@ pub mod routes;
22pub mod session;
33pub mod webext;
44
5- use std:: sync:: Arc ;
6-
7- use crate :: app:: Liwan ;
8- use crate :: app:: models:: Event ;
9- use routes:: { dashboard_service, event_service} ;
10- use std:: sync:: mpsc:: Sender ;
11- use webext:: { EmbeddedFilesEndpoint , PoemErrExt , catch_error} ;
12-
13- pub use session:: SessionUser ;
5+ use std:: net:: SocketAddr ;
6+ use std:: ops:: Deref ;
7+ use std:: sync:: { Arc , mpsc:: Sender } ;
148
159use anyhow:: { Context , Result } ;
1610use rust_embed:: RustEmbed ;
1711
18- use poem:: endpoint:: EmbeddedFileEndpoint ;
19- use poem:: listener:: TcpListener ;
20- use poem:: middleware:: { AddData , Compression , CookieJarManager , Cors , SetHeader } ;
21- use poem:: web:: CompressionAlgo ;
22- use poem:: { EndpointExt , IntoEndpoint , Route , Server } ;
12+ use aide:: { axum:: ApiRouter , openapi} ;
13+ use http:: { Method , header} ;
14+ use tower_http:: {
15+ compression:: CompressionLayer ,
16+ cors:: { Any , CorsLayer } ,
17+ set_header:: SetResponseHeaderLayer ,
18+ } ;
19+
20+ use crate :: app:: { Liwan , models:: Event } ;
21+
22+ pub use session:: SessionUser ;
23+ use webext:: StaticFile ;
2324
2425#[ derive( RustEmbed , Clone ) ]
2526#[ folder = "./web/dist" ]
@@ -29,69 +30,46 @@ struct Files;
2930#[ folder = "./tracker" ]
3031struct Script ;
3132
32- #[ cfg( debug_assertions) ]
33- fn save_spec ( ) -> Result < ( ) > {
34- use std:: path:: Path ;
35-
36- let path = Path :: new ( "./web/src/api/dashboard.ts" ) ;
37- if path. exists ( ) {
38- let spec = serde_json:: to_string ( & serde_json:: from_str :: < serde_json:: Value > ( & dashboard_service ( ) . spec ( ) ) ?) ?
39- . replace ( r#""servers":[],"# , "" ) // fets doesn't work with an empty servers array
40- . replace ( "; charset=utf-8" , "" ) // fets doesn't detect the json content type correctly
41- . replace ( r#""format":"int64","# , "" ) ; // fets uses bigint for int64
42-
43- // check if the spec has changed
44- let old_spec = std:: fs:: read_to_string ( path) ?;
45- if old_spec == format ! ( "export default {spec} as const;\n " ) {
46- return Ok ( ( ) ) ;
47- }
48-
49- tracing:: info!( "API has changed, updating the openapi spec..." ) ;
50- std:: fs:: write ( path, format ! ( "export default {spec} as const;\n " ) ) ?;
51- }
52- Ok ( ( ) )
33+ #[ derive( Clone ) ]
34+ pub struct RouterState {
35+ pub app : Arc < Liwan > ,
36+ pub events : Sender < Event > ,
5337}
5438
55- pub fn create_router ( app : Arc < Liwan > , events : Sender < Event > ) -> impl IntoEndpoint {
56- let handle_events = event_service ( ) . with ( Cors :: new ( ) . allow_method ( "POST" ) . allow_credentials ( false ) ) ;
57-
58- let serve_script = EmbeddedFileEndpoint :: < Script > :: new ( "script.min.js" )
59- . with ( Cors :: new ( ) . allow_method ( "GET" ) . allow_credentials ( false ) )
60- . with ( SetHeader :: new ( ) . appending ( "Content-Type" , "application/javascript" ) ) ;
39+ impl Deref for RouterState {
40+ type Target = Arc < Liwan > ;
6141
62- let headers = SetHeader :: new ( )
63- . appending ( "X-Frame-Options" , "DENY" )
64- . appending ( "X-Content-Type-Options" , "nosniff" )
65- . appending ( "X-XSS-Protection" , "1; mode=block" )
66- . appending (
67- "Content-Security-Policy" ,
68- "default-src 'self' data: 'unsafe-inline'; img-src 'self' data: https://*" ,
69- )
70- . appending ( "Referrer-Policy" , "same-origin" )
71- . appending ( "Permissions-Policy" , "geolocation=(), microphone=(), camera=()" ) ;
72-
73- let api_router = Route :: new ( )
74- . nest_no_strip ( "/event" , handle_events)
75- . nest ( "/dashboard" , dashboard_service ( ) . with ( CookieJarManager :: new ( ) ) )
76- . catch_all_error ( catch_error) ;
77-
78- Route :: new ( )
79- . nest ( "/api" , api_router)
80- . at ( "/script.js" , serve_script)
81- . nest ( "/" , EmbeddedFilesEndpoint :: < Files > :: new ( ) )
82- . with ( AddData :: new ( app) )
83- . with ( AddData :: new ( events) )
84- . with ( CookieJarManager :: new ( ) )
85- . with ( Compression :: new ( ) . algorithms ( [ CompressionAlgo :: BR , CompressionAlgo :: GZIP ] ) )
86- . with ( headers)
42+ fn deref ( & self ) -> & Self :: Target {
43+ & self . app
44+ }
8745}
8846
8947pub async fn start_webserver ( app : Arc < Liwan > , events : Sender < Event > ) -> Result < ( ) > {
90- #[ cfg( debug_assertions) ]
91- save_spec ( ) ?;
92-
93- let router = create_router ( app. clone ( ) , events. clone ( ) ) ;
94- let listener = TcpListener :: bind ( ( "0.0.0.0" , app. config . port ) ) ;
48+ let event_cors = CorsLayer :: new ( ) . allow_methods ( [ Method :: POST ] ) . allow_origin ( Any ) . allow_credentials ( false ) ;
49+ let script_cors = CorsLayer :: new ( ) . allow_methods ( [ Method :: GET ] ) . allow_origin ( Any ) . allow_credentials ( false ) ;
50+
51+ let set_headers = tower:: ServiceBuilder :: new ( )
52+ . layer ( SetResponseHeaderLayer :: if_not_present ( header:: X_FRAME_OPTIONS , "DENY" ) )
53+ . layer ( SetResponseHeaderLayer :: if_not_present ( header:: X_CONTENT_TYPE_OPTIONS , "nosniff" ) )
54+ . layer ( SetResponseHeaderLayer :: if_not_present ( header:: X_XSS_PROTECTION , "1; mode=block" ) )
55+ . layer ( SetResponseHeaderLayer :: if_not_present (
56+ header:: CONTENT_SECURITY_POLICY ,
57+ "default-src 'self' data: 'unsafe-inline'; img-src 'self' data: https://*" ,
58+ ) )
59+ . layer ( SetResponseHeaderLayer :: if_not_present ( header:: REFERRER_POLICY , "same-origin" ) ) ;
60+
61+ let dashboard = ApiRouter :: new ( )
62+ . merge ( routes:: admin:: router ( ) )
63+ . merge ( routes:: auth:: router ( ) )
64+ . merge ( routes:: dashboard:: router ( ) ) ;
65+
66+ let router = ApiRouter :: new ( )
67+ . nest ( "/api" , routes:: event:: router ( ) . layer ( event_cors) )
68+ . nest ( "/api/dashboard" , dashboard)
69+ . route_service ( "/script.js" , StaticFile :: < Script > :: new ( "script.min.js" ) . layer ( script_cors) )
70+ . layer ( CompressionLayer :: new ( ) )
71+ . layer ( set_headers)
72+ . with_state ( RouterState { app : app. clone ( ) , events } ) ;
9573
9674 match app. onboarding . token ( ) ? {
9775 Some ( onboarding) => {
@@ -105,5 +83,10 @@ pub async fn start_webserver(app: Arc<Liwan>, events: Sender<Event>) -> Result<(
10583 }
10684 }
10785
108- Server :: new ( listener) . run ( router) . await . context ( "server exited unexpectedly" )
86+ let mut api = openapi:: OpenApi :: default ( ) ;
87+ api. info = openapi:: Info { description : Some ( "an example API" . to_string ( ) ) , ..openapi:: Info :: default ( ) } ;
88+
89+ let listener = tokio:: net:: TcpListener :: bind ( ( "0.0.0.0" , app. config . port ) ) . await . unwrap ( ) ;
90+ let server = router. finish_api ( & mut api) . into_make_service_with_connect_info :: < SocketAddr > ( ) ;
91+ axum:: serve ( listener, server) . await . context ( "server exited unexpectedly" )
10992}
0 commit comments