perfgate_server/handlers/
admin.rs1use axum::{Extension, Json, extract::Query, http::StatusCode, response::IntoResponse};
4use serde::Deserialize;
5use std::sync::Arc;
6use tracing::{error, info};
7
8use crate::auth::{AuthContext, Scope, check_scope};
9use crate::cleanup::run_cleanup;
10use crate::models::ApiError;
11use crate::storage::ArtifactStore;
12
13#[derive(Debug, Deserialize)]
15pub struct CleanupQuery {
16 pub older_than_days: Option<u64>,
18}
19
20pub async fn admin_cleanup(
27 Extension(auth_ctx): Extension<AuthContext>,
28 Extension(artifact_store): Extension<Option<Arc<dyn ArtifactStore>>>,
29 Extension(default_retention): Extension<DefaultRetentionDays>,
30 Query(query): Query<CleanupQuery>,
31) -> Result<impl IntoResponse, (StatusCode, Json<ApiError>)> {
32 check_scope(
35 Some(&auth_ctx),
36 &auth_ctx.api_key.project_id,
37 None,
38 Scope::Admin,
39 )?;
40
41 let store = match artifact_store {
42 Some(s) => s,
43 None => {
44 return Err((
45 StatusCode::BAD_REQUEST,
46 Json(ApiError::bad_request(
47 "No artifact store configured; cleanup requires S3/GCS/Azure object storage",
48 )),
49 ));
50 }
51 };
52
53 let days = query.older_than_days.or(Some(default_retention.0));
54 let days = match days {
55 Some(d) if d > 0 => d,
56 _ => {
57 return Err((
58 StatusCode::BAD_REQUEST,
59 Json(ApiError::bad_request(
60 "older_than_days must be > 0, or --retention-days must be configured",
61 )),
62 ));
63 }
64 };
65
66 info!(
67 older_than_days = days,
68 triggered_by = %auth_ctx.api_key.id,
69 "Admin cleanup triggered"
70 );
71
72 match run_cleanup(store.as_ref(), days).await {
73 Ok(result) => {
74 info!(
75 deleted = result.deleted,
76 scanned = result.scanned,
77 "Admin cleanup completed"
78 );
79 Ok((StatusCode::OK, Json(result)))
80 }
81 Err(e) => {
82 error!(error = %e, "Admin cleanup failed");
83 Err((
84 StatusCode::INTERNAL_SERVER_ERROR,
85 Json(ApiError::internal_error(&e)),
86 ))
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy)]
94pub struct DefaultRetentionDays(pub u64);