use crate::server::middleware::apply_custom_to_method_router;
use crate::server::{OrdinaryAppRouter, OrdinaryAppServerState, cors, ops};
use axum::Router;
use axum::http::StatusCode;
use axum::routing::get;
use hashbrown::HashMap;
use ordinary_action::Engine;
use ordinary_auth::Auth;
use ordinary_config::{ContentDefinition, ModelConfig, OrdinaryConfig, TemplateFfiVersion};
use ordinary_storage::{ArtifactKind, Storage};
use ordinary_template::OrdinaryTemplate;
use std::sync::Arc;
use std::time::Duration;
use tower::ServiceBuilder;
use tower_http::timeout::TimeoutLayer;
#[allow(clippy::too_many_lines, clippy::type_complexity)]
pub(crate) async fn setup(
config: &mut OrdinaryConfig,
auth: &Arc<Auth>,
storage: &Arc<Storage>,
secure: bool,
engine: &Engine,
model_map: &mut HashMap<String, ModelConfig>,
content_map: &mut HashMap<String, ContentDefinition>,
) -> (
Vec<OrdinaryTemplate>,
HashMap<String, usize>,
Option<u8>,
Option<u8>,
) {
let template_ct = match &config.templates {
Some(i) => i.len(),
None => 0,
};
let mut templates = Vec::with_capacity(template_ct);
let mut template_route_map = HashMap::with_capacity(template_ct);
let mut error_template_idx = None;
let mut mfa_totp_template_idx = None;
let mut globals_map = HashMap::new();
if let Some(globals) = &config.globals {
for global in globals {
globals_map.insert(global.name.clone(), global.clone());
}
}
if let Some(mut template_configs) = config.templates.clone()
&& !template_configs.is_empty()
{
template_configs.sort_by_key(|a| a.idx);
for (i, template_config) in template_configs.iter().enumerate() {
let template_idx = template_config.idx_validated();
let template_name = template_config.name_validated();
if i != template_idx as usize {
tracing::error!("gap in or duplicate template indexes");
}
let is_totp = if let Some(auth) = &config.auth
&& let Some(totp_template) = &auth.mfa.totp.template
&& template_name == totp_template
{
mfa_totp_template_idx = Some(template_idx);
true
} else {
false
};
let is_error = if let Some(error) = &config.error
&& let Some(error_template_name) = &error.template
&& template_name == error_template_name
{
error_template_idx = Some(template_idx);
true
} else {
false
};
let artifact_span = tracing::info_span!("artifact");
let src = artifact_span.in_scope(|| {
let artifact_kind = match template_config.ffi_validated().version {
TemplateFfiVersion::V1 => ArtifactKind::TemplateWasiP1,
TemplateFfiVersion::V2 => ArtifactKind::TemplateWasiP2,
};
match storage.artifact.get(template_idx, artifact_kind) {
Ok(val) => Some(val),
Err(err) => {
tracing::warn!(
name = template_config.name,
%err,
"template not stored",
);
None
}
}
});
let mut globals_list = vec![];
if let Some(template_globals) = &template_config.globals {
for template_global in template_globals {
if let Some(global) = globals_map.get(template_global) {
globals_list.push(global.clone());
}
}
}
if let Ok(template) = OrdinaryTemplate::new(
&config.domain,
&config.version,
&config.canonical.clone().unwrap_or(
config
.cnames
.clone()
.unwrap_or(vec![config.domain.clone()])
.first()
.unwrap_or(&config.domain)
.clone(),
),
if globals_list.is_empty() {
None
} else {
Some(globals_list)
},
secure,
is_error,
is_totp,
src,
template_config.clone(),
auth.clone(),
model_map,
content_map,
engine.clone(),
storage.clone(),
)
.await
{
templates.push(template);
}
}
for template in &templates {
let template_route = template.config.route_validated().to_string();
template_route_map.insert(template_route.clone(), template.idx as usize);
}
}
(
templates,
template_route_map,
error_template_idx,
mfa_totp_template_idx,
)
}
pub(crate) fn setup_router(
config: &Arc<OrdinaryConfig>,
state: &Arc<OrdinaryAppServerState>,
api_domain: Option<&str>,
forwarded_by: &str,
forwarded_proto: &str,
) -> Option<OrdinaryAppRouter> {
if let Some(template_configs) = &config.templates
&& !template_configs.is_empty()
{
let mut router = Router::new();
for template_config in template_configs {
let timeout_s = u64::from(
template_config
.timeout
.unwrap_or(config.default_timeout.unwrap_or(10)),
);
let mut route = get(ops::templates::get).route_layer(ServiceBuilder::new().layer(
TimeoutLayer::with_status_code(
StatusCode::REQUEST_TIMEOUT,
Duration::from_secs(timeout_s),
),
));
if let Some(names) = &template_config.middlewares {
route = apply_custom_to_method_router(
route,
config,
state,
names,
config.domain.clone(),
forwarded_by.to_string(),
forwarded_proto.to_string(),
api_domain,
);
}
route = cors::apply_to_route(config.cors.as_ref(), config.cors.as_ref(), route);
router = router.route(template_config.route_validated(), route);
}
return Some(router);
}
None
}