@@ -6,6 +6,8 @@ use std::thread;
66use std:: time:: { Duration , Instant } ;
77use tauri:: Manager ;
88
9+ pub mod server;
10+
911/// Custom Tauri commands exposed to the frontend via `invoke`.
1012/// These bridge the gap between the web frontend and native capabilities.
1113
@@ -50,8 +52,6 @@ fn detect_repo_root() -> Option<PathBuf> {
5052 }
5153 }
5254
53- // During local desktop development/build this points to:
54- // <repo>/apps/desktop/src-tauri
5555 let manifest_dir = PathBuf :: from ( env ! ( "CARGO_MANIFEST_DIR" ) ) ;
5656 let candidate = manifest_dir. join ( "../../.." ) ;
5757 if candidate. join ( "package.json" ) . exists ( ) {
@@ -145,8 +145,6 @@ fn start_embedded_next_server(
145145 ) ) ;
146146 }
147147
148- // Determine a writable path for the SQLite database.
149- // Prefer ROUTA_DB_PATH env, otherwise use <app_data_dir>/routa.db.
150148 let db_path = std:: env:: var ( "ROUTA_DB_PATH" ) . unwrap_or_else ( |_| {
151149 let data_dir = app
152150 . path ( )
@@ -189,6 +187,51 @@ fn start_embedded_next_server(
189187 Ok ( child)
190188}
191189
190+ /// Resolve the SQLite database path for the desktop app.
191+ fn resolve_db_path ( app : & tauri:: AppHandle ) -> String {
192+ std:: env:: var ( "ROUTA_DB_PATH" ) . unwrap_or_else ( |_| {
193+ let data_dir = app
194+ . path ( )
195+ . app_data_dir ( )
196+ . unwrap_or_else ( |_| dirs:: home_dir ( ) . unwrap_or_default ( ) . join ( ".routa" ) ) ;
197+ std:: fs:: create_dir_all ( & data_dir) . ok ( ) ;
198+ data_dir
199+ . join ( "routa.db" )
200+ . to_string_lossy ( )
201+ . to_string ( )
202+ } )
203+ }
204+
205+ /// Start the embedded Rust backend server (replaces Node.js).
206+ fn start_rust_server ( app : & tauri:: AppHandle , host : & str , port : u16 ) -> Result < ( ) , String > {
207+ let db_path = resolve_db_path ( app) ;
208+ let host = host. to_string ( ) ;
209+
210+ println ! ( "[rust-server] Starting embedded Rust backend server" ) ;
211+ println ! ( "[rust-server] Database path: {}" , db_path) ;
212+ println ! ( "[rust-server] Listening on {}:{}" , host, port) ;
213+
214+ let config = server:: ServerConfig {
215+ host,
216+ port,
217+ db_path,
218+ } ;
219+
220+ // Start the server in the Tauri async runtime
221+ tauri:: async_runtime:: spawn ( async move {
222+ match server:: start_server ( config) . await {
223+ Ok ( addr) => {
224+ println ! ( "[rust-server] Server started on {}" , addr) ;
225+ }
226+ Err ( e) => {
227+ eprintln ! ( "[rust-server] Failed to start server: {}" , e) ;
228+ }
229+ }
230+ } ) ;
231+
232+ Ok ( ( ) )
233+ }
234+
192235#[ cfg_attr( mobile, tauri:: mobile_entry_point) ]
193236pub fn run ( ) {
194237 tauri:: Builder :: default ( )
@@ -212,35 +255,67 @@ pub fn run() {
212255 let window = app. get_webview_window ( "main" ) . unwrap ( ) ;
213256 window. open_devtools ( ) ;
214257 }
258+
259+ // Configurable API mode:
260+ // - rust (default): start embedded Rust server (no Node.js needed).
261+ // - embedded: start packaged standalone Next server (legacy Node.js mode).
262+ // - external: connect to existing server at ROUTA_DESKTOP_API_URL.
263+ // - off: use embedded static UI only.
264+ let api_mode = env_or_default ( "ROUTA_DESKTOP_API_MODE" , "rust" ) ;
265+ let api_host = env_or_default ( "ROUTA_DESKTOP_API_HOST" , "127.0.0.1" ) ;
266+ let port = api_port ( ) ;
267+ let api_url = std:: env:: var ( "ROUTA_DESKTOP_API_URL" )
268+ . unwrap_or_else ( |_| format ! ( "http://{}:{}" , api_host, port) ) ;
269+
215270 #[ cfg( not( debug_assertions) ) ]
216271 {
217- // Allow explicitly enabling DevTools in release builds for diagnostics.
218272 let force_debug =
219273 std:: env:: var ( "ROUTA_TAURI_DEBUG" ) . ok ( ) . as_deref ( ) == Some ( "1" ) ;
220274 if force_debug {
221275 if let Some ( window) = app. get_webview_window ( "main" ) {
222276 window. open_devtools ( ) ;
223277 }
224278 }
279+ }
225280
226- // Configurable API mode:
227- // - embedded (default): start packaged standalone Next server.
228- // - external: do not spawn, connect to existing ROUTA_DESKTOP_API_URL/HOST:PORT.
229- // - off: use embedded static UI only.
230- let api_mode = env_or_default ( "ROUTA_DESKTOP_API_MODE" , "embedded" ) ;
231- let api_host = env_or_default ( "ROUTA_DESKTOP_API_HOST" , "127.0.0.1" ) ;
232- let port = api_port ( ) ;
233- let api_url = std:: env:: var ( "ROUTA_DESKTOP_API_URL" )
234- . unwrap_or_else ( |_| format ! ( "http://{}:{}" , api_host, port) ) ;
235-
236- if api_mode != "off" {
281+ match api_mode. as_str ( ) {
282+ "off" => {
283+ println ! ( "[desktop-server] API mode is off, using embedded static UI only" ) ;
284+ }
285+ "rust" => {
286+ // New: Start embedded Rust server (no Node.js dependency!)
287+ match start_rust_server ( & app. handle ( ) , & api_host, port) {
288+ Ok ( ( ) ) => {
289+ // Wait for the Rust server to become ready
290+ if wait_for_port ( & api_host, port, 5 ) {
291+ if let Some ( window) = app. get_webview_window ( "main" ) {
292+ let js =
293+ format ! ( "window.location.replace('{}');" , api_url) ;
294+ let _ = window. eval ( & js) ;
295+ println ! (
296+ "[rust-server] Webview navigated to {}" ,
297+ api_url
298+ ) ;
299+ }
300+ } else {
301+ eprintln ! (
302+ "[rust-server] Timed out waiting for server. Falling back to static UI."
303+ ) ;
304+ }
305+ }
306+ Err ( e) => {
307+ eprintln ! ( "[rust-server] {}" , e) ;
308+ }
309+ }
310+ }
311+ "embedded" => {
312+ // Legacy: start Node.js server
237313 let mut ready = wait_for_port ( & api_host, port, 1 ) ;
238- if !ready && api_mode == "embedded" {
314+ if !ready {
239315 match start_embedded_next_server ( & app. handle ( ) , & api_host, port) {
240316 Ok ( _child) => { }
241317 Err ( err) => {
242318 eprintln ! ( "[desktop-server] {}" , err) ;
243- // Dev fallback: running app from repository workspace.
244319 match start_local_next_server ( & api_host, port) {
245320 Ok ( _child) => { }
246321 Err ( dev_err) => {
@@ -250,8 +325,11 @@ pub fn run() {
250325 }
251326 }
252327 ready = wait_for_port ( & api_host, port, 25 ) ;
253- } else if ready {
254- println ! ( "[desktop-server] Reusing existing local server on {}" , api_url) ;
328+ } else {
329+ println ! (
330+ "[desktop-server] Reusing existing local server on {}" ,
331+ api_url
332+ ) ;
255333 }
256334
257335 if ready {
@@ -266,10 +344,32 @@ pub fn run() {
266344 api_url
267345 ) ;
268346 }
269- } else {
270- println ! ( "[desktop-server] API mode is off, using embedded static UI only" ) ;
347+ }
348+ "external" => {
349+ if wait_for_port ( & api_host, port, 5 ) {
350+ if let Some ( window) = app. get_webview_window ( "main" ) {
351+ let js = format ! ( "window.location.replace('{}');" , api_url) ;
352+ let _ = window. eval ( & js) ;
353+ println ! (
354+ "[desktop-server] Webview navigated to external {}" ,
355+ api_url
356+ ) ;
357+ }
358+ } else {
359+ eprintln ! (
360+ "[desktop-server] External server not reachable at {}" ,
361+ api_url
362+ ) ;
363+ }
364+ }
365+ _ => {
366+ eprintln ! (
367+ "[desktop-server] Unknown API mode '{}', falling back to static UI" ,
368+ api_mode
369+ ) ;
271370 }
272371 }
372+
273373 Ok ( ( ) )
274374 } )
275375 . run ( tauri:: generate_context!( ) )
0 commit comments