use http::{Response, StatusCode};
use serde::Deserialize;
use serde_json::json;
use crate::app_state::AppState;
use crate::hateoas::{collection_links, self_link};
use crate::router::{Body, json_response, parse_query, problem_response};
const DEFAULT_PER_PAGE: usize = 20;
const MAX_PER_PAGE: usize = 100;
#[derive(Debug, Deserialize)]
struct AuditLogQuery {
#[serde(default)]
tracking_id: Option<String>,
#[serde(default = "default_page")]
page: String,
#[serde(default = "default_per_page")]
per_page: String,
}
fn default_page() -> String {
"1".to_owned()
}
fn default_per_page() -> String {
DEFAULT_PER_PAGE.to_string()
}
pub async fn list_audit_log(
state: AppState,
parts: http::request::Parts,
_params: Vec<(String, String)>,
) -> Response<Body> {
let query = match parse_query::<AuditLogQuery>(&parts.uri) {
Ok(q) => q,
Err(e) => {
return problem_response(
StatusCode::BAD_REQUEST,
"https://ndaal.eu/csaf/errors/bad-request",
"Bad Request",
&e,
);
},
};
let page = query.page.parse::<usize>().unwrap_or(1).max(1);
let per_page = query
.per_page
.parse::<usize>()
.unwrap_or(DEFAULT_PER_PAGE)
.clamp(1, MAX_PER_PAGE);
let offset = (page - 1) * per_page;
let tracking_filter = query.tracking_id.as_deref();
let result = state.db_pool().with_conn(|conn| {
let total = csaf_models::audit_log::count(conn, tracking_filter)?;
let entries = csaf_models::audit_log::list(conn, tracking_filter, per_page, offset)?;
Ok((total, entries))
});
let (total, entries) = match result {
Ok(data) => data,
Err(e) => {
tracing::error!("Failed to query audit log: {e}");
return problem_response(
StatusCode::INTERNAL_SERVER_ERROR,
"https://ndaal.eu/csaf/errors/database",
"Database Error",
"Failed to query audit log",
);
},
};
let items: Vec<serde_json::Value> = entries
.iter()
.map(|entry| {
let mut val = serde_json::to_value(entry).unwrap_or_default();
val["_links"] = json!({
"document": self_link(&format!("/api/v1/csaf/{}", entry.tracking_id)),
});
val
})
.collect();
let base_url = if let Some(tid) = tracking_filter {
format!("/api/v1/audit-log?tracking_id={tid}")
} else {
"/api/v1/audit-log".to_owned()
};
let body = json!({
"data": items,
"total": total,
"page": page,
"per_page": per_page,
"_links": collection_links(&base_url, page, per_page, total),
});
json_response(StatusCode::OK, &body)
}