use axum::{ body::Body, extract::State, http::{StatusCode, header}, response::{IntoResponse, Response}, }; use crate::AppState; pub(super) fn text_asset_response(content: &'static str, content_type: &'static str) -> Response { Response::builder() .status(StatusCode::OK) .header(header::CONTENT_TYPE, content_type) .header(header::CACHE_CONTROL, "public, max-age=86400") .body(Body::from(content)) .expect("text asset response") } pub(super) async fn robots_txt() -> Response { text_asset_response( include_str!("../../static/robots.txt"), "text/plain; charset=utf-8", ) } pub(super) async fn llms_txt() -> Response { text_asset_response( include_str!("../../static/llms.txt"), "text/markdown; charset=utf-8", ) } pub(super) async fn ai_txt() -> Response { llms_txt().await } pub(super) async fn i18n_js() -> Response { text_asset_response( include_str!("../../templates/i18n.js"), "application/javascript; charset=utf-8", ) } pub(super) async fn favicon_svg() -> Response { Response::builder() .status(StatusCode::OK) .header(header::CONTENT_TYPE, "image/svg+xml") .header(header::CACHE_CONTROL, "public, max-age=86400") .body(Body::from(include_str!("../../static/favicon.svg"))) .expect("favicon response") } /// RFC 9728 — OAuth 2.0 Protected Resource Metadata. /// /// Advertises that this server accepts Bearer tokens in the `Authorization` /// header. We deliberately omit `authorization_servers` because this service /// issues its own API keys (no external OAuth AS is involved). MCP clients /// that probe this endpoint will see the resource identifier and stop looking /// for a delegated OAuth flow. pub(super) async fn oauth_protected_resource_metadata( State(state): State, ) -> impl IntoResponse { let body = serde_json::json!({ "resource": state.base_url, "bearer_methods_supported": ["header"], "resource_documentation": format!("{}/dashboard", state.base_url), }); ( StatusCode::OK, [(header::CONTENT_TYPE, "application/json")], axum::Json(body), ) }