use axum::{
extract::{Extension, Path, Query, State},
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use chrono::{DateTime, Utc};
use pensieve_core::catalog::{Catalog, CleanupResult};
use pensieve_core::errors::{CatalogError, Error as PensieveError};
use serde::Deserialize;
use std::sync::Arc;
#[derive(Clone)]
pub struct CleanupState {
pub catalog: Arc<dyn Catalog>,
}
#[derive(Debug, Deserialize)]
pub struct CleanupQuery {
pub before: DateTime<Utc>,
}
pub async fn cleanup_table(
State(state): State<CleanupState>,
principal: Option<Extension<crate::auth::Principal>>,
Path((db, table)): Path<(String, String)>,
Query(q): Query<CleanupQuery>,
) -> Result<Json<CleanupResult>, ApiError> {
if let Some(Extension(p)) = principal {
crate::auth::check_database_scope(&p, &db).map_err(|(status, msg)| ApiError::Forbidden {
status,
message: msg,
})?;
}
let result = state
.catalog
.cleanup_soft_deleted_extents(&db, &table, q.before)
.await?;
Ok(Json(result))
}
#[derive(Debug)]
pub enum ApiError {
Catalog(PensieveError),
Forbidden { status: StatusCode, message: String },
}
impl From<PensieveError> for ApiError {
fn from(e: PensieveError) -> Self {
ApiError::Catalog(e)
}
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
match self {
ApiError::Catalog(PensieveError::Catalog(CatalogError::TableNotFound {
database,
name,
})) => (
StatusCode::NOT_FOUND,
Json(serde_json::json!({
"error": format!("table '{database}'.'{name}' not found")
})),
)
.into_response(),
ApiError::Catalog(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({ "error": e.to_string() })),
)
.into_response(),
ApiError::Forbidden { status, message } => (
status,
Json(serde_json::json!({"error": {"code": "forbidden", "message": message}})),
)
.into_response(),
}
}
}