1use 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
9pub 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 pub fn mount(mut self, path: impl Into<String>) -> Self {
25 self.mount = Some(path.into());
26 self
27 }
28
29 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}