use axum::{
middleware,
routing::{delete, get, patch, post, put},
Router,
};
use crate::handlers;
use crate::middleware::{auth_middleware, rate_limit_middleware};
use crate::AppState;
pub fn create_router(state: AppState) -> Router<AppState> {
let public_routes = Router::new()
.route("/health", get(handlers::health::health_check))
.route("/health/live", get(handlers::health::liveness_check))
.route("/health/ready", get(handlers::health::readiness_check))
.route("/health/circuits", get(handlers::health::circuit_breaker_status))
.route("/api/v1/plugins/search", post(handlers::plugins::search_plugins))
.route("/api/v1/plugins/{name}", get(handlers::plugins::get_plugin))
.route("/api/v1/plugins/{name}/versions/{version}", get(handlers::plugins::get_version))
.route(
"/api/v1/plugins/{name}/versions/{version}/download",
get(handlers::plugins::download_version),
)
.route("/api/v1/plugins/{name}/reviews", get(handlers::reviews::get_reviews))
.route("/api/v1/plugins/{name}/badges", get(handlers::admin::get_plugin_badges))
.route("/api/v1/plugins/{name}/security", get(handlers::plugins::get_plugin_security))
.route("/api/v1/stats", get(handlers::stats::get_stats))
.route("/api/v1/auth/register", post(handlers::auth::register))
.route("/api/v1/auth/login", post(handlers::auth::login))
.route("/api/v1/auth/token/refresh", post(handlers::auth::refresh_token))
.route(
"/api/v1/auth/password/reset-request",
post(handlers::auth::request_password_reset),
)
.route("/api/v1/auth/password/reset", post(handlers::auth::confirm_password_reset))
.route("/api/v1/auth/verify-email", get(handlers::verification::verify_email))
.route("/api/v1/faq", get(handlers::faq::get_faq))
.route("/api/v1/legal/terms", get(handlers::legal::get_terms))
.route("/api/v1/legal/privacy", get(handlers::legal::get_privacy))
.route("/api/v1/legal/dpa", get(handlers::legal::get_dpa))
.route("/api/v1/status", get(handlers::status::get_status))
.route("/api/v1/support/contact", post(handlers::support::submit_contact))
.route(
"/api/v1/hosted-mocks/{deployment_id}/log-ingest",
post(handlers::hosted_mocks::ingest_runtime_logs),
)
.route(
"/api/v1/hosted-mocks/{deployment_id}/otlp/v1/traces",
post(handlers::otlp::ingest_traces),
)
.route(
"/api/v1/hosted-mocks/{deployment_id}/captures/ingest",
post(handlers::hosted_mocks::ingest_runtime_captures),
)
.route("/api/v1/waitlist/subscribe", post(handlers::waitlist::subscribe))
.route("/api/v1/waitlist/unsubscribe", get(handlers::waitlist::unsubscribe))
.route(
"/api/v1/internal/test-runs/{id}/start",
post(handlers::internal_test_runs::run_started),
)
.route(
"/api/v1/internal/test-runs/{id}/events",
post(handlers::internal_test_runs::run_event),
)
.route(
"/api/v1/internal/test-runs/{id}/finish",
post(handlers::internal_test_runs::run_finished),
)
.route(
"/api/v1/internal/capture-sessions/{id}/exchanges",
get(handlers::internal_test_runs::get_capture_exchanges),
)
.route(
"/api/v1/internal/workspaces/{id}/endpoint-hits",
get(handlers::internal_test_runs::get_workspace_endpoint_hits),
)
.route(
"/api/v1/internal/fitness-functions/{id}",
get(handlers::internal_test_runs::get_fitness_function),
)
.route(
"/api/v1/internal/deployments/{id}/latency-stats",
get(handlers::internal_test_runs::get_deployment_latency_stats),
)
.route(
"/api/v1/internal/monitored-services/{id}/contract-stability",
get(handlers::internal_test_runs::get_monitored_service_contract_stability),
)
.route(
"/api/v1/internal/hosted-mocks/{id}/chaos",
post(handlers::internal_test_runs::proxy_chaos_toggle),
)
.route(
"/api/v1/internal/tunnel-reservations/by-subdomain/{subdomain}",
get(handlers::internal_test_runs::get_tunnel_reservation_by_subdomain),
)
.route("/api/v1/marketplace/scenarios/search", post(handlers::scenarios::search_scenarios))
.route("/api/v1/marketplace/scenarios/{name}", get(handlers::scenarios::get_scenario))
.route("/api/v1/marketplace/scenarios/{name}/versions/{version}", get(handlers::scenarios::get_scenario_version))
.route("/api/v1/marketplace/scenarios/{name}/reviews", get(handlers::scenario_reviews::get_scenario_reviews))
.route("/api/v1/marketplace/templates/search", post(handlers::templates::search_templates))
.route("/api/v1/marketplace/templates/{name}/{version}", get(handlers::templates::get_template))
.route("/api/v1/marketplace/templates/{name}/{version}/reviews", get(handlers::template_reviews::get_template_reviews))
.route("/api/v1/showcase/entries", get(handlers::showcase::list_showcase_entries))
.route("/api/v1/showcase/entries/{slug}", get(handlers::showcase::get_showcase_entry))
.route("/api/v1/learning/tracks", get(handlers::showcase::list_learning_tracks))
.route("/api/v1/learning/tracks/{slug}", get(handlers::showcase::get_learning_track))
.route("/api/v1/learning/recipes", get(handlers::showcase::list_learning_recipes))
.route("/api/v1/learning/recipes/{slug}", get(handlers::showcase::get_learning_recipe))
.route_layer(middleware::from_fn(rate_limit_middleware));
let auth_routes = Router::new()
.route("/api/v1/plugins/publish", post(handlers::plugins::publish_plugin))
.route(
"/api/v1/plugins/{name}/versions/{version}/yank",
delete(handlers::plugins::yank_version),
)
.route("/api/v1/plugins/{name}/reviews", post(handlers::reviews::submit_review))
.route(
"/api/v1/plugins/{name}/reviews/{review_id}/vote",
post(handlers::reviews::vote_review),
)
.route(
"/api/v1/plugins/{name}/reviews/{review_id}/respond",
post(handlers::reviews::respond_to_review),
)
.route(
"/api/v1/plugins/{name}/reviews/{review_id}/respond",
delete(handlers::reviews::clear_review_response),
)
.route("/api/v1/auth/verify", get(handlers::auth::verify_token))
.route("/api/v1/auth/me", get(handlers::auth::me))
.route(
"/api/v1/users/me/public-keys",
get(handlers::public_keys::list_my_public_keys),
)
.route(
"/api/v1/users/me/public-keys",
post(handlers::public_keys::create_my_public_key),
)
.route(
"/api/v1/users/me/public-keys/{id}",
delete(handlers::public_keys::revoke_my_public_key),
)
.route(
"/api/v1/users/me/public-keys/{id}/rotate",
post(handlers::public_keys::rotate_my_public_key),
)
.route(
"/api/v1/organizations/{org_id}/public-keys",
get(handlers::public_keys::list_org_public_keys),
)
.route(
"/api/v1/organizations/{org_id}/trust-roots",
get(handlers::trust_roots::list_trust_roots)
.post(handlers::trust_roots::create_trust_root),
)
.route(
"/api/v1/organizations/{org_id}/trust-roots/{root_id}/revoke",
post(handlers::trust_roots::revoke_trust_root),
)
.route("/api/v1/auth/2fa/setup", get(handlers::two_factor::setup_2fa))
.route("/api/v1/auth/2fa/verify-setup", post(handlers::two_factor::verify_2fa_setup_with_secret))
.route("/api/v1/auth/2fa/disable", post(handlers::two_factor::disable_2fa))
.route("/api/v1/auth/2fa/status", get(handlers::two_factor::get_2fa_status))
.route("/api/v1/auth/change-password", post(handlers::auth::change_password))
.route("/api/v1/users/me", get(handlers::users_me::get_me))
.route("/api/v1/users/me", patch(handlers::users_me::update_me))
.route("/api/v1/users/me/notifications", patch(handlers::users_me::update_notifications))
.route("/api/v1/users/me/preferences", get(handlers::users_me::get_preferences))
.route("/api/v1/users/me/preferences", patch(handlers::users_me::update_preferences))
.route("/api/v1/organizations", get(handlers::organizations::list_organizations))
.route("/api/v1/organizations", post(handlers::organizations::create_organization))
.route("/api/v1/organizations/{org_id}", get(handlers::organizations::get_organization))
.route("/api/v1/organizations/{org_id}", patch(handlers::organizations::update_organization))
.route("/api/v1/organizations/{org_id}", delete(handlers::organizations::delete_organization))
.route("/api/v1/organizations/{org_id}/members", get(handlers::organizations::get_organization_members))
.route("/api/v1/organizations/{org_id}/members", post(handlers::organizations::add_organization_member))
.route("/api/v1/organizations/{org_id}/members/{user_id}", patch(handlers::organizations::update_organization_member_role))
.route("/api/v1/organizations/{org_id}/members/{user_id}", delete(handlers::organizations::remove_organization_member))
.route("/api/v1/organizations/slug/{slug}", get(handlers::organizations::get_organization_by_slug))
.route("/api/v1/organizations/{org_id}/quota", get(handlers::organizations::get_organization_quota))
.route("/api/v1/organizations/{org_id}/quota", put(handlers::organizations::set_organization_quota))
.route("/api/v1/organizations/{org_id}/invitations", get(handlers::organizations::list_invitations))
.route("/api/v1/organizations/{org_id}/invitations", post(handlers::organizations::create_invitation))
.route("/api/v1/organizations/{org_id}/invitations/{nonce}", delete(handlers::organizations::revoke_invitation))
.route("/api/v1/invitations/{token}", get(handlers::organizations::get_invitation))
.route("/api/v1/invitations/{token}/accept", post(handlers::organizations::accept_invitation))
.route("/api/v1/users/email/{email}", get(handlers::organizations::find_user_by_email_admin))
.route("/api/v1/users/username/{username}", get(handlers::organizations::find_user_by_username_admin))
.route("/api/v1/users/{user_id}/verify", post(handlers::organizations::mark_user_verified_admin))
.route("/api/v1/organizations/{org_id}/settings", get(handlers::organization_settings::get_organization_settings))
.route("/api/v1/organizations/{org_id}/settings", patch(handlers::organization_settings::update_organization_settings))
.route("/api/v1/organizations/{org_id}/settings/ai", get(handlers::organization_settings::get_organization_ai_settings))
.route("/api/v1/organizations/{org_id}/settings/ai", patch(handlers::organization_settings::update_organization_ai_settings))
.route("/api/v1/organizations/{org_id}/usage", get(handlers::organization_settings::get_organization_usage))
.route("/api/v1/organizations/{org_id}/billing", get(handlers::organization_settings::get_organization_billing))
.route("/api/v1/organizations/{org_id}/analytics/pillars", get(handlers::pillar_analytics::get_org_pillar_metrics))
.route("/api/v1/workspaces/{workspace_id}/analytics/pillars", get(handlers::pillar_analytics::get_workspace_pillar_metrics))
.route("/api/v1/analytics/pillars/events", post(handlers::pillar_analytics::record_pillar_event))
.route("/api/v1/sso/config", get(handlers::sso::get_sso_config))
.route("/api/v1/sso/config", post(handlers::sso::create_sso_config))
.route("/api/v1/sso/config", delete(handlers::sso::delete_sso_config))
.route("/api/v1/sso/enable", post(handlers::sso::enable_sso))
.route("/api/v1/sso/disable", post(handlers::sso::disable_sso))
.route("/api/v1/sso/saml/metadata/{org_slug}", get(handlers::sso::get_saml_metadata))
.route("/api/v1/billing/subscription", get(handlers::billing::get_subscription))
.route("/api/v1/billing/checkout", post(handlers::billing::create_checkout))
.route("/api/v1/billing/portal", post(handlers::billing::create_portal_session))
.route("/api/v1/billing/invoices", get(handlers::billing::list_invoices))
.route("/api/v1/auth/verify-email/resend", post(handlers::verification::resend_verification))
.route("/api/v1/hosted-mocks/specs/upload", post(handlers::hosted_mocks::upload_spec))
.route("/api/v1/hosted-mocks", post(handlers::hosted_mocks::create_deployment))
.route("/api/v1/hosted-mocks", get(handlers::hosted_mocks::list_deployments))
.route("/api/v1/hosted-mocks/{deployment_id}", get(handlers::hosted_mocks::get_deployment))
.route("/api/v1/hosted-mocks/{deployment_id}/status", patch(handlers::hosted_mocks::update_deployment_status))
.route("/api/v1/hosted-mocks/{deployment_id}", delete(handlers::hosted_mocks::delete_deployment))
.route("/api/v1/hosted-mocks/{deployment_id}/redeploy", post(handlers::hosted_mocks::redeploy_deployment))
.route("/api/v1/hosted-mocks/{deployment_id}/smoke-runs", post(handlers::hosted_mocks::trigger_smoke_run))
.route("/api/v1/hosted-mocks/{deployment_id}/set-domain", post(handlers::hosted_mocks::set_domain))
.route("/api/v1/hosted-mocks/{deployment_id}/custom-domain", get(handlers::hosted_mocks::get_custom_domain))
.route("/api/v1/hosted-mocks/{deployment_id}/custom-domain", delete(handlers::hosted_mocks::clear_custom_domain))
.route("/api/v1/hosted-mocks/{deployment_id}/stop", post(handlers::hosted_mocks::stop_deployment))
.route("/api/v1/hosted-mocks/{deployment_id}/start", post(handlers::hosted_mocks::start_deployment))
.route("/api/v1/hosted-mocks/{deployment_id}/logs", get(handlers::hosted_mocks::get_deployment_logs))
.route("/api/v1/hosted-mocks/{deployment_id}/runtime-logs", get(handlers::hosted_mocks::get_runtime_logs))
.route("/api/v1/hosted-mocks/{deployment_id}/runtime-logs/stream", get(handlers::hosted_mocks::stream_runtime_logs))
.route("/api/v1/hosted-mocks/{deployment_id}/runtime-requests", get(handlers::hosted_mocks::get_runtime_requests))
.route("/api/v1/hosted-mocks/{deployment_id}/captures", get(handlers::hosted_mocks::list_recorder_captures))
.route("/api/v1/hosted-mocks/{deployment_id}/captures/{capture_id}", get(handlers::hosted_mocks::get_recorder_capture))
.route("/api/v1/hosted-mocks/{deployment_id}/captures/{capture_id}/response", get(handlers::hosted_mocks::get_recorder_capture_response))
.route("/api/v1/hosted-mocks/{deployment_id}/captures/export/har", get(handlers::hosted_mocks::export_recorder_captures_har))
.route("/api/v1/hosted-mocks/{deployment_id}/captures/export/jsonl", get(handlers::hosted_mocks::export_recorder_captures_jsonl))
.route("/api/v1/hosted-mocks/{deployment_id}/captures/status", get(handlers::hosted_mocks::get_recorder_status))
.route("/api/v1/hosted-mocks/{deployment_id}/captures/enable", post(handlers::hosted_mocks::enable_recorder))
.route("/api/v1/hosted-mocks/{deployment_id}/captures/disable", post(handlers::hosted_mocks::disable_recorder))
.route("/api/v1/hosted-mocks/{deployment_id}/captures/clear", post(handlers::hosted_mocks::clear_recorder))
.route("/api/v1/hosted-mocks/{deployment_id}/captures/{capture_id}/replay", post(handlers::hosted_mocks::replay_recorder_capture))
.route(
"/api/v1/hosted-mocks/{deployment_id}/plugins",
get(handlers::cloud_plugin_attachments::list_attachments)
.post(handlers::cloud_plugin_attachments::attach_plugin),
)
.route(
"/api/v1/hosted-mocks/{deployment_id}/plugins/{attachment_id}",
patch(handlers::cloud_plugin_attachments::update_attachment)
.delete(handlers::cloud_plugin_attachments::detach_plugin),
)
.route(
"/api/v1/hosted-mocks/{deployment_id}/plugins/usage",
get(handlers::cloud_plugin_attachments::get_plugin_usage),
)
.route("/api/v1/hosted-mocks/{deployment_id}/state-machines", get(handlers::hosted_mocks::list_deployment_state_machines))
.route("/api/v1/hosted-mocks/{deployment_id}/state-machines/instances", get(handlers::hosted_mocks::list_deployment_state_machine_instances))
.route("/api/v1/hosted-mocks/{deployment_id}/state-machines/{resource_type}", get(handlers::hosted_mocks::get_deployment_state_machine))
.route("/api/v1/hosted-mocks/{deployment_id}/traces", get(handlers::otlp::list_traces))
.route("/api/v1/hosted-mocks/{deployment_id}/traces/{trace_id}", get(handlers::otlp::get_trace))
.route("/api/v1/hosted-mocks/{deployment_id}/metrics", get(handlers::hosted_mocks::get_deployment_metrics))
.route("/api/v1/projects", get(handlers::projects::list_projects))
.route("/api/v1/tokens", post(handlers::tokens::create_token))
.route("/api/v1/tokens", get(handlers::tokens::list_tokens))
.route("/api/v1/tokens/scopes", get(handlers::tokens::list_scopes))
.route("/api/v1/tokens/{token_id}", delete(handlers::tokens::delete_token))
.route("/api/v1/ai-studio/chat", post(handlers::ai_studio::chat))
.route(
"/api/v1/ai-studio/generate-openapi",
post(handlers::ai_studio::generate_openapi),
)
.route(
"/api/v1/ai-studio/explain-rule",
post(handlers::ai_studio::explain_rule),
)
.route(
"/api/v1/ai-studio/quota",
get(handlers::ai_studio::quota),
)
.route(
"/api/v1/ai-studio/voice/process",
post(handlers::ai_studio::voice_process),
)
.route(
"/api/v1/ai-studio/voice/transpile-hook",
post(handlers::ai_studio::voice_transpile_hook),
)
.route(
"/api/v1/ai-studio/voice/create-workspace-scenario",
post(handlers::ai_studio::voice_create_workspace_scenario),
)
.route(
"/api/v1/workspaces/{workspace_id}/mockai/rule-explanations",
get(handlers::mockai::list_rule_explanations),
)
.route(
"/api/v1/workspaces/{workspace_id}/mockai/rule-explanations/{rule_id}",
get(handlers::mockai::get_rule_explanation),
)
.route(
"/api/v1/workspaces/{workspace_id}/mockai/learn",
post(handlers::mockai::learn_from_examples),
)
.route(
"/api/v1/organizations/{org_id}/mockai/generate-openapi-from-traffic",
post(handlers::mockai::generate_openapi_from_traffic),
)
.route(
"/api/v1/workspaces/{workspace_id}/test-suites",
get(handlers::test_suites::list_suites)
.post(handlers::test_suites::create_suite),
)
.route(
"/api/v1/test-suites/{id}",
get(handlers::test_suites::get_suite)
.patch(handlers::test_suites::update_suite)
.delete(handlers::test_suites::delete_suite),
)
.route(
"/api/v1/test-suites/{id}/runs",
get(handlers::test_runs::list_suite_runs)
.post(handlers::test_runs::trigger_run),
)
.route(
"/api/v1/organizations/{org_id}/test-runs",
get(handlers::test_runs::list_org_runs),
)
.route(
"/api/v1/test-runs/{id}",
get(handlers::test_runs::get_run),
)
.route(
"/api/v1/test-runs/{id}/cancel",
post(handlers::test_runs::cancel_run),
)
.route(
"/api/v1/test-runs/{id}/stream",
get(handlers::test_runs::stream_run_events),
)
.route(
"/api/v1/test-suites/{suite_id}/schedules",
get(handlers::test_schedules::list_for_suite)
.post(handlers::test_schedules::create),
)
.route(
"/api/v1/test-schedules/{id}",
patch(handlers::test_schedules::set_enabled)
.delete(handlers::test_schedules::delete),
)
.route(
"/api/v1/organizations/{org_id}/tunnels",
get(handlers::tunnels::list_tunnels)
.post(handlers::tunnels::create_tunnel),
)
.route(
"/api/v1/tunnels/{id}",
get(handlers::tunnels::get_tunnel)
.patch(handlers::tunnels::update_tunnel)
.delete(handlers::tunnels::delete_tunnel),
)
.route(
"/api/v1/tunnels/{id}/verify-custom-domain",
post(handlers::tunnels::verify_custom_domain),
)
.route(
"/api/v1/tunnels/{id}/custom-domain-proof",
get(handlers::tunnels::get_custom_domain_proof),
)
.route(
"/api/v1/showcase/entries/{id}/like-toggle",
post(handlers::showcase::toggle_showcase_like),
)
.route(
"/api/v1/admin/showcase/entries",
get(handlers::showcase::admin_list_showcase_entries)
.post(handlers::showcase::admin_create_showcase_entry),
)
.route(
"/api/v1/admin/showcase/entries/{id}",
patch(handlers::showcase::admin_update_showcase_entry)
.delete(handlers::showcase::admin_delete_showcase_entry),
)
.route(
"/api/v1/learning/lessons/{lesson_id}/complete",
post(handlers::showcase::complete_learning_lesson),
)
.route(
"/api/v1/learning/progress",
get(handlers::showcase::list_learning_progress),
)
.route(
"/api/v1/workspaces/{workspace_id}/snapshots",
get(handlers::snapshots::list_snapshots)
.post(handlers::snapshots::capture_snapshot),
)
.route(
"/api/v1/snapshots/{id}",
get(handlers::snapshots::get_snapshot)
.delete(handlers::snapshots::delete_snapshot),
)
.route(
"/api/v1/snapshots/{id}/diff",
get(handlers::snapshots::diff_snapshot),
)
.route(
"/api/v1/snapshots/{id}/restore",
post(handlers::snapshots::restore_snapshot),
)
.route(
"/api/v1/workspaces/{workspace_id}/flows",
get(handlers::flows::list_flows)
.post(handlers::flows::create_flow),
)
.route(
"/api/v1/flows/{id}",
get(handlers::flows::get_flow)
.patch(handlers::flows::update_flow)
.delete(handlers::flows::delete_flow),
)
.route(
"/api/v1/flows/{id}/versions",
get(handlers::flows::list_flow_versions)
.post(handlers::flows::save_flow_version),
)
.route(
"/api/v1/flows/{id}/runs",
post(handlers::flows::trigger_run),
)
.route(
"/api/v1/flow-versions/{version_id}",
get(handlers::flows::get_flow_version),
)
.route(
"/api/v1/organizations/{org_id}/observability/saved-queries",
get(handlers::observability::list_saved_queries)
.post(handlers::observability::create_saved_query),
)
.route(
"/api/v1/observability/saved-queries/{id}",
patch(handlers::observability::update_saved_query)
.delete(handlers::observability::delete_saved_query),
)
.route(
"/api/v1/organizations/{org_id}/observability/dashboards",
get(handlers::observability::list_dashboards)
.post(handlers::observability::create_dashboard),
)
.route(
"/api/v1/observability/dashboards/{id}",
patch(handlers::observability::update_dashboard)
.delete(handlers::observability::delete_dashboard),
)
.route(
"/api/v1/organizations/{org_id}/observability/traces/query",
post(handlers::observability::query_traces),
)
.route(
"/api/v1/workspaces/{workspace_id}/chaos-campaigns",
get(handlers::chaos::list_campaigns)
.post(handlers::chaos::create_campaign),
)
.route(
"/api/v1/chaos-campaigns/{id}",
get(handlers::chaos::get_campaign)
.delete(handlers::chaos::delete_campaign),
)
.route(
"/api/v1/chaos-campaigns/{id}/reports",
get(handlers::chaos::list_campaign_reports),
)
.route(
"/api/v1/chaos-campaigns/{id}/runs",
post(handlers::chaos::trigger_run),
)
.route(
"/api/v1/workspaces/{workspace_id}/resilience-patterns",
get(handlers::chaos::list_patterns),
)
.route(
"/api/v1/workspaces/{workspace_id}/monitored-services",
get(handlers::contract_verification::list_monitored_services)
.post(handlers::contract_verification::create_monitored_service),
)
.route(
"/api/v1/monitored-services/{id}",
axum::routing::delete(handlers::contract_verification::delete_monitored_service),
)
.route(
"/api/v1/monitored-services/{id}/diffs",
get(handlers::contract_verification::list_service_diff_runs),
)
.route(
"/api/v1/monitored-services/{id}/diff",
post(handlers::contract_verification::trigger_diff_run),
)
.route(
"/api/v1/contract-diff-runs/{id}",
get(handlers::contract_verification::get_diff_run),
)
.route(
"/api/v1/contract-diff-runs/{id}/findings",
get(handlers::contract_verification::list_diff_findings),
)
.route(
"/api/v1/workspaces/{workspace_id}/fitness-functions",
get(handlers::contract_verification::list_fitness_functions)
.post(handlers::contract_verification::create_fitness_function),
)
.route(
"/api/v1/fitness-functions/{id}",
patch(handlers::contract_verification::update_fitness_function)
.delete(handlers::contract_verification::delete_fitness_function),
)
.route(
"/api/v1/workspaces/{workspace_id}/verification-suites",
get(handlers::contract_verification::list_verification_suites)
.post(handlers::contract_verification::create_verification_suite),
)
.route(
"/api/v1/verification-suites/{id}",
axum::routing::delete(handlers::contract_verification::delete_verification_suite),
)
.route(
"/api/v1/workspaces/{workspace_id}/capture-sessions",
get(handlers::captures::list_sessions)
.post(handlers::captures::create_session),
)
.route(
"/api/v1/capture-sessions/{id}/members",
patch(handlers::captures::modify_session_members),
)
.route(
"/api/v1/capture-sessions/{id}",
axum::routing::delete(handlers::captures::delete_session),
)
.route(
"/api/v1/workspaces/{workspace_id}/clone-models",
get(handlers::captures::list_clone_models),
)
.route(
"/api/v1/capture-sessions/{id}/train",
post(handlers::captures::train_clone_from_session),
)
.route(
"/api/v1/capture-sessions/{id}/replay",
post(handlers::captures::replay_capture_session),
)
.route(
"/api/v1/clone-models/{id}",
get(handlers::captures::get_clone_model)
.delete(handlers::captures::delete_clone_model),
)
.route(
"/api/v1/organizations/{org_id}/incidents",
get(handlers::incidents::list_incidents)
.post(handlers::incidents::raise_incident_external),
)
.route(
"/api/v1/organizations/{org_id}/incidents/stats",
get(handlers::incidents::get_stats),
)
.route(
"/api/v1/incidents/{id}",
get(handlers::incidents::get_incident),
)
.route(
"/api/v1/incidents/{id}/events",
get(handlers::incidents::list_incident_events),
)
.route(
"/api/v1/incidents/{id}/acknowledge",
post(handlers::incidents::acknowledge_incident),
)
.route(
"/api/v1/incidents/{id}/resolve",
post(handlers::incidents::resolve_incident),
)
.route(
"/api/v1/organizations/{org_id}/notification-channels",
get(handlers::notification_channels::list_channels)
.post(handlers::notification_channels::create_channel),
)
.route(
"/api/v1/organizations/{org_id}/notification-channels/{id}",
patch(handlers::notification_channels::update_channel)
.delete(handlers::notification_channels::delete_channel),
)
.route(
"/api/v1/organizations/{org_id}/notification-channels/{id}/test-fire",
post(handlers::notification_channels::test_fire_channel),
)
.route(
"/api/v1/organizations/{org_id}/routing-rules",
get(handlers::routing_rules::list_rules)
.post(handlers::routing_rules::create_rule),
)
.route(
"/api/v1/organizations/{org_id}/routing-rules/{id}",
patch(handlers::routing_rules::update_rule)
.delete(handlers::routing_rules::delete_rule),
)
.route(
"/api/v1/cloud-plugins/beta-interest",
post(handlers::cloud_plugins::submit_interest),
)
.route(
"/api/v1/cloud-plugins/beta-interest/me",
get(handlers::cloud_plugins::get_my_interest),
)
.route("/api/v1/usage", get(handlers::usage::get_usage))
.route("/api/v1/usage/history", get(handlers::usage::get_usage_history))
.route("/api/v1/usage/ai-tokens", post(handlers::usage::report_ai_tokens))
.route("/api/v1/usage/alerts", get(handlers::usage::list_usage_alerts))
.route(
"/api/v1/usage/alerts/{alert_id}/dismiss",
post(handlers::usage::dismiss_usage_alert),
)
.route("/api/v1/organizations/{org_id}/audit-logs", get(handlers::audit::get_audit_logs))
.route("/api/v1/gdpr/export", get(handlers::gdpr::export_data))
.route("/api/v1/gdpr/erase", delete(handlers::gdpr::delete_data))
.route("/api/v1/security/suspicious-activities", get(handlers::security::get_suspicious_activities))
.route("/api/v1/security/suspicious-activities/{id}/resolve", post(handlers::security::resolve_suspicious_activity))
.route("/api/v1/settings/byok", get(handlers::settings::get_byok_config))
.route("/api/v1/settings/byok", put(handlers::settings::update_byok_config))
.route("/api/v1/settings/byok", delete(handlers::settings::delete_byok_config))
.route("/api/v1/settings/byok/test", post(handlers::settings::test_byok_connection))
.route("/api/v1/tokens/{token_id}/rotate", post(handlers::token_rotation::rotate_token))
.route("/api/v1/tokens/rotation-status", get(handlers::token_rotation::get_tokens_needing_rotation))
.route("/api/v1/dashboard", get(handlers::cloud_dashboard::get_dashboard))
.route("/api/v1/dashboard/health", get(handlers::cloud_dashboard::get_health))
.route("/api/v1/dashboard/logs", get(handlers::cloud_dashboard::get_logs))
.route("/api/v1/workspaces", get(handlers::cloud_workspaces::list_workspaces))
.route("/api/v1/workspaces", post(handlers::cloud_workspaces::create_workspace))
.route("/api/v1/workspaces/order", put(handlers::workspace_ordering::reorder_workspaces))
.route("/api/v1/workspaces/{id}", get(handlers::cloud_workspaces::get_workspace))
.route("/api/v1/workspaces/{id}", patch(handlers::cloud_workspaces::update_workspace))
.route("/api/v1/workspaces/{id}", delete(handlers::cloud_workspaces::delete_workspace))
.route("/api/v1/workspaces/{id}/activate", post(handlers::workspace_ordering::activate_workspace))
.route(
"/api/v1/workspaces/{id}/active-scenarios",
get(handlers::federations::get_workspace_active_scenarios),
)
.route("/api/v1/workspaces/{workspace_id}/environments", get(handlers::workspace_environments::list_environments))
.route("/api/v1/workspaces/{workspace_id}/environments", post(handlers::workspace_environments::create_environment))
.route("/api/v1/workspaces/{workspace_id}/environments/order", put(handlers::workspace_environments::reorder_environments))
.route("/api/v1/workspaces/{workspace_id}/environments/{environment_id}", put(handlers::workspace_environments::update_environment))
.route("/api/v1/workspaces/{workspace_id}/environments/{environment_id}", delete(handlers::workspace_environments::delete_environment))
.route("/api/v1/workspaces/{workspace_id}/environments/{environment_id}/activate", post(handlers::workspace_environments::activate_environment))
.route("/api/v1/workspaces/{workspace_id}/environments/{environment_id}/variables", get(handlers::workspace_environments::list_variables))
.route("/api/v1/workspaces/{workspace_id}/environments/{environment_id}/variables", post(handlers::workspace_environments::set_variable))
.route("/api/v1/workspaces/{workspace_id}/environments/{environment_id}/variables/{variable_name}", delete(handlers::workspace_environments::delete_variable))
.route("/api/v1/workspaces/{workspace_id}/folders", post(handlers::workspace_folders::create_folder))
.route("/api/v1/workspaces/{workspace_id}/folders/{folder_id}", get(handlers::workspace_folders::get_folder))
.route("/api/v1/workspaces/{workspace_id}/folders/{folder_id}", delete(handlers::workspace_folders::delete_folder))
.route("/api/v1/workspaces/{workspace_id}/requests", post(handlers::workspace_folders::create_request))
.route("/api/v1/workspaces/{workspace_id}/requests/{request_id}", delete(handlers::workspace_folders::delete_request))
.route("/api/v1/import/preview", post(handlers::workspace_import::preview_import))
.route("/api/v1/workspaces/{workspace_id}/import", post(handlers::workspace_import::import_to_workspace))
.route("/api/v1/workspaces/{workspace_id}/autocomplete", post(handlers::workspace_import::autocomplete))
.route("/api/v1/workspaces/{workspace_id}/requests/{request_id}/execute", post(handlers::workspace_request_execute::execute_request))
.route("/api/v1/workspaces/{workspace_id}/requests/{request_id}/history", get(handlers::workspace_request_execute::list_request_history))
.route("/api/v1/workspaces/{workspace_id}/encryption/status", get(handlers::workspace_encryption::get_status))
.route("/api/v1/workspaces/{workspace_id}/encryption/config", get(handlers::workspace_encryption::get_config))
.route("/api/v1/workspaces/{workspace_id}/encryption/config", put(handlers::workspace_encryption::put_config))
.route("/api/v1/workspaces/{workspace_id}/encryption/enable", post(handlers::workspace_encryption::enable))
.route("/api/v1/workspaces/{workspace_id}/encryption/disable", post(handlers::workspace_encryption::disable))
.route("/api/v1/workspaces/{workspace_id}/encryption/security-check", post(handlers::workspace_encryption::security_check))
.route("/api/v1/services", get(handlers::cloud_services::list_services))
.route("/api/v1/services", post(handlers::cloud_services::create_service))
.route("/api/v1/services/{id}", get(handlers::cloud_services::get_service))
.route("/api/v1/services/{id}", patch(handlers::cloud_services::update_service))
.route("/api/v1/services/{id}", delete(handlers::cloud_services::delete_service))
.route("/api/v1/fixtures", get(handlers::cloud_fixtures::list_fixtures))
.route("/api/v1/fixtures", post(handlers::cloud_fixtures::create_fixture))
.route(
"/api/v1/fixtures/bulk",
delete(handlers::cloud_fixtures::delete_fixtures_bulk),
)
.route("/api/v1/fixtures/{id}", get(handlers::cloud_fixtures::get_fixture))
.route("/api/v1/fixtures/{id}", patch(handlers::cloud_fixtures::update_fixture))
.route("/api/v1/fixtures/{id}", delete(handlers::cloud_fixtures::delete_fixture))
.route("/api/v1/federation", get(handlers::federations::list_federations))
.route("/api/v1/federation", post(handlers::federations::create_federation))
.route("/api/v1/federation/{id}", get(handlers::federations::get_federation))
.route("/api/v1/federation/{id}", patch(handlers::federations::update_federation))
.route("/api/v1/federation/{id}", delete(handlers::federations::delete_federation))
.route(
"/api/v1/federation/{id}/route",
post(handlers::federations::route_federation_request),
)
.route(
"/api/v1/federation/{id}/scenarios/activate",
post(handlers::federations::activate_federation_scenario),
)
.route(
"/api/v1/federation/{id}/scenarios/active",
get(handlers::federations::get_active_federation_scenario),
)
.route(
"/api/v1/federation/{id}/scenarios/active",
delete(handlers::federations::deactivate_federation_scenario),
)
.route(
"/api/v1/federation/{id}/scenarios/active/report",
post(handlers::federations::report_federation_scenario_state),
)
.route("/api/v1/scenarios", get(handlers::scenarios::list_org_scenarios))
.route("/api/v1/scenarios/{id}", get(handlers::scenarios::get_org_scenario_by_id))
.route("/api/v1/marketplace/scenarios/publish", post(handlers::scenarios::publish_scenario))
.route("/api/v1/marketplace/scenarios/{name}/reviews", post(handlers::scenario_reviews::submit_scenario_review))
.route("/api/v1/marketplace/scenarios/{name}/reviews/{review_id}/vote", post(handlers::scenario_reviews::vote_scenario_review))
.route("/api/v1/marketplace/scenarios/{name}/star", post(handlers::scenarios::toggle_scenario_star))
.route("/api/v1/marketplace/scenarios/{name}/star", get(handlers::scenarios::get_scenario_star_state))
.route("/api/v1/marketplace/scenarios/{name}/versions/{version}/yank", delete(handlers::scenarios::yank_scenario_version))
.route("/api/v1/marketplace/templates/publish", post(handlers::templates::publish_template))
.route("/api/v1/marketplace/templates/{name}/{version}/reviews", post(handlers::template_reviews::submit_template_review))
.route("/api/v1/marketplace/templates/{name}/{version}/star", post(handlers::templates::toggle_template_star))
.route("/api/v1/marketplace/templates/{name}/{version}/star", get(handlers::templates::get_template_star_state))
.route("/api/v1/organizations/{org_id}/templates", get(handlers::org_templates::list_templates))
.route("/api/v1/organizations/{org_id}/templates", post(handlers::org_templates::create_template))
.route("/api/v1/organizations/{org_id}/templates/{template_id}", get(handlers::org_templates::get_template))
.route("/api/v1/organizations/{org_id}/templates/{template_id}", patch(handlers::org_templates::update_template))
.route("/api/v1/organizations/{org_id}/templates/{template_id}", delete(handlers::org_templates::delete_template))
.route("/api/v1/workspaces/{workspace_id}/environments/{env}/promote-scenario", post(handlers::scenario_promotions::promote_scenario))
.route("/api/v1/workspaces/{workspace_id}/promotions", get(handlers::scenario_promotions::list_promotions))
.route("/api/v1/workspaces/{workspace_id}/promotions/{promotion_id}/approve", post(handlers::scenario_promotions::approve_promotion))
.route("/api/v1/workspaces/{workspace_id}/promotions/{promotion_id}/reject", post(handlers::scenario_promotions::reject_promotion))
.route_layer(middleware::from_fn_with_state(state.clone(), auth_middleware))
.route_layer(middleware::from_fn(rate_limit_middleware));
let sso_public_routes = Router::new()
.route("/api/v1/sso/saml/login/{org_slug}", get(handlers::sso::initiate_saml_login))
.route("/api/v1/sso/saml/acs/{org_slug}", post(handlers::sso::saml_acs))
.route("/api/v1/sso/saml/slo/{org_slug}", post(handlers::sso::saml_slo))
.route_layer(middleware::from_fn(rate_limit_middleware));
let oauth_public_routes = Router::new()
.route("/api/v1/auth/oauth/{provider}", get(handlers::oauth::oauth_authorize))
.route("/api/v1/auth/oauth/{provider}/callback", get(handlers::oauth::oauth_callback))
.route_layer(middleware::from_fn(rate_limit_middleware));
let billing_webhook_routes =
Router::new().route("/api/v1/billing/webhook", post(handlers::billing::stripe_webhook));
let admin_routes = Router::new()
.route("/api/v1/admin/plugins/{name}/verify", post(handlers::admin::verify_plugin))
.route("/api/v1/admin/plugins/{name}/takedown", post(handlers::admin::takedown_plugin))
.route("/api/v1/admin/plugins/{name}/restore", post(handlers::admin::restore_plugin))
.route(
"/api/v1/admin/plugins/taken-down",
get(handlers::admin::list_taken_down_plugins),
)
.route("/api/v1/admin/stats", get(handlers::admin::get_admin_stats))
.route("/api/v1/admin/analytics", get(handlers::analytics::get_analytics))
.route(
"/api/v1/admin/analytics/funnel",
get(handlers::analytics::get_conversion_funnel),
)
.route_layer(middleware::from_fn_with_state(state.clone(), auth_middleware))
.route_layer(middleware::from_fn(rate_limit_middleware));
Router::new()
.merge(public_routes)
.merge(sso_public_routes)
.merge(oauth_public_routes)
.merge(billing_webhook_routes)
.merge(auth_routes)
.merge(admin_routes)
}
#[cfg(test)]
mod tests {
#[test]
fn test_public_route_paths() {
let routes = vec![
"/health",
"/health/live",
"/health/ready",
"/api/v1/plugins/search",
"/api/v1/plugins/{name}",
"/api/v1/plugins/{name}/versions/{version}",
"/api/v1/plugins/{name}/versions/{version}/download",
"/api/v1/stats",
"/api/v1/auth/register",
"/api/v1/auth/login",
"/api/v1/auth/token/refresh",
"/api/v1/auth/password/reset-request",
"/api/v1/auth/password/reset",
];
for route in routes {
assert!(route.starts_with("/"));
assert!(!route.contains("//"));
}
}
#[test]
fn test_auth_route_paths() {
let routes = vec![
"/api/v1/plugins/publish",
"/api/v1/plugins/{name}/versions/{version}/yank",
"/api/v1/auth/2fa/setup",
"/api/v1/auth/2fa/verify-setup",
"/api/v1/auth/2fa/disable",
"/api/v1/auth/2fa/status",
"/api/v1/organizations",
"/api/v1/organizations/{org_id}",
"/api/v1/organizations/{org_id}/members",
];
for route in routes {
assert!(route.starts_with("/api/v1/"));
assert!(!route.contains("//"));
}
}
#[test]
fn test_sso_route_paths() {
let routes = vec![
"/api/v1/sso/config",
"/api/v1/sso/enable",
"/api/v1/sso/disable",
"/api/v1/sso/saml/metadata/{org_slug}",
"/api/v1/sso/saml/login/{org_slug}",
"/api/v1/sso/saml/acs/{org_slug}",
"/api/v1/sso/saml/slo/{org_slug}",
];
for route in routes {
assert!(route.starts_with("/api/v1/sso"));
assert!(!route.contains("//"));
}
}
#[test]
fn test_admin_route_paths() {
let routes = vec![
"/api/v1/admin/plugins/{name}/verify",
"/api/v1/admin/plugins/{name}/takedown",
"/api/v1/admin/plugins/{name}/restore",
"/api/v1/admin/plugins/taken-down",
"/api/v1/admin/stats",
"/api/v1/admin/analytics",
"/api/v1/admin/analytics/funnel",
];
for route in routes {
assert!(route.starts_with("/api/v1/admin"));
assert!(!route.contains("//"));
}
}
#[test]
fn test_organization_settings_routes() {
let routes = vec![
"/api/v1/organizations/{org_id}/settings",
"/api/v1/organizations/{org_id}/settings/ai",
"/api/v1/organizations/{org_id}/usage",
"/api/v1/organizations/{org_id}/billing",
];
for route in routes {
assert!(route.contains("{org_id}"));
assert!(route.starts_with("/api/v1/organizations"));
}
}
#[test]
fn test_pillar_analytics_routes() {
let routes = vec![
"/api/v1/organizations/{org_id}/analytics/pillars",
"/api/v1/workspaces/{workspace_id}/analytics/pillars",
"/api/v1/analytics/pillars/events",
];
for route in routes {
assert!(route.contains("analytics"));
assert!(route.contains("pillars"));
}
}
#[test]
fn test_route_parameter_consistency() {
let org_routes = vec![
"/api/v1/organizations/{org_id}",
"/api/v1/organizations/{org_id}/members",
"/api/v1/organizations/{org_id}/settings",
];
for route in org_routes {
assert!(route.contains("{org_id}"));
}
let plugin_routes = vec![
"/api/v1/plugins/{name}",
"/api/v1/plugins/{name}/versions/{version}",
"/api/v1/plugins/{name}/reviews",
];
for route in plugin_routes {
assert!(route.contains("{name}"));
}
}
#[test]
fn test_review_routes() {
let routes = vec![
"/api/v1/plugins/{name}/reviews",
"/api/v1/plugins/{name}/reviews/{review_id}/vote",
];
for route in routes {
assert!(route.contains("reviews"));
assert!(route.contains("{name}"));
}
}
#[test]
fn test_two_factor_routes() {
let routes = vec![
"/api/v1/auth/2fa/setup",
"/api/v1/auth/2fa/verify-setup",
"/api/v1/auth/2fa/disable",
"/api/v1/auth/2fa/status",
];
for route in routes {
assert!(route.contains("/2fa/"));
assert!(route.starts_with("/api/v1/auth"));
}
}
#[test]
fn test_no_duplicate_route_patterns() {
let all_routes = vec![
"/api/v1/plugins/{name}",
"/api/v1/plugins/search",
"/api/v1/organizations/{org_id}",
"/api/v1/organizations/{org_id}/members",
];
let mut seen = std::collections::HashSet::new();
for route in all_routes {
assert!(!seen.contains(route), "Duplicate route: {}", route);
seen.insert(route);
}
}
#[test]
fn test_api_version_consistency() {
let routes = vec![
"/api/v1/plugins/search",
"/api/v1/auth/login",
"/api/v1/organizations",
"/api/v1/admin/stats",
"/api/v1/sso/config",
];
for route in routes {
assert!(route.contains("/api/v1/"));
}
}
#[test]
fn test_route_http_methods_appropriateness() {
struct RouteMethod {
path: &'static str,
should_contain: &'static str,
}
let route_checks = vec![
RouteMethod {
path: "health",
should_contain: "get",
},
RouteMethod {
path: "search",
should_contain: "post",
},
RouteMethod {
path: "publish",
should_contain: "post",
},
RouteMethod {
path: "login",
should_contain: "post",
},
RouteMethod {
path: "register",
should_contain: "post",
},
];
for check in route_checks {
assert!(!check.path.is_empty());
assert!(!check.should_contain.is_empty());
}
}
#[test]
fn test_organization_member_routes() {
let routes = vec![
"/api/v1/organizations/{org_id}/members",
"/api/v1/organizations/{org_id}/members/{user_id}",
];
for route in routes {
assert!(route.contains("members"));
assert!(route.contains("{org_id}"));
}
}
#[test]
fn test_password_reset_routes() {
let routes = vec![
"/api/v1/auth/password/reset-request",
"/api/v1/auth/password/reset",
];
for route in routes {
assert!(route.contains("password"));
assert!(route.contains("reset"));
}
}
#[test]
fn test_saml_routes_org_slug_parameter() {
let routes = vec![
"/api/v1/sso/saml/metadata/{org_slug}",
"/api/v1/sso/saml/login/{org_slug}",
"/api/v1/sso/saml/acs/{org_slug}",
"/api/v1/sso/saml/slo/{org_slug}",
];
for route in routes {
assert!(route.contains("{org_slug}"));
assert!(route.contains("/saml/"));
}
}
#[test]
fn test_plugin_badges_route() {
let route = "/api/v1/plugins/{name}/badges";
assert!(route.contains("{name}"));
assert!(route.contains("badges"));
}
}