Skip to main content

perfgate_server/handlers/
admin.rs

1//! Admin handlers for server management operations.
2
3use 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/// Query parameters for the admin cleanup endpoint.
14#[derive(Debug, Deserialize)]
15pub struct CleanupQuery {
16    /// Remove objects older than this many days.
17    pub older_than_days: Option<u64>,
18}
19
20/// `DELETE /api/v1/admin/cleanup?older_than_days=N`
21///
22/// Triggers an immediate cleanup of expired artifacts. Requires admin scope.
23/// If `older_than_days` is not specified, defaults to the server's
24/// configured retention period. Returns 400 if no retention period is
25/// available.
26pub 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    // Admin-only: use a synthetic "admin" project check.
33    // The `Scope::Admin` check ensures only admin-role keys can call this.
34    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/// Newtype wrapper for the server's default retention days, used as an
92/// Axum extension so the handler can fall back to it.
93#[derive(Debug, Clone, Copy)]
94pub struct DefaultRetentionDays(pub u64);