systemprompt_users/repository/banned_ip/
queries.rs1use crate::error::Result;
7
8use super::BannedIpRepository;
9use super::types::{BanDuration, BanIpParams, BanIpWithMetadataParams, BannedIp};
10
11impl BannedIpRepository {
12 pub async fn is_banned(&self, ip_address: &str) -> Result<bool> {
13 let result = sqlx::query_scalar!(
14 r#"
15 SELECT EXISTS(
16 SELECT 1 FROM banned_ips
17 WHERE ip_address = $1
18 AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)
19 ) as "exists!"
20 "#,
21 ip_address
22 )
23 .fetch_one(&*self.pool)
24 .await?;
25
26 Ok(result)
27 }
28
29 pub async fn find_ban(&self, ip_address: &str) -> Result<Option<BannedIp>> {
30 let row = sqlx::query_as!(
31 BannedIp,
32 r#"
33 SELECT
34 ip_address,
35 reason,
36 banned_at,
37 expires_at,
38 ban_count,
39 last_offense_path,
40 last_user_agent,
41 is_permanent,
42 source_fingerprint,
43 ban_source,
44 associated_session_ids
45 FROM banned_ips
46 WHERE ip_address = $1
47 AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)
48 "#,
49 ip_address
50 )
51 .fetch_optional(&*self.pool)
52 .await?;
53
54 Ok(row)
55 }
56
57 pub async fn ban_ip(&self, params: BanIpParams<'_>) -> Result<()> {
58 let expires_at = params.duration.to_expiry();
59 let is_permanent = matches!(params.duration, BanDuration::Permanent);
60
61 sqlx::query!(
62 r#"
63 INSERT INTO banned_ips (
64 ip_address, reason, expires_at, is_permanent,
65 source_fingerprint, ban_source
66 )
67 VALUES ($1, $2, $3, $4, $5, $6)
68 ON CONFLICT (ip_address) DO UPDATE SET
69 reason = $2,
70 expires_at = CASE
71 WHEN banned_ips.is_permanent THEN banned_ips.expires_at
72 ELSE COALESCE($3, banned_ips.expires_at)
73 END,
74 ban_count = banned_ips.ban_count + 1,
75 is_permanent = banned_ips.is_permanent OR $4,
76 source_fingerprint = COALESCE($5, banned_ips.source_fingerprint),
77 ban_source = $6
78 "#,
79 params.ip_address,
80 params.reason,
81 expires_at,
82 is_permanent,
83 params.source_fingerprint,
84 params.ban_source
85 )
86 .execute(&*self.write_pool)
87 .await?;
88
89 Ok(())
90 }
91
92 pub async fn ban_ip_with_metadata(&self, params: BanIpWithMetadataParams<'_>) -> Result<()> {
93 let expires_at = params.duration.to_expiry();
94 let is_permanent = matches!(params.duration, BanDuration::Permanent);
95 let session_ids: Option<Vec<String>> = params.session_id.map(|s| vec![s.to_owned()]);
96
97 sqlx::query!(
98 r#"
99 INSERT INTO banned_ips (
100 ip_address, reason, expires_at, is_permanent,
101 source_fingerprint, ban_source, last_offense_path,
102 last_user_agent, associated_session_ids
103 )
104 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
105 ON CONFLICT (ip_address) DO UPDATE SET
106 reason = $2,
107 expires_at = CASE
108 WHEN banned_ips.is_permanent THEN banned_ips.expires_at
109 ELSE COALESCE($3, banned_ips.expires_at)
110 END,
111 ban_count = banned_ips.ban_count + 1,
112 is_permanent = banned_ips.is_permanent OR $4,
113 source_fingerprint = COALESCE($5, banned_ips.source_fingerprint),
114 ban_source = $6,
115 last_offense_path = COALESCE($7, banned_ips.last_offense_path),
116 last_user_agent = COALESCE($8, banned_ips.last_user_agent),
117 associated_session_ids = CASE
118 WHEN $9::TEXT[] IS NOT NULL
119 THEN array_cat(COALESCE(banned_ips.associated_session_ids, '{}'::TEXT[]), $9)
120 ELSE banned_ips.associated_session_ids
121 END
122 "#,
123 params.ip_address,
124 params.reason,
125 expires_at,
126 is_permanent,
127 params.source_fingerprint,
128 params.ban_source,
129 params.offense_path,
130 params.user_agent,
131 session_ids.as_deref()
132 )
133 .execute(&*self.write_pool)
134 .await?;
135
136 Ok(())
137 }
138
139 pub async fn unban_ip(&self, ip_address: &str) -> Result<bool> {
140 let result = sqlx::query!(
141 r#"
142 DELETE FROM banned_ips
143 WHERE ip_address = $1
144 "#,
145 ip_address
146 )
147 .execute(&*self.write_pool)
148 .await?;
149
150 Ok(result.rows_affected() > 0)
151 }
152
153 pub async fn cleanup_expired(&self) -> Result<u64> {
154 let result = sqlx::query!(
155 r#"
156 DELETE FROM banned_ips
157 WHERE expires_at IS NOT NULL
158 AND expires_at < CURRENT_TIMESTAMP
159 AND NOT is_permanent
160 "#
161 )
162 .execute(&*self.write_pool)
163 .await?;
164
165 Ok(result.rows_affected())
166 }
167}