1use crate::entity::scope_membership_audit;
4use crate::types::ScopeRef;
5use chrono::Utc;
6use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect};
7use sova_core::Result;
8use sova_db::{ActiveModelTrait, DbError, DbHandle, Set};
9
10pub const AUDIT_ADDED: &str = "added";
11pub const AUDIT_ROLE_CHANGED: &str = "role_changed";
12pub const AUDIT_REMOVED: &str = "removed";
13
14#[derive(Clone, Debug)]
15pub struct AuditEntry {
16 pub id: i64,
17 pub scope_kind: String,
18 pub scope_id: i64,
19 pub user_id: i64,
20 pub actor_id: Option<i64>,
21 pub action: String,
22 pub old_role: Option<String>,
23 pub new_role: Option<String>,
24 pub created_at: chrono::DateTime<chrono::Utc>,
25}
26
27pub struct AuditStore;
28
29impl AuditStore {
30 pub async fn record(
31 db: &DbHandle,
32 scope: &ScopeRef,
33 user_id: i64,
34 actor_id: Option<i64>,
35 action: &str,
36 old_role: Option<&str>,
37 new_role: Option<&str>,
38 ) -> Result<AuditEntry> {
39 let row = scope_membership_audit::ActiveModel {
40 scope_kind: Set(scope.kind.clone()),
41 scope_id: Set(scope.id),
42 user_id: Set(user_id),
43 actor_id: Set(actor_id),
44 action: Set(action.into()),
45 old_role: Set(old_role.map(str::to_string)),
46 new_role: Set(new_role.map(str::to_string)),
47 created_at: Set(Utc::now()),
48 ..Default::default()
49 }
50 .insert(db)
51 .await
52 .map_err(DbError::from)?;
53 Ok(model_to_entry(row))
54 }
55
56 pub async fn list_for_scope(
57 db: &DbHandle,
58 scope: ScopeRef,
59 limit: u64,
60 ) -> Result<Vec<AuditEntry>> {
61 Ok(scope_membership_audit::Entity::find()
62 .filter(scope_membership_audit::Column::ScopeKind.eq(scope.kind))
63 .filter(scope_membership_audit::Column::ScopeId.eq(scope.id))
64 .order_by_desc(scope_membership_audit::Column::CreatedAt)
65 .limit(limit)
66 .all(db)
67 .await
68 .map_err(DbError::from)?
69 .into_iter()
70 .map(model_to_entry)
71 .collect())
72 }
73
74 pub async fn list_for_user(
75 db: &DbHandle,
76 user_id: i64,
77 limit: u64,
78 ) -> Result<Vec<AuditEntry>> {
79 Ok(scope_membership_audit::Entity::find()
80 .filter(scope_membership_audit::Column::UserId.eq(user_id))
81 .order_by_desc(scope_membership_audit::Column::CreatedAt)
82 .limit(limit)
83 .all(db)
84 .await
85 .map_err(DbError::from)?
86 .into_iter()
87 .map(model_to_entry)
88 .collect())
89 }
90}
91
92fn model_to_entry(m: scope_membership_audit::Model) -> AuditEntry {
93 AuditEntry {
94 id: m.id,
95 scope_kind: m.scope_kind,
96 scope_id: m.scope_id,
97 user_id: m.user_id,
98 actor_id: m.actor_id,
99 action: m.action,
100 old_role: m.old_role,
101 new_role: m.new_role,
102 created_at: m.created_at,
103 }
104}