Skip to main content

sova_activity/
plugin.rs

1//! Activity plugin: state + optional read mount.
2
3use crate::list::{list_activity, ActivityFilter};
4use crate::record::ActivityLog;
5use sova_core::extend::{MwEntry, IntoMwEntry};
6use sova_core::{App, Json, Plugin, Request, Result, Router};
7use sova_db::DbExt;
8
9/// Activity / audit log plugin.
10pub struct Activity {
11    mount: Option<String>,
12    guard: Option<MwEntry>,
13}
14
15impl Activity {
16    pub fn new() -> Self {
17        Self {
18            mount: None,
19            guard: None,
20        }
21    }
22
23    /// Serve `GET {path}` list (query: `subject_type`, `subject_id`, `event`, `limit`).
24    pub fn mount(mut self, path: impl Into<String>) -> Self {
25        self.mount = Some(path.into());
26        self
27    }
28
29    /// Optional auth middleware for the read mount.
30    pub fn guard(mut self, mw: impl IntoMwEntry) -> Self {
31        self.guard = Some(mw.into_mw_entry());
32        self
33    }
34}
35
36impl Default for Activity {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42impl Plugin for Activity {
43    fn id(&self) -> &'static str {
44        "activity"
45    }
46
47    fn requires(&self) -> &'static [&'static str] {
48        &["db"]
49    }
50
51    fn meta(&self) -> sova_core::PluginMeta {
52        sova_core::PluginMeta::new("Activity")
53            .description("Audit / activity log (who changed what)")
54            .version(env!("CARGO_PKG_VERSION"))
55    }
56
57    fn install(self, app: &mut App) {
58        app.state(ActivityLog);
59
60        if let Some(path) = self.mount {
61            let mut r = Router::new();
62            if let Some(g) = self.guard {
63                r.use_middleware(g);
64            }
65            r.get("/", list_handler);
66            app.mount(&path, r);
67        }
68    }
69}
70
71async fn list_handler(req: Request) -> Result<Json<Vec<crate::ActivityRow>>> {
72    let limit = req
73        .query("limit")
74        .and_then(|s| s.parse().ok())
75        .unwrap_or(50);
76    let filter = ActivityFilter {
77        subject_type: req.query("subject_type").map(str::to_string),
78        subject_id: req.query("subject_id").map(str::to_string),
79        event: req.query("event").map(str::to_string),
80        actor_id: req.query("actor_id").and_then(|s| s.parse().ok()),
81        limit,
82    };
83    let rows = list_activity(req.db(), filter).await?;
84    Ok(Json(rows))
85}