feat: serve static upload app from axum
Introduce the first PLAN.md milestone: replace the hello-world binary with an Axum server that binds to localhost by default, exposes a health endpoint, and serves the static browser UI from the repository's static directory. The router is available through the library crate so integration tests can exercise server behavior without opening a network listener. Add a justfile for routine validation and document the initial project shape, configuration knobs, and reusable test checklist. The rustfmt config now uses only stable options so the new formatting recipe runs without nightly warnings. The upload API and resumable chunk behavior are intentionally left for later milestones; the UI currently handles file selection only. Test Plan: - just check Refs: PLAN.md milestone 1
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
||||
use std::{
|
||||
env,
|
||||
error::Error,
|
||||
net::SocketAddr,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use axum::{Router, routing::get};
|
||||
use tower_http::services::{ServeDir, ServeFile};
|
||||
|
||||
const DEFAULT_BIND_ADDR: &str = "127.0.0.1:3000";
|
||||
const STATIC_DIR_ENV: &str = "UPL_STATIC_DIR";
|
||||
const BIND_ENV: &str = "UPL_BIND";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AppConfig {
|
||||
pub bind_addr: SocketAddr,
|
||||
pub static_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
/// Loads bind and static directory settings from environment variables.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error when `UPL_BIND` is set but is not a valid socket address.
|
||||
pub fn from_env() -> Result<Self, Box<dyn Error>> {
|
||||
let bind_addr = env::var(BIND_ENV)
|
||||
.unwrap_or_else(|_| DEFAULT_BIND_ADDR.to_owned())
|
||||
.parse()?;
|
||||
let static_dir = env::var_os(STATIC_DIR_ENV).map_or_else(default_static_dir, PathBuf::from);
|
||||
|
||||
Ok(Self {
|
||||
bind_addr,
|
||||
static_dir,
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new(bind_addr: SocketAddr, static_dir: impl Into<PathBuf>) -> Self {
|
||||
Self {
|
||||
bind_addr,
|
||||
static_dir: static_dir.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_router(config: &AppConfig) -> Router {
|
||||
Router::new()
|
||||
.route("/healthz", get(healthz))
|
||||
.fallback_service(static_service(&config.static_dir))
|
||||
}
|
||||
|
||||
async fn healthz() -> &'static str {
|
||||
"ok"
|
||||
}
|
||||
|
||||
fn static_service(static_dir: &Path) -> ServeDir<ServeFile> {
|
||||
ServeDir::new(static_dir).fallback(ServeFile::new(static_dir.join("index.html")))
|
||||
}
|
||||
|
||||
fn default_static_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("static")
|
||||
}
|
||||
Reference in New Issue
Block a user