use axum::{Extension, Json, extract::Query, http::StatusCode, response::IntoResponse};
use serde::Deserialize;
use std::sync::Arc;
use tracing::{error, info};
use crate::auth::{AuthContext, Scope, check_scope};
use crate::cleanup::run_cleanup;
use crate::models::ApiError;
use crate::storage::ArtifactStore;
#[derive(Debug, Deserialize)]
pub struct CleanupQuery {
pub older_than_days: Option<u64>,
}
pub async fn admin_cleanup(
Extension(auth_ctx): Extension<AuthContext>,
Extension(artifact_store): Extension<Option<Arc<dyn ArtifactStore>>>,
Extension(default_retention): Extension<DefaultRetentionDays>,
Query(query): Query<CleanupQuery>,
) -> Result<impl IntoResponse, (StatusCode, Json<ApiError>)> {
check_scope(
Some(&auth_ctx),
&auth_ctx.api_key.project_id,
None,
Scope::Admin,
)?;
let store = match artifact_store {
Some(s) => s,
None => {
return Err((
StatusCode::BAD_REQUEST,
Json(ApiError::bad_request(
"No artifact store configured; cleanup requires S3/GCS/Azure object storage",
)),
));
}
};
let days = query.older_than_days.or(Some(default_retention.0));
let days = match days {
Some(d) if d > 0 => d,
_ => {
return Err((
StatusCode::BAD_REQUEST,
Json(ApiError::bad_request(
"older_than_days must be > 0, or --retention-days must be configured",
)),
));
}
};
info!(
older_than_days = days,
triggered_by = %auth_ctx.api_key.id,
"Admin cleanup triggered"
);
match run_cleanup(store.as_ref(), days).await {
Ok(result) => {
info!(
deleted = result.deleted,
scanned = result.scanned,
"Admin cleanup completed"
);
Ok((StatusCode::OK, Json(result)))
}
Err(e) => {
error!(error = %e, "Admin cleanup failed");
Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(ApiError::internal_error(&e)),
))
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DefaultRetentionDays(pub u64);