systemprompt_analytics/repository/fingerprint/
mutations.rs1use crate::Result;
10use systemprompt_identifiers::UserId;
11
12use super::FingerprintRepository;
13use crate::models::{FingerprintReputation, FlagReason};
14
15impl FingerprintRepository {
16 pub async fn upsert_fingerprint(
17 &self,
18 fingerprint_hash: &str,
19 ip_address: Option<&str>,
20 user_agent: Option<&str>,
21 user_id: Option<&UserId>,
22 ) -> Result<FingerprintReputation> {
23 let user_ids = user_id.map_or_else(Vec::new, |u| vec![u.as_str().to_owned()]);
24
25 let row = sqlx::query_as!(
26 FingerprintReputation,
27 r#"
28 INSERT INTO fingerprint_reputation (
29 fingerprint_hash, last_ip_address, last_user_agent,
30 associated_user_ids, total_session_count
31 )
32 VALUES ($1, $2, $3, $4, 1)
33 ON CONFLICT (fingerprint_hash) DO UPDATE SET
34 last_seen_at = CURRENT_TIMESTAMP,
35 last_ip_address = COALESCE($2, fingerprint_reputation.last_ip_address),
36 last_user_agent = COALESCE($3, fingerprint_reputation.last_user_agent),
37 total_session_count = fingerprint_reputation.total_session_count + 1,
38 associated_user_ids = CASE
39 WHEN array_length($4, 1) > 0 AND NOT ($4[1] = ANY(fingerprint_reputation.associated_user_ids))
40 THEN array_cat(fingerprint_reputation.associated_user_ids, $4)
41 ELSE fingerprint_reputation.associated_user_ids
42 END,
43 updated_at = CURRENT_TIMESTAMP
44 RETURNING
45 fingerprint_hash,
46 first_seen_at,
47 last_seen_at,
48 total_session_count,
49 active_session_count,
50 total_request_count,
51 requests_last_hour,
52 peak_requests_per_minute,
53 sustained_high_velocity_minutes,
54 is_flagged,
55 flag_reason,
56 flagged_at,
57 reputation_score,
58 abuse_incidents,
59 last_abuse_at,
60 last_ip_address,
61 last_user_agent,
62 associated_user_ids as "associated_user_ids: Vec<UserId>",
63 updated_at
64 "#,
65 fingerprint_hash,
66 ip_address,
67 user_agent,
68 &user_ids[..],
69 )
70 .fetch_one(&*self.write_pool)
71 .await?;
72
73 Ok(row)
74 }
75
76 pub async fn flag_fingerprint(
77 &self,
78 fingerprint_hash: &str,
79 reason: FlagReason,
80 new_score: i32,
81 ) -> Result<()> {
82 sqlx::query!(
83 r#"
84 UPDATE fingerprint_reputation
85 SET is_flagged = TRUE,
86 flag_reason = $2,
87 flagged_at = CURRENT_TIMESTAMP,
88 reputation_score = $3,
89 abuse_incidents = abuse_incidents + 1,
90 last_abuse_at = CURRENT_TIMESTAMP,
91 updated_at = CURRENT_TIMESTAMP
92 WHERE fingerprint_hash = $1
93 "#,
94 fingerprint_hash,
95 reason.as_str(),
96 new_score,
97 )
98 .execute(&*self.write_pool)
99 .await?;
100
101 Ok(())
102 }
103
104 pub async fn increment_request_count(&self, fingerprint_hash: &str) -> Result<()> {
105 sqlx::query!(
106 r#"
107 UPDATE fingerprint_reputation
108 SET total_request_count = total_request_count + 1,
109 updated_at = CURRENT_TIMESTAMP
110 WHERE fingerprint_hash = $1
111 "#,
112 fingerprint_hash,
113 )
114 .execute(&*self.write_pool)
115 .await?;
116
117 Ok(())
118 }
119}