Skip to main content

chronicle/web/
mod.rs

1mod api;
2mod assets;
3
4use crate::error::{ChronicleError, Result};
5use crate::git::CliOps;
6
7pub fn serve(git_ops: CliOps, port: Option<u16>, open_browser: bool) -> Result<()> {
8    let bind_port = port.unwrap_or(0);
9    let listener = std::net::TcpListener::bind(("127.0.0.1", bind_port)).map_err(|e| {
10        ChronicleError::Config {
11            message: format!("failed to bind to port {bind_port}: {e}"),
12            location: snafu::Location::default(),
13        }
14    })?;
15    let actual_port = listener
16        .local_addr()
17        .map_err(|e| ChronicleError::Config {
18            message: format!("failed to get local address: {e}"),
19            location: snafu::Location::default(),
20        })?
21        .port();
22
23    let server =
24        tiny_http::Server::from_listener(listener, None).map_err(|e| ChronicleError::Config {
25            message: format!("failed to start web server: {e}"),
26            location: snafu::Location::default(),
27        })?;
28
29    let url = format!("http://localhost:{actual_port}");
30    eprintln!("\n  Chronicle web viewer running at {url}\n  Press Ctrl+C to stop\n");
31    if open_browser {
32        open::that(&url).ok();
33    }
34
35    for request in server.incoming_requests() {
36        let req_url = request.url().to_string();
37        let result = if req_url.starts_with("/api/") {
38            api::handle(&git_ops, &req_url)
39        } else {
40            Ok(assets::handle(&req_url))
41        };
42
43        match result {
44            Ok(response) => {
45                request.respond(response).ok();
46            }
47            Err(e) => {
48                eprintln!("[chronicle-web] ERROR {req_url}: {e}");
49                let body = serde_json::json!({ "error": e.to_string() }).to_string();
50                let response = tiny_http::Response::from_string(body)
51                    .with_header(
52                        tiny_http::Header::from_bytes(
53                            &b"Content-Type"[..],
54                            &b"application/json"[..],
55                        )
56                        .unwrap(),
57                    )
58                    .with_status_code(500);
59                request.respond(response).ok();
60            }
61        }
62    }
63
64    Ok(())
65}