1use crate::utils::{Result, TrackError};
4use crate::webui::routes::{self, WebState};
5use crate::webui::sse::sse_handler;
6use crate::webui::state::AppState;
7use crate::webui::templates::Templates;
8use axum::{
9 routing::{delete, get, patch, post},
10 Router,
11};
12use std::net::SocketAddr;
13use std::sync::Arc;
14use tokio::net::TcpListener;
15use tower_http::services::ServeDir;
16
17pub fn build_router(web_state: WebState) -> Router {
19 Router::new()
20 .route("/", get(routes::index))
22 .route("/api/card/description", get(routes::get_description))
24 .route("/api/card/ticket", get(routes::get_ticket))
25 .route("/api/card/links", get(routes::get_links))
26 .route("/api/card/todos", get(routes::get_todos))
27 .route("/api/card/scraps", get(routes::get_scraps))
28 .route("/partials/todos", get(routes::get_todos))
30 .route("/partials/repos", get(routes::get_repos))
31 .route("/api/status", get(routes::api_status))
33 .route("/api/todo", post(routes::add_todo))
34 .route("/api/todo/:id", delete(routes::delete_todo))
35 .route("/api/todo/:id/next", patch(routes::move_todo_to_next))
36 .route("/api/todo/:id/:status", patch(routes::update_todo_status))
37 .route("/api/scrap", post(routes::add_scrap))
38 .route("/api/description", post(routes::update_description))
39 .route("/api/ticket", post(routes::update_ticket))
40 .route("/api/link", post(routes::add_link))
41 .route("/api/link/:id", delete(routes::delete_link))
42 .route("/api/sse", get(sse_handler))
44 .nest_service("/static", ServeDir::new("static"))
46 .with_state(web_state)
47}
48
49pub async fn start_server(port: u16, open_browser: bool) -> Result<()> {
51 let app_state = AppState::new()?;
53
54 let templates = Arc::new(Templates::embedded());
56
57 let web_state = WebState {
58 app: app_state.clone(),
59 templates,
60 };
61
62 let poll_state = app_state.clone();
64 tokio::spawn(async move {
65 poll_state.start_change_detection().await;
66 });
67
68 let app = build_router(web_state);
70
71 let addr = SocketAddr::from(([127, 0, 0, 1], port));
72
73 println!("Starting track webui server...");
74 println!(" → http://localhost:{}", port);
75 println!();
76 println!("Press Ctrl+C to stop the server.");
77
78 if open_browser {
80 let url = format!("http://localhost:{}", port);
81 if let Err(e) = open::that(&url) {
82 eprintln!("Warning: Failed to open browser: {}", e);
83 }
84 }
85
86 let listener = TcpListener::bind(addr).await.map_err(TrackError::Io)?;
88 axum::serve(listener, app).await.map_err(TrackError::Io)?;
89
90 Ok(())
91}