use std::net::SocketAddr;
use axum::{
http::{header, Method, StatusCode, Uri},
response::{Html, IntoResponse, Response},
routing::{get, post},
Extension, Router,
};
use rust_embed::RustEmbed;
use terraphim_config::ConfigState;
use terraphim_types::IndexedDocument;
use tokio::sync::broadcast::channel;
use tower_http::cors::{Any, CorsLayer};
mod api;
mod error;
use api::{create_document, health, search_documents, search_documents_post};
pub use api::{ConfigResponse, CreateDocumentResponse, SearchResponse};
pub use error::{Result, Status};
static INDEX_HTML: &str = "index.html";
#[derive(RustEmbed, Clone)]
#[folder = "dist/"]
struct Assets;
pub async fn axum_server(server_hostname: SocketAddr, config_state: ConfigState) -> Result<()> {
log::info!("Starting axum server");
let (tx, _rx) = channel::<IndexedDocument>(10);
let app = Router::new()
.route("/health", get(health))
.route("/documents", post(create_document))
.route("/documents/", post(create_document))
.route("/documents/search", get(search_documents))
.route("/documents/search", post(search_documents_post))
.route("/config", get(api::get_config))
.route("/config/", get(api::get_config))
.route("/config", post(api::update_config))
.route("/config/", post(api::update_config))
.fallback(static_handler)
.with_state(config_state)
.layer(Extension(tx))
.layer(
CorsLayer::new()
.allow_origin(Any)
.allow_headers(Any)
.allow_methods(vec![
Method::GET,
Method::POST,
Method::PUT,
Method::PATCH,
Method::DELETE,
]),
);
println!("listening on http://{server_hostname}");
axum::Server::bind(&server_hostname)
.serve(app.into_make_service())
.await?;
Ok(())
}
async fn static_handler(uri: Uri) -> impl IntoResponse {
let path = uri.path().trim_start_matches('/');
if path.is_empty() || path == INDEX_HTML {
return index_html().await;
}
match Assets::get(path) {
Some(content) => {
let mime = mime_guess::from_path(path).first_or_octet_stream();
([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response()
}
None => {
if path.contains('.') {
return not_found().await;
}
index_html().await
}
}
}
async fn index_html() -> Response {
match Assets::get(INDEX_HTML) {
Some(content) => Html(content.data).into_response(),
None => not_found().await,
}
}
async fn not_found() -> Response {
(StatusCode::NOT_FOUND, "404").into_response()
}