athena_rs 3.26.3

Hyper performant polyglot Database driver
//! Public HTTP API routes for Athena RS.
//!
//! This module wires REST endpoints for the router registry and query execution.
//!
//! ## Endpoints
//!
//! - `GET /router/registry` - Retrieve all Athena router registry entries

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;
mod webhook_sink_schema_heal;
pub mod webhook_sinks;

pub use error::{ApiError, ApiResult};

// crate imports
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 {
    // Preserve the long-standing shared-gateway behavior unless operators explicitly disable it.
    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);
    }
}

/// List Athena router registry entries.
///
/// Fetches all router entries from the Athena router registry and returns them as a JSON response.
///
/// # Returns
///
/// Returns an HTTP response containing either:
/// - Success (200): A JSON array of all router entries
/// - Error (500): An error message if the fetch operation fails
#[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),
    }
}

/// Serves the bundled `openapi.yaml` so clients can download the latest spec.
///
#[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)
}

/// Serves the bundled `openapi-wss.yaml` so clients can download the WebSocket spec.
#[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)
}

/// Redirects `/docs` to the public Athena documentation site.
///
/// Issues a 308 permanent redirect so browsers/crawlers can follow the canonical documentation URL.
#[get("/docs")]
pub async fn athena_docs() -> impl Responder {
    actix_web::HttpResponse::PermanentRedirect()
        .insert_header(("Location", "https://xylex.group/docs/athena"))
        .finish()
}

/// Lists the shared Athena error catalog with stable numbers and docs URLs.
#[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
        }),
    )
}

// All mount points for the api will be moved to here with just a simple abstraction function that's called to reference it's logic