refactor: html-router builder pattern and structure

This commit is contained in:
Per Stark
2025-03-25 12:03:05 +01:00
parent d01d8b6bd7
commit a40ed0fe94
38 changed files with 1050 additions and 938 deletions

View File

@@ -0,0 +1,159 @@
use axum::{
extract::FromRef,
middleware::{from_fn_with_state, map_response_with_state},
Router,
};
use axum_session::SessionLayer;
use axum_session_auth::{AuthConfig, AuthSessionLayer};
use axum_session_surreal::SessionSurrealPool;
use common::storage::types::user::User;
use surrealdb::{engine::any::Any, Surreal};
use tower_http::services::ServeDir;
use crate::{
html_state::HtmlState,
middlewares::{
analytics_middleware::analytics_middleware, auth_middleware::require_auth,
response_middleware::with_template_response,
},
};
pub struct RouterFactory<S> {
app_state: HtmlState,
public_routers: Vec<Router<S>>,
protected_routers: Vec<Router<S>>,
nested_routes: Vec<(String, Router<S>)>,
nested_protected_routes: Vec<(String, Router<S>)>,
custom_middleware: Vec<Box<dyn FnOnce(Router<S>) -> Router<S> + Send>>,
public_assets_config: Option<AssetsConfig>,
}
struct AssetsConfig {
path: String, // URL path for assets
directory: String, // Directory on disk
}
impl<S> RouterFactory<S>
where
S: Clone + Send + Sync + 'static,
HtmlState: FromRef<S>,
{
pub fn new(app_state: &HtmlState) -> Self {
Self {
app_state: app_state.to_owned(),
public_routers: Vec::new(),
protected_routers: Vec::new(),
nested_routes: Vec::new(),
nested_protected_routes: Vec::new(),
custom_middleware: Vec::new(),
public_assets_config: None,
}
}
// Add a serving of assets
pub fn with_public_assets(mut self, path: &str, directory: &str) -> Self {
self.public_assets_config = Some(AssetsConfig {
path: path.to_string(),
directory: directory.to_string(),
});
self
}
// Add a public router that will be merged at the root level
pub fn add_public_routes(mut self, routes: Router<S>) -> Self {
self.public_routers.push(routes);
self
}
// Add a protected router that will be merged at the root level
pub fn add_protected_routes(mut self, routes: Router<S>) -> Self {
self.protected_routers.push(routes);
self
}
// Nest a public router under a path prefix
pub fn nest_public_routes(mut self, path: &str, routes: Router<S>) -> Self {
self.nested_routes.push((path.to_string(), routes));
self
}
// Nest a protected router under a path prefix
pub fn nest_protected_routes(mut self, path: &str, routes: Router<S>) -> Self {
self.nested_protected_routes
.push((path.to_string(), routes));
self
}
// Add custom middleware to be applied before the standard ones
pub fn with_middleware<F>(mut self, middleware_fn: F) -> Self
where
F: FnOnce(Router<S>) -> Router<S> + Send + 'static,
{
self.custom_middleware.push(Box::new(middleware_fn));
self
}
pub fn build(self) -> Router<S> {
// Start with an empty router
let mut public_router = Router::new();
// Merge all public routers
for router in self.public_routers {
public_router = public_router.merge(router);
}
// Add nested public routes
for (path, router) in self.nested_routes {
public_router = public_router.nest(&path, router);
}
// Add public assets to public router
if let Some(assets) = self.public_assets_config {
public_router =
public_router.nest_service(&assets.path, ServeDir::new(assets.directory));
}
// Start with an empty protected router
let mut protected_router = Router::new();
// Merge all protected routers
for router in self.protected_routers {
protected_router = protected_router.merge(router);
}
// Add nested protected routes
for (path, router) in self.nested_protected_routes {
protected_router = protected_router.nest(&path, router);
}
// Apply auth middleware to all protected routes
let protected_router =
protected_router.route_layer(from_fn_with_state(self.app_state.clone(), require_auth));
// Combine public and protected routes
let mut router = Router::new().merge(public_router).merge(protected_router);
// Apply custom middleware in order they were added
for middleware_fn in self.custom_middleware {
router = middleware_fn(router);
}
// Apply common middleware
router
.layer(from_fn_with_state(
self.app_state.clone(),
analytics_middleware,
))
.layer(map_response_with_state(
self.app_state.clone(),
with_template_response,
))
.layer(
AuthSessionLayer::<User, String, SessionSurrealPool<Any>, Surreal<Any>>::new(Some(
self.app_state.db.client.clone(),
))
.with_config(AuthConfig::<String>::default()),
)
.layer(SessionLayer::new((*self.app_state.session_store).clone()))
}
}