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