mod.rs

  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}