Skip to main content

systemprompt_analytics/
extension.rs

1//! Extension registration — wires analytics schemas (sessions, funnels,
2//! engagement events, fingerprint reputation, anomaly thresholds) and
3//! schema-evolution migrations into the extension framework.
4
5use systemprompt_extension::prelude::*;
6
7const MIGRATION_001_EVENT_TYPE: &str = r"
8ALTER TABLE engagement_events ADD COLUMN IF NOT EXISTS event_type VARCHAR(50) NOT NULL DEFAULT 'page_exit';
9CREATE INDEX IF NOT EXISTS idx_engagement_events_event_type ON engagement_events(event_type);
10";
11
12const MIGRATION_002_EVENT_DATA: &str = r"
13ALTER TABLE engagement_events ADD COLUMN IF NOT EXISTS event_data JSONB;
14";
15
16#[derive(Debug, Clone, Copy, Default)]
17pub struct AnalyticsExtension;
18
19impl Extension for AnalyticsExtension {
20    fn metadata(&self) -> ExtensionMetadata {
21        ExtensionMetadata {
22            id: "analytics",
23            name: "Analytics",
24            version: env!("CARGO_PKG_VERSION"),
25        }
26    }
27
28    fn migration_weight(&self) -> u32 {
29        200
30    }
31
32    fn schemas(&self) -> Vec<SchemaDefinition> {
33        vec![
34            SchemaDefinition::inline(
35                "engagement_events",
36                include_str!("../schema/engagement_events.sql"),
37            )
38            .with_required_columns(vec![
39                "id".into(),
40                "session_id".into(),
41                "created_at".into(),
42            ]),
43            SchemaDefinition::inline(
44                "anomaly_thresholds",
45                include_str!("../schema/anomaly_thresholds.sql"),
46            )
47            .with_required_columns(vec!["metric_name".into()]),
48            SchemaDefinition::inline(
49                "fingerprint_reputation",
50                include_str!("../schema/fingerprint_reputation.sql"),
51            )
52            .with_required_columns(vec!["fingerprint_hash".into()]),
53            SchemaDefinition::inline("funnels", include_str!("../schema/funnels.sql"))
54                .with_required_columns(vec!["id".into(), "name".into()]),
55            SchemaDefinition::inline(
56                "funnel_progress",
57                include_str!("../schema/funnel_progress.sql"),
58            )
59            .with_required_columns(vec![
60                "id".into(),
61                "funnel_id".into(),
62                "session_id".into(),
63            ]),
64        ]
65    }
66
67    fn dependencies(&self) -> Vec<&'static str> {
68        vec!["users"]
69    }
70
71    fn migrations(&self) -> Vec<Migration> {
72        vec![
73            Migration::new(1, "add_engagement_event_type", MIGRATION_001_EVENT_TYPE),
74            Migration::new(2, "add_engagement_event_data", MIGRATION_002_EVENT_DATA),
75        ]
76    }
77}
78
79register_extension!(AnalyticsExtension);