use axum::{
extract::{Path, Query, State},
http::HeaderMap,
Json,
};
use std::sync::Arc;
use uuid::Uuid;
use crate::callback::AuthCallback;
use crate::errors::AppError;
use crate::models::{AuditLogQueryParams, AuditLogResponse, ListAuditLogsResponse};
use crate::repositories::OrgRole;
use crate::services::EmailService;
use crate::utils::authenticate;
use crate::AppState;
const MAX_LIMIT: u32 = 200;
pub async fn get_org_audit_logs<C: AuthCallback, E: EmailService>(
State(state): State<Arc<AppState<C, E>>>,
headers: HeaderMap,
Path(org_id): Path<Uuid>,
Query(params): Query<AuditLogQueryParams>,
) -> Result<Json<ListAuditLogsResponse>, AppError> {
let auth = authenticate(&state, &headers).await?;
let membership = state
.membership_repo
.find_by_user_and_org(auth.user_id, org_id)
.await?
.ok_or(AppError::Forbidden(
"Not a member of this organization".into(),
))?;
if !membership.role.has_at_least(OrgRole::Admin) {
return Err(AppError::Forbidden(
"Only admins and owners can view audit logs".into(),
));
}
let mut query = params.to_query(Some(org_id));
query.limit = clamp_limit(query.limit);
let (logs, total) = state.audit_repo.query_with_count(query).await?;
let log_responses: Vec<AuditLogResponse> = logs.iter().map(AuditLogResponse::from).collect();
Ok(Json(ListAuditLogsResponse {
logs: log_responses,
total: total as usize,
}))
}
pub async fn get_system_audit_logs<C: AuthCallback, E: EmailService>(
State(state): State<Arc<AppState<C, E>>>,
headers: HeaderMap,
Query(params): Query<AuditLogQueryParams>,
) -> Result<Json<ListAuditLogsResponse>, AppError> {
let auth = authenticate(&state, &headers).await?;
let user = state
.user_repo
.find_by_id(auth.user_id)
.await?
.ok_or(AppError::InvalidToken)?;
if !user.is_system_admin {
return Err(AppError::Forbidden(
"Only system administrators can view system audit logs".into(),
));
}
let mut query = params.to_query(None);
query.limit = clamp_limit(query.limit);
let (logs, total) = state.audit_repo.query_with_count(query).await?;
let log_responses: Vec<AuditLogResponse> = logs.iter().map(AuditLogResponse::from).collect();
Ok(Json(ListAuditLogsResponse {
logs: log_responses,
total: total as usize,
}))
}
fn clamp_limit(limit: Option<u32>) -> Option<u32> {
limit.map(|value| value.min(MAX_LIMIT))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clamp_limit() {
assert_eq!(clamp_limit(Some(10)), Some(10));
assert_eq!(clamp_limit(Some(500)), Some(MAX_LIMIT));
assert_eq!(clamp_limit(None), None);
}
}