use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Json},
routing::get,
Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::state::AppState;
pub fn custom_router() -> Router<Arc<AppState>> {
Router::new()
.route("/health", get(health_check))
.route("/ready", get(readiness_check))
}
pub fn admin_router() -> Router<Arc<AppState>> {
let router = Router::new()
.route("/live", get(health_check))
.route("/health", get(health_check))
.route("/ready", get(readiness_check));
#[cfg(feature = "otel")]
let router = router.route("/metrics", get(crate::otel::metrics_handler));
router
}
async fn health_check() -> Json<HealthResponse> {
Json(HealthResponse {
status: "ok".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
})
}
async fn readiness_check(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let jwks = state.jwt_config.jwks.as_ref().map(|jwks| jwks.status());
let jwks_loaded = jwks.as_ref().map(|status| status.loaded);
let jwks_problem = jwks.filter(|status| !status.loaded).map(|status| {
format!(
"the keys at PGRST_JWT_JWKS_URL have not loaded: {}",
status.last_error.as_deref().unwrap_or("not fetched yet")
)
});
let (database, message) = match sqlx::query("SELECT 1").execute(&state.pool).await {
Ok(_) => (true, jwks_problem),
Err(e) => (false, Some(e.to_string())),
};
let ready = database && jwks_loaded != Some(false);
(
if ready {
StatusCode::OK
} else {
StatusCode::SERVICE_UNAVAILABLE
},
Json(ReadinessResponse {
ready,
database,
jwks: jwks_loaded,
message,
}),
)
}
#[derive(Serialize)]
struct HealthResponse {
status: String,
version: String,
}
#[derive(Serialize)]
struct ReadinessResponse {
ready: bool,
database: bool,
#[serde(skip_serializing_if = "Option::is_none")]
jwks: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
message: Option<String>,
}
#[derive(Deserialize)]
#[allow(dead_code)]
struct WebhookPayload {
event_type: String,
data: serde_json::Value,
}
#[allow(dead_code)]
async fn handle_webhook(
State(state): State<Arc<AppState>>,
Json(payload): Json<WebhookPayload>,
) -> impl IntoResponse {
tracing::info!("Received webhook: {}", payload.event_type);
let _pool = &state.pool;
(
StatusCode::OK,
Json(serde_json::json!({ "received": true })),
)
}
#[derive(Deserialize)]
#[allow(dead_code)]
struct CustomRpcRequest {
action: String,
params: serde_json::Value,
}
#[allow(dead_code)]
async fn custom_rpc(
State(state): State<Arc<AppState>>,
Json(req): Json<CustomRpcRequest>,
) -> impl IntoResponse {
let result = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM users")
.fetch_one(&state.pool)
.await;
match result {
Ok(count) => (
StatusCode::OK,
Json(serde_json::json!({
"action": req.action,
"result": count
})),
),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": e.to_string()
})),
),
}
}