postrust_server/
custom.rs1use axum::{
9 extract::State,
10 http::StatusCode,
11 response::{IntoResponse, Json},
12 routing::get,
13 Router,
14};
15use serde::{Deserialize, Serialize};
16use std::sync::Arc;
17
18use crate::state::AppState;
19
20pub fn custom_router() -> Router<Arc<AppState>> {
22 Router::new()
23 .route("/health", get(health_check))
24 .route("/ready", get(readiness_check))
25}
26
27pub fn admin_router() -> Router<Arc<AppState>> {
41 let router = Router::new()
42 .route("/live", get(health_check))
43 .route("/health", get(health_check))
44 .route("/ready", get(readiness_check));
45 #[cfg(feature = "otel")]
46 let router = router.route("/metrics", get(crate::otel::metrics_handler));
47 router
48}
49
50async fn health_check() -> Json<HealthResponse> {
55 Json(HealthResponse {
56 status: "ok".to_string(),
57 version: env!("CARGO_PKG_VERSION").to_string(),
58 })
59}
60
61async fn readiness_check(State(state): State<Arc<AppState>>) -> impl IntoResponse {
62 let jwks = state.jwt_config.jwks.as_ref().map(|jwks| jwks.status());
66 let jwks_loaded = jwks.as_ref().map(|status| status.loaded);
67 let jwks_problem = jwks.filter(|status| !status.loaded).map(|status| {
68 format!(
69 "the keys at PGRST_JWT_JWKS_URL have not loaded: {}",
70 status.last_error.as_deref().unwrap_or("not fetched yet")
71 )
72 });
73
74 let (database, message) = match sqlx::query("SELECT 1").execute(&state.pool).await {
76 Ok(_) => (true, jwks_problem),
77 Err(e) => (false, Some(e.to_string())),
78 };
79 let ready = database && jwks_loaded != Some(false);
80 (
81 if ready {
82 StatusCode::OK
83 } else {
84 StatusCode::SERVICE_UNAVAILABLE
85 },
86 Json(ReadinessResponse {
87 ready,
88 database,
89 jwks: jwks_loaded,
90 message,
91 }),
92 )
93}
94
95#[derive(Serialize)]
100struct HealthResponse {
101 status: String,
102 version: String,
103}
104
105#[derive(Serialize)]
106struct ReadinessResponse {
107 ready: bool,
108 database: bool,
109 #[serde(skip_serializing_if = "Option::is_none")]
112 jwks: Option<bool>,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 message: Option<String>,
115}
116
117#[derive(Deserialize)]
122#[allow(dead_code)]
123struct WebhookPayload {
124 event_type: String,
125 data: serde_json::Value,
126}
127
128#[allow(dead_code)]
129async fn handle_webhook(
130 State(state): State<Arc<AppState>>,
131 Json(payload): Json<WebhookPayload>,
132) -> impl IntoResponse {
133 tracing::info!("Received webhook: {}", payload.event_type);
134
135 let _pool = &state.pool;
137
138 (
141 StatusCode::OK,
142 Json(serde_json::json!({ "received": true })),
143 )
144}
145
146#[derive(Deserialize)]
151#[allow(dead_code)]
152struct CustomRpcRequest {
153 action: String,
154 params: serde_json::Value,
155}
156
157#[allow(dead_code)]
158async fn custom_rpc(
159 State(state): State<Arc<AppState>>,
160 Json(req): Json<CustomRpcRequest>,
161) -> impl IntoResponse {
162 let result = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM users")
164 .fetch_one(&state.pool)
165 .await;
166
167 match result {
168 Ok(count) => (
169 StatusCode::OK,
170 Json(serde_json::json!({
171 "action": req.action,
172 "result": count
173 })),
174 ),
175 Err(e) => (
176 StatusCode::INTERNAL_SERVER_ERROR,
177 Json(serde_json::json!({
178 "error": e.to_string()
179 })),
180 ),
181 }
182}