use axum::{
extract::{Query, State},
Json,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::db::queries::secret_audit::{self as queries, AuditQuery};
use crate::db::DbPool;
use crate::error::AppResult;
use crate::services::secret_audit::AuditEvent;
#[derive(Clone)]
pub struct SecretAuditDeps {
pub pool: DbPool,
}
#[derive(Debug, Deserialize, Default)]
pub struct AuditQueryParams {
pub credential: Option<String>,
pub execution_id: Option<i64>,
pub from: Option<DateTime<Utc>>,
pub to: Option<DateTime<Utc>>,
pub limit: Option<i64>,
}
#[derive(Debug, Serialize)]
pub struct AuditQueryResponse {
pub rows: Vec<AuditEvent>,
}
pub async fn query(
State(deps): State<SecretAuditDeps>,
Query(params): Query<AuditQueryParams>,
) -> AppResult<Json<AuditQueryResponse>> {
let span = tracing::info_span!(
"secret_audit.query",
credential = params.credential.as_deref(),
execution_id = params.execution_id,
limit = params.limit,
);
let _g = span.enter();
let rows = queries::query(
&deps.pool,
AuditQuery {
credential: params.credential,
execution_id: params.execution_id,
from: params.from,
to: params.to,
limit: params.limit,
},
)
.await?;
Ok(Json(AuditQueryResponse { rows }))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn query_params_round_trip_via_json() {
let v: AuditQueryParams = serde_json::from_str(
r#"{"credential":"duffel_token","execution_id":42,"limit":50}"#,
)
.unwrap();
assert_eq!(v.credential.as_deref(), Some("duffel_token"));
assert_eq!(v.execution_id, Some(42));
assert_eq!(v.limit, Some(50));
}
#[test]
fn empty_object_decodes_to_all_none() {
let v: AuditQueryParams = serde_json::from_str("{}").unwrap();
assert!(v.credential.is_none());
assert!(v.execution_id.is_none());
assert!(v.from.is_none());
assert!(v.to.is_none());
assert!(v.limit.is_none());
}
}