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::http::header::ETAG;
use axum::routing::get;
use bytes::Bytes;
use hashbrown::HashMap;
use ordinary_action::Engine;
use ordinary_auth::Auth;
use ordinary_config::{ContentDefinition, ModelConfig, OrdinaryConfig};
use ordinary_storage::{ArtifactKind, Storage};
use ordinary_template::Template;
use ordinary_types::json_to_flexbuffer_vec;
use ordinary_utils::middleware::{modify_etag_for_encoding, x_via};
use std::sync::Arc;
use std::time::Duration;
use tower::ServiceBuilder;
use tower_http::set_header::SetResponseHeaderLayer;
use tower_http::timeout::TimeoutLayer;
#[allow(clippy::too_many_lines, clippy::type_complexity)]
pub(crate) 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<Template>,
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);
templates = template_configs
.iter()
.enumerate()
.filter_map(|(i, template_config)| {
if i != template_config.idx as usize {
tracing::error!("gap in or duplicate template indexes");
}
let artifact_span = tracing::info_span!("artifact");
let src = artifact_span.in_scope(|| {
match storage
.artifact
.get(template_config.idx, ArtifactKind::Template)
{
Ok(val) => Some(val),
Err(err) => {
tracing::warn!(
name = template_config.name,
%err,
"template not stored",
);
None
}
}
});
let mut globals =
flexbuffers::Builder::new(&flexbuffers::BuilderOptions::SHARE_NONE);
let mut globals_vec = globals.start_vector();
if let Some(template_globals) = &template_config.globals {
for template_global in template_globals {
if let Some(global) = globals_map.get(template_global)
&& let Err(err) = json_to_flexbuffer_vec(
&global.kind,
&global.value,
&mut globals_vec,
)
{
tracing::error!(%err);
}
}
}
globals_vec.end_vector();
let template_globals = Bytes::copy_from_slice(globals.view());
let mut fields =
flexbuffers::Builder::new(&flexbuffers::BuilderOptions::SHARE_NONE);
let mut fields_vec = fields.start_vector();
if let Some(template_fields) = &template_config.fields {
for field in template_fields {
if let Err(err) =
json_to_flexbuffer_vec(&field.kind, &field.value, &mut fields_vec)
{
tracing::error!(%err);
}
}
}
fields_vec.end_vector();
let template_fields = Bytes::copy_from_slice(fields.view());
Template::new(
&config.domain,
secure,
src,
template_config.clone(),
auth.clone(),
model_map,
content_map,
template_config
.globals
.is_some()
.then_some(template_globals),
template_config.fields.is_some().then_some(template_fields),
engine.clone(),
storage.clone(),
)
.ok()
})
.collect::<Vec<Template>>();
for template in &templates {
if let Some(auth) = &config.auth
&& let Some(totp_template) = &auth.mfa.totp.template
&& &template.config.name == totp_template
{
mfa_totp_template_idx = Some(template.idx);
}
if let Some(error) = &config.error
&& let Some(template_name) = &error.template
&& &template.config.name == template_name
{
error_template_idx = Some(template.idx);
}
template_route_map.insert(template.config.route.clone(), template.idx as usize);
template.start_tasks();
}
}
(
templates,
template_route_map,
error_template_idx,
mfa_totp_template_idx,
)
}
#[allow(clippy::ref_option)]
pub(crate) fn setup_router(
config: &Arc<OrdinaryConfig>,
state: &Arc<OrdinaryAppServerState>,
api_domain: &Option<String>,
forwarded_by: &str,
forwarded_proto: &str,
) -> Option<OrdinaryAppRouter> {
if let Some(template_configs) = &config.templates
&& !template_configs.is_empty()
{
let mut router = Router::new().route(
"/.ordinary/v1/templates/query/{idx}/{*path}",
get(ops::templates::query).route_layer(
ServiceBuilder::new()
.layer(SetResponseHeaderLayer::overriding(
ETAG,
modify_etag_for_encoding,
))
.layer(axum::middleware::from_fn(x_via)),
),
);
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),
))
.layer(SetResponseHeaderLayer::overriding(
ETAG,
modify_etag_for_encoding,
))
.layer(axum::middleware::from_fn(x_via)),
);
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.clone(),
);
}
route = cors::apply_to_route(&config.cors, &config.cors, route);
router = router.route(&template_config.route, route);
}
return Some(router);
}
None
}