use axum::Router;
use axum::routing::get;
use std::sync::Arc;
use systemprompt_runtime::AppContext;
use systemprompt_traits::{StartupEvent, StartupEventSender};
use crate::services::middleware::authz::AuthzPolicy;
use crate::services::middleware::{PublicContextMiddleware, RouterExt, site_auth_gate};
use crate::services::static_content::{
StaticContentMatcher, StaticContentState, serve_homepage, smart_fallback_handler,
};
pub(super) fn build_static_router(
ctx: &AppContext,
public_middleware: PublicContextMiddleware,
events: Option<&StartupEventSender>,
) -> Router {
let path = ctx.app_paths().system().content_config().to_path_buf();
let content_matcher = path.to_str().map_or_else(
|| {
if let Some(tx) = events {
if tx
.unbounded_send(StartupEvent::Warning {
message: "CONTENT_CONFIG_PATH contains invalid UTF-8".to_owned(),
context: None,
})
.is_err()
{
tracing::debug!("Startup event receiver dropped");
}
}
Arc::new(StaticContentMatcher::empty())
},
|path_str| match StaticContentMatcher::from_config(path_str) {
Ok(matcher) => Arc::new(matcher),
Err(e) => {
if let Some(tx) = events {
if tx
.unbounded_send(StartupEvent::Warning {
message: format!("Failed to load content config: {e}"),
context: Some("Static content matching will be disabled".to_owned()),
})
.is_err()
{
tracing::debug!("Startup event receiver dropped");
}
}
Arc::new(StaticContentMatcher::empty())
},
},
);
let static_state = StaticContentState {
ctx: Arc::new(ctx.clone()),
matcher: content_matcher,
route_classifier: Arc::clone(ctx.route_classifier()),
};
let static_router = Router::new()
.route("/", get(serve_homepage))
.fallback(smart_fallback_handler)
.with_state(static_state)
.with_auth(public_middleware, AuthzPolicy::public());
let site_auth_config = ctx
.extension_registry()
.extensions()
.iter()
.find_map(|ext| ext.site_auth());
if let Some(auth_config) = site_auth_config {
static_router.layer(axum::middleware::from_fn(move |req, next| {
let config = auth_config;
async move { site_auth_gate(req, next, config).await }
}))
} else {
static_router
}
}