1use crate::entity;
4use chrono::{DateTime, Utc};
5use sova_core::Result;
6use sova_db::{DbError, DbHandle};
7use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect};
8use serde::Serialize;
9
10#[derive(Debug, Clone, Default)]
11pub struct ActivityFilter {
12 pub subject_type: Option<String>,
13 pub subject_id: Option<String>,
14 pub event: Option<String>,
15 pub actor_id: Option<i64>,
16 pub limit: u64,
17}
18
19#[derive(Debug, Clone, Serialize)]
20pub struct ActivityRow {
21 pub id: i64,
22 pub actor_id: Option<i64>,
23 pub subject_type: String,
24 pub subject_id: String,
25 pub event: String,
26 pub properties: serde_json::Value,
27 pub ip: Option<String>,
28 pub user_agent: Option<String>,
29 pub created_at: DateTime<Utc>,
30}
31
32impl From<entity::Model> for ActivityRow {
33 fn from(m: entity::Model) -> Self {
34 let properties = serde_json::from_str(&m.properties).unwrap_or(serde_json::json!({}));
35 Self {
36 id: m.id,
37 actor_id: m.actor_id,
38 subject_type: m.subject_type,
39 subject_id: m.subject_id,
40 event: m.event,
41 properties,
42 ip: m.ip,
43 user_agent: m.user_agent,
44 created_at: m.created_at,
45 }
46 }
47}
48
49pub async fn list_activity(db: &DbHandle, filter: ActivityFilter) -> Result<Vec<ActivityRow>> {
50 let limit = filter.limit.clamp(1, 200);
51 let mut q = entity::Entity::find();
52 if let Some(ref t) = filter.subject_type {
53 q = q.filter(entity::Column::SubjectType.eq(t.clone()));
54 }
55 if let Some(ref id) = filter.subject_id {
56 q = q.filter(entity::Column::SubjectId.eq(id.clone()));
57 }
58 if let Some(ref ev) = filter.event {
59 q = q.filter(entity::Column::Event.eq(ev.clone()));
60 }
61 if let Some(aid) = filter.actor_id {
62 q = q.filter(entity::Column::ActorId.eq(aid));
63 }
64 let rows = q
65 .order_by_desc(entity::Column::CreatedAt)
66 .limit(limit)
67 .all(db)
68 .await
69 .map_err(|e| sova_core::Error::from(DbError::from(e)))?;
70 Ok(rows.into_iter().map(ActivityRow::from).collect())
71}