cortiq-gateway 0.2.11

Universal LLM gateway with intelligent routing and an embedded multilingual admin console
//! Embedded static assets for the admin panel (SPA). The `web/` directory is compiled
//! into the binary via `rust-embed`, so the gateway remains a single self-contained file.
//!
//! Served as the main router's fallback: everything under `/admin/*` not matched by
//! an explicit route is served as a file; an unknown path under `/admin` falls back to
//! `index.html` (the client-side hash router handles navigation).

use axum::{
    http::{header, StatusCode, Uri},
    response::{IntoResponse, Response},
};
use rust_embed::RustEmbed;

#[derive(RustEmbed)]
#[folder = "web/"]
struct WebAssets;

fn serve_file(path: &str) -> Response {
    // The panel is rebuilt often (assets are embedded at compile time and served
    // from memory), so tell the browser to always revalidate — otherwise a stale
    // cached SPA hides freshly shipped fixes until a hard reload.
    const NO_CACHE: &str = "no-store, must-revalidate";
    match WebAssets::get(path) {
        Some(content) => {
            let mime = mime_guess::from_path(path).first_or_octet_stream();
            (
                [
                    (header::CONTENT_TYPE, mime.as_ref()),
                    (header::CACHE_CONTROL, NO_CACHE),
                ],
                content.data.into_owned(),
            )
                .into_response()
        }
        None => {
            // SPA: unknown path under /admin → serve index.html
            match WebAssets::get("index.html") {
                Some(content) => (
                    [
                        (header::CONTENT_TYPE, "text/html; charset=utf-8"),
                        (header::CACHE_CONTROL, NO_CACHE),
                    ],
                    content.data.into_owned(),
                )
                    .into_response(),
                None => (StatusCode::NOT_FOUND, "not found").into_response(),
            }
        }
    }
}

/// Application-wide fallback: serves `/admin/*`; everything else → 404 JSON.
pub async fn fallback(uri: Uri) -> Response {
    let path = uri.path();
    if let Some(rest) = path.strip_prefix("/admin") {
        let rest = rest.trim_start_matches('/');
        let file = if rest.is_empty() { "index.html" } else { rest };
        serve_file(file)
    } else {
        (
            StatusCode::NOT_FOUND,
            axum::Json(serde_json::json!({
                "error": { "message": "not found", "type": "not_found", "code": "not_found" }
            })),
        )
            .into_response()
    }
}