1mod handlers;
2mod helpers;
3mod mutations;
4mod views;
5
6use std::path::Path;
7use std::sync::Arc;
8
9use anyhow::Result;
10use axum::response::IntoResponse;
11use axum::routing::{get, post};
12use axum::Router;
13
14use crate::db;
15
16#[derive(Clone)]
17struct AppState {
18 data_root: Arc<std::path::PathBuf>,
19}
20
21async fn static_oat_css() -> impl IntoResponse {
22 (
23 [(axum::http::header::CONTENT_TYPE, "text/css; charset=utf-8")],
24 include_bytes!("../../../static/oat.min.css").as_slice(),
25 )
26}
27
28async fn static_td_css() -> impl IntoResponse {
29 (
30 [(axum::http::header::CONTENT_TYPE, "text/css; charset=utf-8")],
31 include_bytes!("../../../static/td.css").as_slice(),
32 )
33}
34
35async fn static_js() -> impl IntoResponse {
36 (
37 [(
38 axum::http::header::CONTENT_TYPE,
39 "application/javascript; charset=utf-8",
40 )],
41 include_bytes!("../../../static/oat.min.js").as_slice(),
42 )
43}
44
45async fn static_td_js() -> impl IntoResponse {
46 (
47 [(
48 axum::http::header::CONTENT_TYPE,
49 "application/javascript; charset=utf-8",
50 )],
51 include_bytes!("../../../static/td.js").as_slice(),
52 )
53}
54
55pub fn run(cwd: &Path, host: &str, port: u16, explicit_project: Option<&str>) -> Result<()> {
56 let data_root = db::data_root()?;
57 let state = AppState {
58 data_root: Arc::new(data_root),
59 };
60
61 let app = Router::new()
62 .route("/", get(handlers::index_handler))
63 .route("/projects", post(mutations::create_project_handler))
64 .route("/projects/{name}", get(handlers::project_handler))
65 .route("/projects/{name}/tasks", post(mutations::create_handler))
66 .route(
67 "/projects/{name}/tasks/{id}",
68 get(handlers::task_handler).post(mutations::update_handler),
69 )
70 .route(
71 "/projects/{name}/tasks/{id}/log",
72 post(mutations::log_handler),
73 )
74 .route(
75 "/projects/{name}/tasks/{id}/done",
76 post(mutations::done_handler),
77 )
78 .route(
79 "/projects/{name}/tasks/{id}/reopen",
80 post(mutations::reopen_handler),
81 )
82 .route(
83 "/projects/{name}/tasks/{id}/labels",
84 post(mutations::label_handler),
85 )
86 .route(
87 "/projects/{name}/tasks/{id}/deps",
88 post(mutations::dep_handler),
89 )
90 .route(
91 "/projects/{name}/tasks/{id}/delete",
92 post(mutations::delete_handler),
93 )
94 .route("/static/oat.min.css", get(static_oat_css))
95 .route("/static/td.css", get(static_td_css))
96 .route("/static/oat.min.js", get(static_js))
97 .route("/static/td.js", get(static_td_js))
98 .with_state(state);
99
100 let addr = format!("{host}:{port}");
101 let root_url = format!("http://{addr}");
102
103 // Resolve current project for a convenience URL.
104 let project_url = match explicit_project {
105 Some(p) => Some(format!("{root_url}/projects/{p}")),
106 None => db::try_open(cwd)
107 .ok()
108 .flatten()
109 .map(|s| format!("{root_url}/projects/{}", s.project_name())),
110 };
111
112 eprintln!("listening on {root_url}");
113 if let Some(ref url) = project_url {
114 eprintln!("project: {url}");
115 }
116
117 tokio::runtime::Builder::new_multi_thread()
118 .enable_all()
119 .build()?
120 .block_on(async {
121 let listener = tokio::net::TcpListener::bind(&addr).await?;
122 axum::serve(listener, app).await?;
123 Ok(())
124 })
125}