11use actix_cors:: Cors ;
2- use actix_web:: { get, post, web, App , HttpResponse , HttpServer , Responder } ;
2+ use actix_web:: { get, post, web, App , HttpRequest , HttpResponse , HttpServer , Responder } ;
33use clap:: Parser ;
4+ use rust_embed:: RustEmbed ;
45use std:: sync:: Arc ;
56use tracing_subscriber:: prelude:: * ;
67
@@ -12,14 +13,43 @@ mod integrations;
1213mod jobs;
1314mod financial_keywords;
1415
15- #[ get( "/" ) ]
16+ #[ derive( RustEmbed ) ]
17+ #[ folder = "../gui/dist" ]
18+ struct GuiAssets ;
19+
20+ fn gui_response_for_path ( path : & str ) -> HttpResponse {
21+ if let Some ( content) = GuiAssets :: get ( path) {
22+ let mime = mime_guess:: from_path ( path) . first_or_octet_stream ( ) ;
23+ HttpResponse :: Ok ( )
24+ . content_type ( mime. as_ref ( ) )
25+ . body ( content. data . into_owned ( ) )
26+ } else {
27+ match GuiAssets :: get ( "index.html" ) {
28+ Some ( index) => HttpResponse :: Ok ( )
29+ . content_type ( "text/html; charset=utf-8" )
30+ . body ( index. data . into_owned ( ) ) ,
31+ None => HttpResponse :: InternalServerError ( ) . body ( "GUI assets not found" ) ,
32+ }
33+ }
34+ }
35+
36+ async fn serve_gui ( req : HttpRequest ) -> HttpResponse {
37+ let path = req. path ( ) . trim_start_matches ( '/' ) ;
38+ if path == "api" || path. starts_with ( "api/" ) {
39+ return HttpResponse :: NotFound ( ) . finish ( ) ;
40+ }
41+ let path = if path. is_empty ( ) { "index.html" } else { path } ;
42+ gui_response_for_path ( path)
43+ }
44+
45+ #[ get( "/api/hello" ) ]
1646async fn hello ( ) -> impl Responder {
1747 HttpResponse :: Ok ( ) . json ( serde_json:: json!( {
1848 "message" : "Hello World"
1949 } ) )
2050}
2151
22- #[ get( "/health" ) ]
52+ #[ get( "/api/ health" ) ]
2353async fn health ( db : web:: Data < Arc < database:: Database > > ) -> impl Responder {
2454 // Test database connection
2555 match db. connection . lock ( ) {
@@ -34,12 +64,12 @@ async fn health(db: web::Data<Arc<database::Database>>) -> impl Responder {
3464 }
3565}
3666
37- #[ get( "/settings" ) ]
67+ #[ get( "/api/ settings" ) ]
3868async fn get_settings ( data : web:: Data < handlers:: settings:: SettingsAppState > ) -> impl Responder {
3969 handlers:: settings:: get_settings ( data) . await
4070}
4171
42- #[ post( "/settings/api-keys" ) ]
72+ #[ post( "/api/ settings/api-keys" ) ]
4373async fn update_api_keys (
4474 data : web:: Data < handlers:: settings:: SettingsAppState > ,
4575 request : web:: Json < shared_types:: UpdateApiKeysRequest > ,
@@ -53,6 +83,8 @@ async fn update_api_keys(
5383struct Args {
5484 #[ arg( long) ]
5585 log_file_path : Option < String > ,
86+ #[ arg( long) ]
87+ no_open : bool ,
5688}
5789
5890#[ actix_web:: main]
@@ -393,13 +425,25 @@ async fn main() -> std::io::Result<()> {
393425 . route ( "/api/financial/patterns/{id}/toggle" , web:: patch ( ) . to ( handlers:: financial:: toggle_pattern) )
394426 . service ( handlers:: pattern_generation:: process_sender)
395427 . service ( handlers:: pattern_generation:: generate_pattern)
428+ . default_service ( web:: route ( ) . to ( serve_gui) )
396429 } )
397430 . bind ( ( host. as_str ( ) , port) ) ?
398431 . run ( ) ;
399432
400433 let handle = server. handle ( ) ;
401434 let shutdown_manager = download_manager. clone ( ) ;
402435
436+ let open_in_browser = !args. no_open && std:: env:: var ( "DWATA_NO_OPEN" ) . is_err ( ) ;
437+ if open_in_browser {
438+ let url = format ! ( "http://{}:{}/" , host, port) ;
439+ tokio:: spawn ( async move {
440+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 300 ) ) . await ;
441+ if let Err ( err) = webbrowser:: open ( & url) {
442+ tracing:: warn!( "Failed to open browser: {}" , err) ;
443+ }
444+ } ) ;
445+ }
446+
403447 tokio:: spawn ( async move {
404448 if let Err ( e) = tokio:: signal:: ctrl_c ( ) . await {
405449 tracing:: error!( "Failed to listen for Ctrl+C: {}" , e) ;
0 commit comments