use axum::Router;
use tower_sessions::cookie::time::Duration;
use super::runique_app::RuniqueApp;
use super::templates::TemplateLoader;
use crate::config::RuniqueConfig;
use crate::engine::RuniqueEngine;
use crate::macros::add_urls;
use crate::middleware::{HostPolicy, SecurityPolicy};
use crate::utils::aliases::{new, new_serve};
use super::error_build::BuildError;
use super::staging::{AdminStaging, CoreStaging, MiddlewareStaging, StaticStaging};
use crate::admin::build_admin_router;
#[cfg(feature = "orm")]
use crate::db::DatabaseConfig;
#[cfg(feature = "orm")]
use sea_orm::DatabaseConnection;
pub struct RuniqueAppBuilder {
config: RuniqueConfig,
core: CoreStaging,
middleware: MiddlewareStaging,
statics: StaticStaging,
router: Option<Router>,
admin: AdminStaging,
}
impl RuniqueAppBuilder {
pub fn new(config: RuniqueConfig) -> Self {
let middleware = MiddlewareStaging::from_config(&config);
Self {
config,
core: CoreStaging::new(),
middleware,
statics: StaticStaging::new(),
router: None,
admin: AdminStaging::new(),
}
}
pub fn core(mut self, f: impl FnOnce(CoreStaging) -> CoreStaging) -> Self {
self.core = f(self.core);
self
}
#[cfg(feature = "orm")]
pub fn with_database(mut self, db: DatabaseConnection) -> Self {
self.core = self.core.with_database(db);
self
}
#[cfg(feature = "orm")]
pub fn with_database_config(mut self, config: DatabaseConfig) -> Self {
self.core = self.core.with_database_config(config);
self
}
pub fn routes(mut self, router: Router) -> Self {
self.router = Some(router);
self
}
pub fn middleware(mut self, f: impl FnOnce(MiddlewareStaging) -> MiddlewareStaging) -> Self {
self.middleware = f(self.middleware);
self
}
pub fn with_session_duration(mut self, duration: Duration) -> Self {
self.middleware = self.middleware.with_session_duration(duration);
self
}
pub fn with_error_handler(mut self, enable: bool) -> Self {
self.middleware = self.middleware.with_debug_errors(enable);
self
}
pub fn static_files(mut self, f: impl FnOnce(StaticStaging) -> StaticStaging) -> Self {
self.statics = f(self.statics);
self
}
pub fn statics(mut self) -> Self {
self.statics = self.statics.enable();
self
}
pub fn no_statics(mut self) -> Self {
self.statics = self.statics.disable();
self
}
pub fn with_admin(mut self, f: impl FnOnce(AdminStaging) -> AdminStaging) -> Self {
self.admin = f(self.admin.enable());
self
}
pub async fn build(mut self) -> Result<RuniqueApp, BuildError> {
self.validate()?;
if !self.all_ready() {
return Err(BuildError::validation(
"Un ou plusieurs composants ne sont pas prêts pour la construction",
));
}
#[cfg(feature = "orm")]
let db = self.core.connect().await?;
let config = self.config;
let url_registry = self.core.url_registry;
let middleware = self.middleware;
let statics_enabled = self.statics.enabled;
let router = self.router;
let tera = new(TemplateLoader::init(&config, url_registry.clone())
.map_err(|e| BuildError::template(e.to_string()))?);
let config = new(config);
let engine = new(RuniqueEngine {
config: (*config).clone(),
tera: tera.clone(),
#[cfg(feature = "orm")]
db: new(db),
features: middleware.features.clone(),
url_registry,
security_csp: new(SecurityPolicy::from_env()),
security_hosts: new(HostPolicy::from_env()),
});
add_urls(&engine);
let router = router.unwrap_or_default();
let router = if self.admin.enabled {
let admin_router = build_admin_router(self.admin);
router.merge(admin_router)
} else {
router
};
let router = middleware.apply_to_router(router, config, engine.clone(), tera);
let router = if statics_enabled {
Self::attach_static_files(router, &engine.config)
} else {
router
};
Ok(RuniqueApp { engine, router })
}
fn validate(&self) -> Result<(), BuildError> {
self.core.validate()?;
self.middleware.validate()?;
self.statics.validate()?;
self.admin.validate()?;
self.cross_validate()?;
Ok(())
}
fn cross_validate(&self) -> Result<(), BuildError> {
Ok(())
}
fn all_ready(&self) -> bool {
self.core.is_ready()
&& self.middleware.is_ready()
&& self.statics.is_ready()
&& self.admin.is_ready()
}
fn attach_static_files(mut router: Router, config: &RuniqueConfig) -> Router {
router = router
.nest_service(
&config.static_files.static_url,
new_serve(&config.static_files.staticfiles_dirs),
)
.nest_service(
&config.static_files.media_url,
new_serve(&config.static_files.media_root),
);
if !config.static_files.static_runique_url.is_empty() {
router = router.nest_service(
&config.static_files.static_runique_url,
new_serve(&config.static_files.static_runique_path),
);
}
router
}
}