summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorozpv <39195175+ozpv@users.noreply.github.com>2026-05-26 01:06:55 -0500
committerozpv <39195175+ozpv@users.noreply.github.com>2026-05-26 01:06:55 -0500
commit16f8cf5ced3ddcbfb142665e3f372e0fc8be73c5 (patch)
treeb4761495520d84ce4a7043ec528e290a2d1b0506 /src
parent9590b3e84a9502accf801110b9695aab72423fb2 (diff)
add site
Diffstat (limited to 'src')
-rw-r--r--src/env.rs20
-rw-r--r--src/lib.rs2
-rw-r--r--src/main.rs91
-rw-r--r--src/state.rs18
4 files changed, 131 insertions, 0 deletions
diff --git a/src/env.rs b/src/env.rs
new file mode 100644
index 0000000..0a4d46c
--- /dev/null
+++ b/src/env.rs
@@ -0,0 +1,20 @@
+use std::{env, path::PathBuf};
+
+pub struct Env {
+ pub site_addr: String,
+ pub dist_dir: PathBuf,
+}
+
+impl Env {
+ pub fn get_or_default() -> Self {
+ let site_addr = env::var("SITE_ADDR").unwrap_or_else(|_| "127.0.0.1:3000".to_string());
+ let dist_dir = env::var("DIST_DIR")
+ .unwrap_or_else(|_| format!("{}/public", env!("CARGO_MANIFEST_DIR")))
+ .into();
+
+ Self {
+ site_addr,
+ dist_dir,
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..c112ba4
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod env;
+pub mod state;
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..1ddddfb
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,91 @@
+use axum::{
+ Router,
+ extract::{Request, State},
+ handler::HandlerWithoutStateExt,
+ http::StatusCode,
+ response::{Html, IntoResponse},
+ routing::{get, get_service},
+};
+use site::{env::Env, state::AppState};
+use std::{path::PathBuf, time::Duration};
+use tokio::net::TcpListener;
+use tower::util::ServiceExt;
+use tower_http::{
+ compression::CompressionLayer,
+ services::{ServeDir, ServeFile},
+ timeout::TimeoutLayer,
+ trace::TraceLayer,
+};
+
+const ERR: (StatusCode, Html<&'static str>) =
+ (StatusCode::NOT_FOUND, Html("<h1>404 Not Found</h1>"));
+
+async fn not_found() -> impl IntoResponse {
+ ERR
+}
+
+async fn static_files(State(state): State<AppState>, req: Request) -> impl IntoResponse {
+ ServeDir::new(state.get_dist_dir().as_ref())
+ .precompressed_br()
+ .precompressed_gzip()
+ .fallback(not_found.into_service())
+ .oneshot(req)
+ .await
+ .map_err(|_| ERR)
+}
+
+async fn html_files(State(state): State<AppState>, mut req: Request) -> impl IntoResponse {
+ let mut file_path = PathBuf::from(req.uri().path());
+
+ if file_path.extension().is_none() {
+ file_path.set_extension("html");
+ }
+
+ *req.uri_mut() = file_path
+ .to_str()
+ // if this were to run on a windows machine
+ .map(|s| s.replace("\\", "/"))
+ .ok_or(ERR)?
+ .parse()
+ .map_err(|_| ERR)?;
+
+ ServeDir::new(state.get_dist_dir().as_ref())
+ .fallback(not_found.into_service())
+ .oneshot(req)
+ .await
+ .map_err(|_| ERR)
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+ tracing_subscriber::fmt()
+ .with_max_level(tracing::Level::DEBUG)
+ .init();
+
+ let Env {
+ site_addr,
+ dist_dir,
+ } = Env::get_or_default();
+
+ let listener = TcpListener::bind(&site_addr).await?;
+
+ tracing::info!("Listening on http://{site_addr}/");
+
+ let router = Router::new()
+ .route(
+ "/",
+ get_service(ServeFile::new(dist_dir.join("index.html"))),
+ )
+ .route("/assets/{*any}", get(static_files))
+ .fallback(html_files)
+ .layer(CompressionLayer::new().gzip(true))
+ .layer(TimeoutLayer::with_status_code(
+ StatusCode::REQUEST_TIMEOUT,
+ Duration::from_secs(30),
+ ))
+ .layer(TraceLayer::new_for_http())
+ .with_state(AppState::new(dist_dir));
+
+ axum::serve(listener, router).await?;
+ Ok(())
+}
diff --git a/src/state.rs b/src/state.rs
new file mode 100644
index 0000000..dfffbe0
--- /dev/null
+++ b/src/state.rs
@@ -0,0 +1,18 @@
+use std::path::PathBuf;
+use std::sync::Arc;
+
+#[derive(Clone)]
+pub struct AppState {
+ dist_dir: Arc<PathBuf>,
+}
+
+impl AppState {
+ pub fn new(dist_dir: PathBuf) -> Self {
+ let dist_dir = Arc::new(dist_dir);
+
+ Self { dist_dir }
+ }
+ pub fn get_dist_dir(&self) -> Arc<PathBuf> {
+ Arc::clone(&self.dist_dir)
+ }
+}