use std::sync::Arc; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use serde_json::Value; use time::OffsetDateTime; use crate::{AuditEventId, UserId, UserSessionId, WorkspaceId}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct AuditActor { pub user_id: UserId, pub email: String, pub session_id: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum AuditTargetKind { Workspace, Membership, Invitation, Operation, Agent, PlatformApiKey, Secret, AuthProfile, Other, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct AuditTarget { pub workspace_id: WorkspaceId, pub kind: AuditTargetKind, pub id: String, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AuditEvent { pub id: AuditEventId, #[serde(with = "time::serde::rfc3339")] pub occurred_at: OffsetDateTime, pub actor: AuditActor, pub action: String, pub target: AuditTarget, pub payload: Value, pub source_ip: Option, pub user_agent: Option, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct AuditQuery { pub workspace_id: Option, pub action: Option, pub cursor: Option, pub limit: Option, } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct AuditEventPage { pub items: Vec, pub next_cursor: Option, } #[derive(Debug, thiserror::Error)] pub enum AuditError { #[error("audit sink failed: {0}")] Backend(String), } #[async_trait] pub trait AuditSink: Send + Sync { async fn record(&self, event: AuditEvent) -> Result<(), AuditError>; async fn list(&self, _query: AuditQuery) -> Result { Ok(AuditEventPage::default()) } async fn get(&self, _id: &AuditEventId) -> Result, AuditError> { Ok(None) } } pub type SharedAuditSink = Arc; #[derive(Clone, Copy, Debug, Default)] pub struct NoopAuditSink; #[async_trait] impl AuditSink for NoopAuditSink { async fn record(&self, _event: AuditEvent) -> Result<(), AuditError> { Ok(()) } }