mod.rs

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