Files
hemx/examples/saas/src/main.rs
T
slhx agent ce4c2e086d feat(axum): expose hashed runtime asset path
Generate a SHA-256 content hash for the embedded hemx runtime, expose the hashed path from hemx-axum, serve immutable runtime headers, and update examples to load the hemx-owned path instead of fixed /hemx.js URLs.

req: axum_integration/005
2026-06-05 18:27:38 +02:00

83 lines
2.4 KiB
Rust

use axum::body::Body;
use axum::extract::{Query, State};
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use axum::Router;
use futures_util::stream;
use hemx::IntoEffect;
use hemx_axum::{runtime_js, runtime_js_path, sse, EffectResponse, InteractionRequest};
use hemx_saas_example::{home_page, live_status, registry, settings_page, ui, AppContext};
use std::collections::BTreeMap;
use std::convert::Infallible;
#[tokio::main]
async fn main() {
let app = app(AppContext::demo());
let listener = tokio::net::TcpListener::bind("127.0.0.1:3003")
.await
.expect("bind saas tutorial example");
axum::serve(listener, app)
.await
.expect("serve saas tutorial example");
}
fn app(ctx: AppContext) -> Router {
Router::new()
.route("/", get(home).post(interact))
.route("/settings", get(settings))
.route("/events", get(events))
.route(runtime_js_path(), get(runtime))
.route("/app.css", get(css))
.route("/metrics.js", get(metrics_js))
.with_state(ctx)
}
async fn home(State(ctx): State<AppContext>) -> impl IntoResponse {
axum::response::Html(home_page(&ctx).into_string())
}
async fn settings(State(ctx): State<AppContext>) -> impl IntoResponse {
axum::response::Html(settings_page(&ctx).into_string())
}
async fn interact(
State(ctx): State<AppContext>,
request: InteractionRequest,
) -> Result<EffectResponse, impl IntoResponse> {
request.dispatch_async(registry(ctx)).await
}
async fn events(
Query(params): Query<BTreeMap<String, String>>,
State(ctx): State<AppContext>,
) -> impl IntoResponse {
let count = ctx.projects().len();
if params.contains_key("once") {
return sse(stream::iter([Ok::<_, Infallible>(
live_status(count).into_batch(ui::BUILD_FINGERPRINT),
)]));
}
sse(stream::iter([Ok::<_, Infallible>(
live_status(count).into_batch(ui::BUILD_FINGERPRINT),
)]))
}
async fn runtime() -> impl IntoResponse {
runtime_js()
}
async fn css() -> Response {
Response::builder()
.header("content-type", "text/css; charset=utf-8")
.body(Body::from(include_str!("../templates/app.css")))
.expect("css response")
}
async fn metrics_js() -> Response {
Response::builder()
.header("content-type", "text/javascript; charset=utf-8")
.body(Body::from(include_str!("../templates/metrics.js")))
.expect("metrics js response")
}