summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorozpv <39195175+ozpv@users.noreply.github.com>2026-05-27 01:28:39 -0500
committerozpv <39195175+ozpv@users.noreply.github.com>2026-05-27 01:28:39 -0500
commitac28f04d4d7e23d01a61459f81dbee7f7c3e7c9f (patch)
tree995a60235a44443b504ecb06ad681717f2918bac /src/main.rs
parentcae06d048c3fd162b7df84e64a0e0b8f119726d3 (diff)
inject components server-side
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs92
1 files changed, 75 insertions, 17 deletions
diff --git a/src/main.rs b/src/main.rs
index 1ddddfb..4f03f7f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,13 +1,14 @@
use axum::{
Router,
+ body::{self, Body},
extract::{Request, State},
handler::HandlerWithoutStateExt,
- http::StatusCode,
- response::{Html, IntoResponse},
- routing::{get, get_service},
+ http::{StatusCode, header},
+ response::{Html, IntoResponse, Response},
+ routing::get_service,
};
use site::{env::Env, state::AppState};
-use std::{path::PathBuf, time::Duration};
+use std::{fs, path::PathBuf, time::Duration};
use tokio::net::TcpListener;
use tower::util::ServiceExt;
use tower_http::{
@@ -24,14 +25,48 @@ 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 inject(
+ res: Response,
+ component_name: &'static str,
+ after: &'static str,
+ state: AppState,
+) -> impl IntoResponse {
+ if !res
+ .headers()
+ .get(header::CONTENT_TYPE)
+ .and_then(|value| value.to_str().ok())
+ .is_some_and(|value| value.contains("text/html"))
+ {
+ return res;
+ }
+
+ let (parts, body) = res.into_parts();
+
+ let Ok(body) = body::to_bytes(body, 5_000_000).await else {
+ return ERR.into_response();
+ };
+
+ let Ok(mut string) = String::from_utf8(body.to_vec()) else {
+ return ERR.into_response();
+ };
+
+ let Some(index) = string.find(after) else {
+ return ERR.into_response();
+ };
+
+ let path = state
+ .get_dist_dir()
+ .as_ref()
+ .join("components")
+ .join(component_name);
+
+ let Ok(component) = fs::read_to_string(path) else {
+ return ERR.into_response();
+ };
+
+ string.insert_str(index + after.len(), component.as_str());
+
+ (parts, Body::from(string)).into_response()
}
async fn html_files(State(state): State<AppState>, mut req: Request) -> impl IntoResponse {
@@ -43,23 +78,47 @@ async fn html_files(State(state): State<AppState>, mut req: Request) -> impl Int
*req.uri_mut() = file_path
.to_str()
- // if this were to run on a windows machine
- .map(|s| s.replace("\\", "/"))
+ // in case this were to run on a windows machine
+ .map(|s| s.replace('\\', "/"))
.ok_or(ERR)?
.parse()
.map_err(|_| ERR)?;
+ println!(
+ "{}{}",
+ state.get_dist_dir().as_ref().display(),
+ req.uri().path()
+ );
+
ServeDir::new(state.get_dist_dir().as_ref())
+ .precompressed_br()
+ .precompressed_gzip()
.fallback(not_found.into_service())
.oneshot(req)
.await
.map_err(|_| ERR)
}
+// TODO: move all of this to build.rs
+async fn html_inject_head_nav_footer(
+ State(state): State<AppState>,
+ req: Request,
+) -> impl IntoResponse {
+ let res = html_files(State(state.clone()), req).await.into_response();
+ let res = inject(res, "head.html", "<html lang=\"en\">", state.clone())
+ .await
+ .into_response();
+ let res = inject(res, "nav.html", "<body>", state.clone())
+ .await
+ .into_response();
+
+ inject(res, "footer.html", "</main>", state.clone()).await
+}
+
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
- .with_max_level(tracing::Level::DEBUG)
+ .with_max_level(tracing::Level::TRACE)
.init();
let Env {
@@ -76,8 +135,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
"/",
get_service(ServeFile::new(dist_dir.join("index.html"))),
)
- .route("/assets/{*any}", get(static_files))
- .fallback(html_files)
+ .fallback(html_inject_head_nav_footer)
.layer(CompressionLayer::new().gzip(true))
.layer(TimeoutLayer::with_status_code(
StatusCode::REQUEST_TIMEOUT,