change capture
This commit is contained in:
304
crates/api/src/routes/analytics.rs
Normal file
304
crates/api/src/routes/analytics.rs
Normal file
@@ -0,0 +1,304 @@
|
||||
//! Analytics API routes
|
||||
//!
|
||||
//! Provides read-only access to TimescaleDB continuous aggregates for dashboard
|
||||
//! widgets and time-series analytics. All data is pre-computed by TimescaleDB
|
||||
//! continuous aggregate policies — these endpoints simply query the materialized views.
|
||||
|
||||
use axum::{
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Json, Router,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use attune_common::repositories::analytics::AnalyticsRepository;
|
||||
|
||||
use crate::{
|
||||
auth::middleware::RequireAuth,
|
||||
dto::{
|
||||
analytics::{
|
||||
AnalyticsQueryParams, DashboardAnalyticsResponse, EnforcementVolumeResponse,
|
||||
EventVolumeResponse, ExecutionStatusTimeSeriesResponse, ExecutionThroughputResponse,
|
||||
FailureRateResponse, TimeSeriesPoint, WorkerStatusTimeSeriesResponse,
|
||||
},
|
||||
common::ApiResponse,
|
||||
},
|
||||
middleware::ApiResult,
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
/// Get a combined dashboard analytics payload.
|
||||
///
|
||||
/// Returns all key metrics in a single response to avoid multiple round-trips
|
||||
/// from the dashboard page. Includes execution throughput, status transitions,
|
||||
/// event volume, enforcement volume, worker status, and failure rate.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/analytics/dashboard",
|
||||
tag = "analytics",
|
||||
params(AnalyticsQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Dashboard analytics", body = inline(ApiResponse<DashboardAnalyticsResponse>)),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_dashboard_analytics(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Query(query): Query<AnalyticsQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let range = query.to_time_range();
|
||||
|
||||
// Run all aggregate queries concurrently
|
||||
let (throughput, status, events, enforcements, workers, failure_rate) = tokio::try_join!(
|
||||
AnalyticsRepository::execution_throughput_hourly(&state.db, &range),
|
||||
AnalyticsRepository::execution_status_hourly(&state.db, &range),
|
||||
AnalyticsRepository::event_volume_hourly(&state.db, &range),
|
||||
AnalyticsRepository::enforcement_volume_hourly(&state.db, &range),
|
||||
AnalyticsRepository::worker_status_hourly(&state.db, &range),
|
||||
AnalyticsRepository::execution_failure_rate(&state.db, &range),
|
||||
)?;
|
||||
|
||||
let response = DashboardAnalyticsResponse {
|
||||
since: range.since,
|
||||
until: range.until,
|
||||
execution_throughput: throughput.into_iter().map(Into::into).collect(),
|
||||
execution_status: status.into_iter().map(Into::into).collect(),
|
||||
event_volume: events.into_iter().map(Into::into).collect(),
|
||||
enforcement_volume: enforcements.into_iter().map(Into::into).collect(),
|
||||
worker_status: workers.into_iter().map(Into::into).collect(),
|
||||
failure_rate: FailureRateResponse::from_summary(failure_rate, &range),
|
||||
};
|
||||
|
||||
Ok((StatusCode::OK, Json(ApiResponse::new(response))))
|
||||
}
|
||||
|
||||
/// Get execution status transitions over time.
|
||||
///
|
||||
/// Returns hourly buckets of execution status transitions (e.g., how many
|
||||
/// executions moved to "completed", "failed", "running" per hour).
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/analytics/executions/status",
|
||||
tag = "analytics",
|
||||
params(AnalyticsQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Execution status transitions", body = inline(ApiResponse<ExecutionStatusTimeSeriesResponse>)),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_execution_status_analytics(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Query(query): Query<AnalyticsQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let range = query.to_time_range();
|
||||
let rows = AnalyticsRepository::execution_status_hourly(&state.db, &range).await?;
|
||||
|
||||
let data: Vec<TimeSeriesPoint> = rows.into_iter().map(Into::into).collect();
|
||||
|
||||
let response = ExecutionStatusTimeSeriesResponse {
|
||||
since: range.since,
|
||||
until: range.until,
|
||||
data,
|
||||
};
|
||||
|
||||
Ok((StatusCode::OK, Json(ApiResponse::new(response))))
|
||||
}
|
||||
|
||||
/// Get execution throughput over time.
|
||||
///
|
||||
/// Returns hourly buckets of execution creation counts.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/analytics/executions/throughput",
|
||||
tag = "analytics",
|
||||
params(AnalyticsQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Execution throughput", body = inline(ApiResponse<ExecutionThroughputResponse>)),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_execution_throughput_analytics(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Query(query): Query<AnalyticsQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let range = query.to_time_range();
|
||||
let rows = AnalyticsRepository::execution_throughput_hourly(&state.db, &range).await?;
|
||||
|
||||
let data: Vec<TimeSeriesPoint> = rows.into_iter().map(Into::into).collect();
|
||||
|
||||
let response = ExecutionThroughputResponse {
|
||||
since: range.since,
|
||||
until: range.until,
|
||||
data,
|
||||
};
|
||||
|
||||
Ok((StatusCode::OK, Json(ApiResponse::new(response))))
|
||||
}
|
||||
|
||||
/// Get the execution failure rate summary.
|
||||
///
|
||||
/// Returns aggregate failure/timeout/completion counts and the failure rate
|
||||
/// percentage over the requested time range.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/analytics/executions/failure-rate",
|
||||
tag = "analytics",
|
||||
params(AnalyticsQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Failure rate summary", body = inline(ApiResponse<FailureRateResponse>)),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_failure_rate_analytics(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Query(query): Query<AnalyticsQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let range = query.to_time_range();
|
||||
let summary = AnalyticsRepository::execution_failure_rate(&state.db, &range).await?;
|
||||
|
||||
let response = FailureRateResponse::from_summary(summary, &range);
|
||||
|
||||
Ok((StatusCode::OK, Json(ApiResponse::new(response))))
|
||||
}
|
||||
|
||||
/// Get event volume over time.
|
||||
///
|
||||
/// Returns hourly buckets of event creation counts, aggregated across all triggers.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/analytics/events/volume",
|
||||
tag = "analytics",
|
||||
params(AnalyticsQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Event volume", body = inline(ApiResponse<EventVolumeResponse>)),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_event_volume_analytics(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Query(query): Query<AnalyticsQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let range = query.to_time_range();
|
||||
let rows = AnalyticsRepository::event_volume_hourly(&state.db, &range).await?;
|
||||
|
||||
let data: Vec<TimeSeriesPoint> = rows.into_iter().map(Into::into).collect();
|
||||
|
||||
let response = EventVolumeResponse {
|
||||
since: range.since,
|
||||
until: range.until,
|
||||
data,
|
||||
};
|
||||
|
||||
Ok((StatusCode::OK, Json(ApiResponse::new(response))))
|
||||
}
|
||||
|
||||
/// Get worker status transitions over time.
|
||||
///
|
||||
/// Returns hourly buckets of worker status changes (online/offline/draining).
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/analytics/workers/status",
|
||||
tag = "analytics",
|
||||
params(AnalyticsQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Worker status transitions", body = inline(ApiResponse<WorkerStatusTimeSeriesResponse>)),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_worker_status_analytics(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Query(query): Query<AnalyticsQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let range = query.to_time_range();
|
||||
let rows = AnalyticsRepository::worker_status_hourly(&state.db, &range).await?;
|
||||
|
||||
let data: Vec<TimeSeriesPoint> = rows.into_iter().map(Into::into).collect();
|
||||
|
||||
let response = WorkerStatusTimeSeriesResponse {
|
||||
since: range.since,
|
||||
until: range.until,
|
||||
data,
|
||||
};
|
||||
|
||||
Ok((StatusCode::OK, Json(ApiResponse::new(response))))
|
||||
}
|
||||
|
||||
/// Get enforcement volume over time.
|
||||
///
|
||||
/// Returns hourly buckets of enforcement creation counts, aggregated across all rules.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/analytics/enforcements/volume",
|
||||
tag = "analytics",
|
||||
params(AnalyticsQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Enforcement volume", body = inline(ApiResponse<EnforcementVolumeResponse>)),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_enforcement_volume_analytics(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Query(query): Query<AnalyticsQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let range = query.to_time_range();
|
||||
let rows = AnalyticsRepository::enforcement_volume_hourly(&state.db, &range).await?;
|
||||
|
||||
let data: Vec<TimeSeriesPoint> = rows.into_iter().map(Into::into).collect();
|
||||
|
||||
let response = EnforcementVolumeResponse {
|
||||
since: range.since,
|
||||
until: range.until,
|
||||
data,
|
||||
};
|
||||
|
||||
Ok((StatusCode::OK, Json(ApiResponse::new(response))))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Router
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Build the analytics routes.
|
||||
///
|
||||
/// Mounts:
|
||||
/// - `GET /analytics/dashboard` — combined dashboard payload
|
||||
/// - `GET /analytics/executions/status` — execution status transitions
|
||||
/// - `GET /analytics/executions/throughput` — execution creation throughput
|
||||
/// - `GET /analytics/executions/failure-rate` — failure rate summary
|
||||
/// - `GET /analytics/events/volume` — event creation volume
|
||||
/// - `GET /analytics/workers/status` — worker status transitions
|
||||
/// - `GET /analytics/enforcements/volume` — enforcement creation volume
|
||||
pub fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.route("/analytics/dashboard", get(get_dashboard_analytics))
|
||||
.route(
|
||||
"/analytics/executions/status",
|
||||
get(get_execution_status_analytics),
|
||||
)
|
||||
.route(
|
||||
"/analytics/executions/throughput",
|
||||
get(get_execution_throughput_analytics),
|
||||
)
|
||||
.route(
|
||||
"/analytics/executions/failure-rate",
|
||||
get(get_failure_rate_analytics),
|
||||
)
|
||||
.route("/analytics/events/volume", get(get_event_volume_analytics))
|
||||
.route(
|
||||
"/analytics/workers/status",
|
||||
get(get_worker_status_analytics),
|
||||
)
|
||||
.route(
|
||||
"/analytics/enforcements/volume",
|
||||
get(get_enforcement_volume_analytics),
|
||||
)
|
||||
}
|
||||
245
crates/api/src/routes/history.rs
Normal file
245
crates/api/src/routes/history.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
//! Entity history API routes
|
||||
//!
|
||||
//! Provides read-only access to the TimescaleDB entity history hypertables.
|
||||
//! History records are written by PostgreSQL triggers — these endpoints only query them.
|
||||
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Json, Router,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use attune_common::models::entity_history::HistoryEntityType;
|
||||
use attune_common::repositories::entity_history::EntityHistoryRepository;
|
||||
|
||||
use crate::{
|
||||
auth::middleware::RequireAuth,
|
||||
dto::{
|
||||
common::{PaginatedResponse, PaginationMeta, PaginationParams},
|
||||
history::{HistoryQueryParams, HistoryRecordResponse},
|
||||
},
|
||||
middleware::{ApiError, ApiResult},
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
/// List history records for a given entity type.
|
||||
///
|
||||
/// Supported entity types: `execution`, `worker`, `enforcement`, `event`.
|
||||
/// Returns a paginated list of change records ordered by time descending.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/history/{entity_type}",
|
||||
tag = "history",
|
||||
params(
|
||||
("entity_type" = String, Path, description = "Entity type: execution, worker, enforcement, or event"),
|
||||
HistoryQueryParams,
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Paginated list of history records", body = PaginatedResponse<HistoryRecordResponse>),
|
||||
(status = 400, description = "Invalid entity type"),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn list_entity_history(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(entity_type_str): Path<String>,
|
||||
Query(query): Query<HistoryQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let entity_type = parse_entity_type(&entity_type_str)?;
|
||||
|
||||
let repo_params = query.to_repo_params();
|
||||
|
||||
let (records, total) = tokio::try_join!(
|
||||
EntityHistoryRepository::query(&state.db, entity_type, &repo_params),
|
||||
EntityHistoryRepository::count(&state.db, entity_type, &repo_params),
|
||||
)?;
|
||||
|
||||
let data: Vec<HistoryRecordResponse> = records.into_iter().map(Into::into).collect();
|
||||
|
||||
let pagination_params = PaginationParams {
|
||||
page: query.page,
|
||||
page_size: query.page_size,
|
||||
};
|
||||
|
||||
let response = PaginatedResponse {
|
||||
data,
|
||||
pagination: PaginationMeta::new(
|
||||
pagination_params.page,
|
||||
pagination_params.page_size,
|
||||
total as u64,
|
||||
),
|
||||
};
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
/// Get history for a specific execution by ID.
|
||||
///
|
||||
/// Returns all change records for the given execution, ordered by time descending.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/executions/{id}/history",
|
||||
tag = "history",
|
||||
params(
|
||||
("id" = i64, Path, description = "Execution ID"),
|
||||
HistoryQueryParams,
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "History records for the execution", body = PaginatedResponse<HistoryRecordResponse>),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_execution_history(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(id): Path<i64>,
|
||||
Query(query): Query<HistoryQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
get_entity_history_by_id(&state, HistoryEntityType::Execution, id, query).await
|
||||
}
|
||||
|
||||
/// Get history for a specific worker by ID.
|
||||
///
|
||||
/// Returns all change records for the given worker, ordered by time descending.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/workers/{id}/history",
|
||||
tag = "history",
|
||||
params(
|
||||
("id" = i64, Path, description = "Worker ID"),
|
||||
HistoryQueryParams,
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "History records for the worker", body = PaginatedResponse<HistoryRecordResponse>),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_worker_history(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(id): Path<i64>,
|
||||
Query(query): Query<HistoryQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
get_entity_history_by_id(&state, HistoryEntityType::Worker, id, query).await
|
||||
}
|
||||
|
||||
/// Get history for a specific enforcement by ID.
|
||||
///
|
||||
/// Returns all change records for the given enforcement, ordered by time descending.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/enforcements/{id}/history",
|
||||
tag = "history",
|
||||
params(
|
||||
("id" = i64, Path, description = "Enforcement ID"),
|
||||
HistoryQueryParams,
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "History records for the enforcement", body = PaginatedResponse<HistoryRecordResponse>),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_enforcement_history(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(id): Path<i64>,
|
||||
Query(query): Query<HistoryQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
get_entity_history_by_id(&state, HistoryEntityType::Enforcement, id, query).await
|
||||
}
|
||||
|
||||
/// Get history for a specific event by ID.
|
||||
///
|
||||
/// Returns all change records for the given event, ordered by time descending.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/events/{id}/history",
|
||||
tag = "history",
|
||||
params(
|
||||
("id" = i64, Path, description = "Event ID"),
|
||||
HistoryQueryParams,
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "History records for the event", body = PaginatedResponse<HistoryRecordResponse>),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_event_history(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(id): Path<i64>,
|
||||
Query(query): Query<HistoryQueryParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
get_entity_history_by_id(&state, HistoryEntityType::Event, id, query).await
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Parse and validate the entity type path parameter.
|
||||
fn parse_entity_type(s: &str) -> Result<HistoryEntityType, ApiError> {
|
||||
s.parse::<HistoryEntityType>().map_err(ApiError::BadRequest)
|
||||
}
|
||||
|
||||
/// Shared implementation for `GET /<entities>/:id/history` endpoints.
|
||||
async fn get_entity_history_by_id(
|
||||
state: &AppState,
|
||||
entity_type: HistoryEntityType,
|
||||
entity_id: i64,
|
||||
query: HistoryQueryParams,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// Override entity_id from the path — ignore any entity_id in query params
|
||||
let mut repo_params = query.to_repo_params();
|
||||
repo_params.entity_id = Some(entity_id);
|
||||
|
||||
let (records, total) = tokio::try_join!(
|
||||
EntityHistoryRepository::query(&state.db, entity_type, &repo_params),
|
||||
EntityHistoryRepository::count(&state.db, entity_type, &repo_params),
|
||||
)?;
|
||||
|
||||
let data: Vec<HistoryRecordResponse> = records.into_iter().map(Into::into).collect();
|
||||
|
||||
let pagination_params = PaginationParams {
|
||||
page: query.page,
|
||||
page_size: query.page_size,
|
||||
};
|
||||
|
||||
let response = PaginatedResponse {
|
||||
data,
|
||||
pagination: PaginationMeta::new(
|
||||
pagination_params.page,
|
||||
pagination_params.page_size,
|
||||
total as u64,
|
||||
),
|
||||
};
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Router
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Build the history routes.
|
||||
///
|
||||
/// Mounts:
|
||||
/// - `GET /history/:entity_type` — generic history query
|
||||
/// - `GET /executions/:id/history` — execution-specific history
|
||||
/// - `GET /workers/:id/history` — worker-specific history (note: currently no /workers base route exists)
|
||||
/// - `GET /enforcements/:id/history` — enforcement-specific history
|
||||
/// - `GET /events/:id/history` — event-specific history
|
||||
pub fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
// Generic history endpoint
|
||||
.route("/history/{entity_type}", get(list_entity_history))
|
||||
// Entity-specific convenience endpoints
|
||||
.route("/executions/{id}/history", get(get_execution_history))
|
||||
.route("/workers/{id}/history", get(get_worker_history))
|
||||
.route("/enforcements/{id}/history", get(get_enforcement_history))
|
||||
.route("/events/{id}/history", get(get_event_history))
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
//! API route modules
|
||||
|
||||
pub mod actions;
|
||||
pub mod analytics;
|
||||
pub mod auth;
|
||||
pub mod events;
|
||||
pub mod executions;
|
||||
pub mod health;
|
||||
pub mod history;
|
||||
pub mod inquiries;
|
||||
pub mod keys;
|
||||
pub mod packs;
|
||||
@@ -14,10 +16,12 @@ pub mod webhooks;
|
||||
pub mod workflows;
|
||||
|
||||
pub use actions::routes as action_routes;
|
||||
pub use analytics::routes as analytics_routes;
|
||||
pub use auth::routes as auth_routes;
|
||||
pub use events::routes as event_routes;
|
||||
pub use executions::routes as execution_routes;
|
||||
pub use health::routes as health_routes;
|
||||
pub use history::routes as history_routes;
|
||||
pub use inquiries::routes as inquiry_routes;
|
||||
pub use keys::routes as key_routes;
|
||||
pub use packs::routes as pack_routes;
|
||||
|
||||
Reference in New Issue
Block a user