use chrono::{DateTime, TimeZone, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct AuditLogEntry {
pub id: String,
pub timestamp: u64,
pub action: String,
pub actor: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource: Option<String>,
pub outcome: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub channel: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct AuditEnvelope {
pub event_id: String,
pub recorded_at: String,
pub action: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_id: Option<String>,
#[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
pub detail: serde_json::Map<String, serde_json::Value>,
}
impl From<&AuditLogEntry> for AuditEnvelope {
fn from(row: &AuditLogEntry) -> Self {
let mut detail = serde_json::Map::new();
if let Some(reason) = &row.detail {
detail.insert("reason".into(), reason.clone().into());
}
if let Some(channel) = &row.channel {
detail.insert("channel".into(), channel.clone().into());
}
Self {
event_id: row.id.clone(),
recorded_at: Utc
.timestamp_opt(row.timestamp as i64, 0)
.single()
.unwrap_or_else(|| DateTime::from_timestamp(0, 0).expect("epoch is valid"))
.to_rfc3339(),
action: row.action.clone(),
outcome: Some(row.outcome.clone()),
actor: Some(row.actor.clone()),
target: row.resource.clone(),
context_id: row.context_id.clone(),
detail,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema, utoipa::IntoParams))]
#[cfg_attr(feature = "openapi", into_params(parameter_in = Query))]
pub struct ListAuditLogsBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub to: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub action: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub page_size: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct ListAuditLogsResultBody {
pub entries: Vec<AuditEnvelope>,
pub truncated: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}