1use sqlx::{PgPool, Row};
2use crate::types::{AppError, Result};
3use serde::{Deserialize, Serialize};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6fn now_ts() -> i64 {
7 SystemTime::now()
8 .duration_since(UNIX_EPOCH)
9 .unwrap()
10 .as_secs() as i64
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct AuditLogEntry {
15 pub id: String,
16 pub action: String,
17 pub resource_type: String,
18 pub resource_id: String,
19 pub details: Option<String>,
20 pub admin_ip: Option<String>,
21 pub created_at: i64,
22}
23
24pub async fn log_admin_action(
25 pool: &PgPool,
26 action: &str,
27 resource_type: &str,
28 resource_id: &str,
29 details: Option<&str>,
30 admin_ip: Option<&str>,
31) -> Result<()> {
32 let id = uuid::Uuid::new_v4().to_string();
33 let now = now_ts();
34
35 sqlx::query(
36 "INSERT INTO admin_audit_log (id, action, resource_type, resource_id, details, admin_ip, created_at)
37 VALUES ($1, $2, $3, $4, $5, $6, $7)"
38 )
39 .bind(&id)
40 .bind(action)
41 .bind(resource_type)
42 .bind(resource_id)
43 .bind(details)
44 .bind(admin_ip)
45 .bind(now)
46 .execute(pool)
47 .await
48 .map_err(|e| AppError::Database(e.to_string()))?;
49
50 Ok(())
51}
52
53pub async fn list_audit_log(pool: &PgPool, limit: i64, offset: i64) -> Result<Vec<AuditLogEntry>> {
54 let rows = sqlx::query(
55 "SELECT id, action, resource_type, resource_id, details, admin_ip, created_at
56 FROM admin_audit_log ORDER BY created_at DESC LIMIT $1 OFFSET $2"
57 )
58 .bind(limit)
59 .bind(offset)
60 .fetch_all(pool)
61 .await
62 .map_err(|e| AppError::Database(e.to_string()))?;
63
64 rows.iter().map(|row| {
65 Ok(AuditLogEntry {
66 id: row.get("id"),
67 action: row.get("action"),
68 resource_type: row.get("resource_type"),
69 resource_id: row.get("resource_id"),
70 details: row.get("details"),
71 admin_ip: row.get("admin_ip"),
72 created_at: row.get("created_at"),
73 })
74 }).collect()
75}