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::{delete, get, post, put};
use hashbrown::HashMap;
use ordinary_action::{Action, Engine, PrivilegedComponents};
use ordinary_auth::Auth;
use ordinary_config::{ActionTrigger, ContentDefinition, HttpMethod, ModelConfig, OrdinaryConfig};
use ordinary_integration::Integration;
use ordinary_storage::{ArtifactKind, Storage};
use ordinary_utils::middleware::x_via;
use std::sync::Arc;
use std::time::Duration;
use tower::ServiceBuilder;
use tower_http::timeout::TimeoutLayer;
#[allow(clippy::too_many_arguments, clippy::too_many_lines, clippy::ref_option)]
pub(super) fn setup(
config: &mut OrdinaryConfig,
auth: &Arc<Auth>,
storage: &Arc<Storage>,
privileged_components: &Option<PrivilegedComponents>,
integrations: &Arc<Vec<Integration>>,
engine: &Engine,
model_map: &mut HashMap<String, ModelConfig>,
content_map: &mut HashMap<String, ContentDefinition>,
) -> (Vec<Action>, HashMap<String, usize>, Vec<u8>) {
let action_ct = match &config.actions {
Some(i) => i.len(),
None => 0,
};
let mut actions = Vec::with_capacity(action_ct);
let mut action_route_map = HashMap::with_capacity(action_ct);
let mut registration_actions = vec![];
if let Some(mut action_configs) = config.actions.clone()
&& !action_configs.is_empty()
{
action_configs.sort_by_key(|a| a.idx);
actions = action_configs
.iter()
.enumerate()
.filter_map(|(i, action_config)| {
if i != action_config.idx as usize {
tracing::error!("gap in or duplicate action indexes");
}
let artifact_span = tracing::info_span!("artifact");
let src = artifact_span.in_scope(|| {
match storage
.artifact
.get(action_config.idx, ArtifactKind::Action)
{
Ok(val) => Some(val),
Err(err) => {
tracing::warn!("action '{}' not stored - {err}", action_config.name);
None
}
}
});
Action::new(
src,
engine.clone(),
action_config.clone(),
auth.clone(),
storage.clone(),
integrations.clone(),
model_map,
content_map,
&config.actions,
privileged_components.clone(),
)
.ok()
})
.collect::<Vec<Action>>();
for action in &actions {
for trigger in &action.config.triggered_by {
match trigger {
ActionTrigger::Registration => {
registration_actions.push(action.idx);
}
ActionTrigger::Json { route, method: _ }
| ActionTrigger::Form {
route,
method: _,
redirect: _,
} => {
action_route_map.insert(route.clone(), action.idx as usize);
}
_ => {}
}
}
}
}
actions.shrink_to_fit();
action_route_map.shrink_to_fit();
registration_actions.shrink_to_fit();
(actions, action_route_map, registration_actions)
}
#[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(action_configs) = &config.actions
&& !action_configs.is_empty()
{
let mut router = Router::new();
router = router.route(
"/.ordinary/v1/actions/invoke/{idx}",
post(ops::actions::invoke),
);
for action_config in action_configs {
let timeout_s = u64::from(
action_config
.timeout
.unwrap_or(config.default_timeout.unwrap_or(10)),
);
let timeout_layer = TimeoutLayer::with_status_code(
StatusCode::REQUEST_TIMEOUT,
Duration::from_secs(timeout_s),
);
for trigger in &action_config.triggered_by {
let (route, mut mr) = match trigger {
ActionTrigger::Form {
route,
method,
redirect: _,
} => match method {
HttpMethod::GET => (route, get(ops::actions::form)),
HttpMethod::PUT => (route, put(ops::actions::form)),
HttpMethod::POST => (route, post(ops::actions::form)),
HttpMethod::DELETE => (route, delete(ops::actions::form)),
},
ActionTrigger::Json { route, method } => match method {
HttpMethod::GET => (route, get(ops::actions::json)),
HttpMethod::PUT => (route, put(ops::actions::json)),
HttpMethod::POST => (route, post(ops::actions::json)),
HttpMethod::DELETE => (route, delete(ops::actions::json)),
},
_ => {
continue;
}
};
if let Some(names) = &action_config.middlewares {
mr = apply_custom_to_method_router(
mr,
config,
state,
names,
config.domain.clone(),
forwarded_by.to_string(),
forwarded_proto.to_string(),
api_domain.clone(),
);
}
mr = mr.route_layer(
ServiceBuilder::new()
.layer(timeout_layer)
.layer(axum::middleware::from_fn(x_via)),
);
mr = cors::apply_to_route(&config.cors, &config.cors, mr);
router = router.route(route, mr);
}
}
return Some(router);
}
None
}