use actix_web::{HttpRequest, HttpResponse, Responder, get, web, web::Data};
use serde_json::json;
use std::collections::BTreeMap;
use std::env;
pub mod admin;
pub mod auth;
pub mod backup;
pub mod billing;
pub mod cache;
pub mod chat;
pub mod client_context;
pub mod daemons;
pub mod debug;
pub mod error;
pub mod gateway;
pub mod headers;
pub mod health;
mod health_capabilities;
mod health_cluster_cache;
mod health_cluster_payload;
mod health_cluster_service;
mod health_contracts;
mod health_endpoint_payload;
mod health_mirror_probe;
mod health_routes_catalog;
mod health_status;
pub mod host_routing;
pub mod management;
pub mod metrics;
pub mod pg_meta;
pub mod pipelines;
pub mod provision;
pub mod public_routes;
pub mod query;
pub mod rate_limit;
pub mod registry;
pub mod response;
pub mod s3;
pub mod schema;
pub mod service_router;
pub mod storage;
pub mod supabase;
pub mod typesense;
pub use error::{ApiError, ApiResult};
use crate::AppState;
use crate::api::gateway::auth::{read_right_for_resource, require_admin_or_gateway};
use crate::data::athena_router::list_athena_router_entries;
use client_context::logging_pool;
use response::{api_ok, api_success, internal_error};
const OPENAPI_YAML: &str = include_str!("../../openapi.yaml");
const OPENAPI_WSS_YAML: &str = include_str!("../../openapi-wss.yaml");
pub const ENABLE_ADMIN_PROVISION_ROUTES_ENV: &str = "ATHENA_ENABLE_ADMIN_PROVISION_ROUTES";
pub const ENABLE_ADMIN_BACKUP_ROUTES_ENV: &str = "ATHENA_ENABLE_ADMIN_BACKUP_ROUTES";
pub const ALLOW_DIRECT_PG_URI_WITHOUT_API_KEY_ENV: &str =
"ATHENA_ALLOW_DIRECT_PG_URI_WITHOUT_API_KEY";
const LEGACY_ALLOW_DIRECT_PG_URI_WITHOUT_API_KEY_ENV: &str =
"ATHENA_GATEWAY_ALLOW_DIRECT_PG_URI_WITHOUT_API_KEY";
fn env_flag_value(key: &str) -> Option<bool> {
match env::var(key).ok()?.trim() {
"1" | "true" | "TRUE" | "yes" | "YES" | "on" | "ON" => Some(true),
"0" | "false" | "FALSE" | "no" | "NO" | "off" | "OFF" => Some(false),
_ => None,
}
}
pub fn admin_provision_routes_enabled() -> bool {
env_flag_value(ENABLE_ADMIN_PROVISION_ROUTES_ENV).unwrap_or(false)
}
pub fn admin_backup_routes_enabled() -> bool {
env_flag_value(ENABLE_ADMIN_BACKUP_ROUTES_ENV).unwrap_or(true)
}
pub fn direct_pg_uri_auth_bypass_enabled() -> bool {
env_flag_value(ALLOW_DIRECT_PG_URI_WITHOUT_API_KEY_ENV).unwrap_or(false)
|| env_flag_value(LEGACY_ALLOW_DIRECT_PG_URI_WITHOUT_API_KEY_ENV).unwrap_or(false)
}
pub fn configure_optional_operator_services(cfg: &mut web::ServiceConfig) {
if admin_backup_routes_enabled() {
backup::services(cfg);
}
if admin_provision_routes_enabled() {
provision::services(cfg);
}
}
#[get("/router/registry")]
pub async fn athena_router_registry(req: HttpRequest, state: Data<AppState>) -> impl Responder {
if let Err(resp) = require_admin_or_gateway(
&req,
state.get_ref(),
None,
vec![read_right_for_resource(None)],
)
.await
{
return resp;
}
let pool: sqlx::Pool<sqlx::Postgres> = match logging_pool(&state) {
Ok(pool) => pool,
Err(resp) => return resp,
};
match list_athena_router_entries(&pool).await {
Ok(entries) => api_ok(entries),
Err(err) => internal_error("Failed to list router entries", err),
}
}
#[get("/openapi.yaml")]
pub async fn athena_openapi_host(req: HttpRequest, state: Data<AppState>) -> impl Responder {
if let Err(resp) = require_admin_or_gateway(
&req,
state.get_ref(),
None,
vec![read_right_for_resource(None)],
)
.await
{
return resp;
}
HttpResponse::Ok()
.content_type("application/yaml; charset=utf-8")
.body(OPENAPI_YAML)
}
#[get("/openapi-wss.yaml")]
pub async fn athena_wss_openapi_host(req: HttpRequest, state: Data<AppState>) -> impl Responder {
if let Err(resp) = require_admin_or_gateway(
&req,
state.get_ref(),
None,
vec![read_right_for_resource(None)],
)
.await
{
return resp;
}
HttpResponse::Ok()
.content_type("application/yaml; charset=utf-8")
.body(OPENAPI_WSS_YAML)
}
#[get("/docs")]
pub async fn athena_docs() -> impl Responder {
actix_web::HttpResponse::PermanentRedirect()
.insert_header(("Location", "https://xylex.group/docs/athena"))
.finish()
}
#[get("/admin/errors/catalog")]
pub async fn athena_error_catalog(req: HttpRequest, state: Data<AppState>) -> impl Responder {
if let Err(resp) = require_admin_or_gateway(
&req,
state.get_ref(),
None,
vec![read_right_for_resource(None)],
)
.await
{
return resp;
}
let catalog = crate::error::catalog::public_error_catalog();
let mut counts_by_domain = BTreeMap::<String, usize>::new();
for entry in &catalog {
*counts_by_domain
.entry(entry.domain.to_string())
.or_default() += 1;
}
api_success(
"Listed Athena error catalog",
json!({
"docsBaseUrl": "https://docs.athena-cluster.com",
"registryDoc": "docs/athena_error_registry.md",
"totalCount": catalog.len(),
"countsByDomain": counts_by_domain,
"errors": catalog
}),
)
}