mod errors;
mod routes;
use anyhow::Context;
use axum::response::{Html, IntoResponse, Response};
use axum::{http::header, routing::get, Router};
use colored::Colorize;
use http::{StatusCode, Uri};
use rust_embed::Embed;
use sea_orm::DatabaseConnection;
#[derive(Embed, Clone)]
#[folder = "src/public"]
struct Asset;
pub struct StaticFile<T>(pub T);
impl<T> IntoResponse for StaticFile<T>
where
T: Into<String>,
{
fn into_response(self) -> Response {
let path = self.0.into();
match Asset::get(path.as_str()) {
Some(content) => {
let mime = mime_guess::from_path(path).first_or_octet_stream();
([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response()
}
None => (StatusCode::NOT_FOUND, "404 Not Found").into_response(),
}
}
}
async fn index_handler() -> impl IntoResponse {
static_handler("/index.html".parse::<Uri>().unwrap()).await
}
async fn static_handler(uri: Uri) -> impl IntoResponse {
let mut path = uri.path().trim_start_matches('/').to_string();
if path.starts_with("dist/") {
path = path.replace("dist/", "");
}
StaticFile(path)
}
async fn not_found() -> Html<&'static str> {
Html("<h1>404</h1><p>Not Found</p>")
}
async fn create_app(db: &DatabaseConnection) -> Router {
Router::new()
.route("/", get(index_handler))
.route("/index.html", get(index_handler))
.route("/api/scenarios", get(routes::get_scenarios))
.route("/api/runs/:scenario_name", get(routes::get_runs))
.route("/*file", get(static_handler))
.fallback_service(get(not_found))
.with_state(db.clone())
}
pub async fn start(port: u32, db: &DatabaseConnection) -> anyhow::Result<()> {
let app = create_app(db).await;
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
.await
.unwrap();
println!("\n{}", " Cardamon UI ".reversed().green());
println!("> Server started: visit http://localhost:{}", port);
axum::serve(listener, app).await.context("Error serving UI")
}