systemprompt_users/repository/user/
updates.rs1use chrono::{Duration, Utc};
7use systemprompt_identifiers::UserId;
8
9use super::operations::UpdateUserParams;
10use crate::error::{Result, UserError};
11use crate::models::{User, UserRole, UserStatus};
12use crate::repository::UserRepository;
13
14impl UserRepository {
15 pub async fn update_email(&self, id: &UserId, email: &str) -> Result<User> {
16 let row = sqlx::query_as!(
17 User,
18 r#"
19 UPDATE users
20 SET email = $1, email_verified = false, updated_at = $2
21 WHERE id = $3
22 RETURNING id, name, email, full_name, display_name, status, email_verified,
23 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
24 "#,
25 email,
26 Utc::now(),
27 id.as_str()
28 )
29 .fetch_optional(&*self.write_pool)
30 .await?
31 .ok_or_else(|| UserError::NotFound(id.clone()))?;
32
33 Ok(row)
34 }
35
36 pub async fn update_full_name(&self, id: &UserId, full_name: &str) -> Result<User> {
37 let row = sqlx::query_as!(
38 User,
39 r#"
40 UPDATE users
41 SET full_name = $1, updated_at = $2
42 WHERE id = $3
43 RETURNING id, name, email, full_name, display_name, status, email_verified,
44 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
45 "#,
46 full_name,
47 Utc::now(),
48 id.as_str()
49 )
50 .fetch_optional(&*self.write_pool)
51 .await?
52 .ok_or_else(|| UserError::NotFound(id.clone()))?;
53
54 Ok(row)
55 }
56
57 pub async fn update_status(&self, id: &UserId, status: UserStatus) -> Result<User> {
58 let row = sqlx::query_as!(
59 User,
60 r#"
61 UPDATE users
62 SET status = $1, updated_at = $2
63 WHERE id = $3
64 RETURNING id, name, email, full_name, display_name, status, email_verified,
65 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
66 "#,
67 status.as_str(),
68 Utc::now(),
69 id.as_str()
70 )
71 .fetch_optional(&*self.write_pool)
72 .await?
73 .ok_or_else(|| UserError::NotFound(id.clone()))?;
74
75 Ok(row)
76 }
77
78 pub async fn update_email_verified(&self, id: &UserId, verified: bool) -> Result<User> {
79 let row = sqlx::query_as!(
80 User,
81 r#"
82 UPDATE users
83 SET email_verified = $1, updated_at = $2
84 WHERE id = $3
85 RETURNING id, name, email, full_name, display_name, status, email_verified,
86 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
87 "#,
88 verified,
89 Utc::now(),
90 id.as_str()
91 )
92 .fetch_optional(&*self.write_pool)
93 .await?
94 .ok_or_else(|| UserError::NotFound(id.clone()))?;
95
96 Ok(row)
97 }
98
99 pub async fn update_display_name(&self, id: &UserId, display_name: &str) -> Result<User> {
100 let row = sqlx::query_as!(
101 User,
102 r#"
103 UPDATE users
104 SET display_name = $1, updated_at = $2
105 WHERE id = $3
106 RETURNING id, name, email, full_name, display_name, status, email_verified,
107 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
108 "#,
109 display_name,
110 Utc::now(),
111 id.as_str()
112 )
113 .fetch_optional(&*self.write_pool)
114 .await?
115 .ok_or_else(|| UserError::NotFound(id.clone()))?;
116
117 Ok(row)
118 }
119
120 pub async fn update_all_fields(
121 &self,
122 id: &UserId,
123 params: UpdateUserParams<'_>,
124 ) -> Result<User> {
125 let row = sqlx::query_as!(
126 User,
127 r#"
128 UPDATE users
129 SET email = $1, full_name = $2, display_name = $3, status = $4, updated_at = $5
130 WHERE id = $6
131 RETURNING id, name, email, full_name, display_name, status, email_verified,
132 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
133 "#,
134 params.email,
135 params.full_name,
136 params.display_name,
137 params.status.as_str(),
138 Utc::now(),
139 id.as_str()
140 )
141 .fetch_optional(&*self.write_pool)
142 .await?
143 .ok_or_else(|| UserError::NotFound(id.clone()))?;
144
145 Ok(row)
146 }
147
148 pub async fn assign_roles(&self, id: &UserId, roles: &[String]) -> Result<User> {
149 let row = sqlx::query_as!(
150 User,
151 r#"
152 UPDATE users
153 SET roles = $1, updated_at = $2
154 WHERE id = $3
155 RETURNING id, name, email, full_name, display_name, status, email_verified,
156 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
157 "#,
158 roles,
159 Utc::now(),
160 id.as_str()
161 )
162 .fetch_optional(&*self.write_pool)
163 .await?
164 .ok_or_else(|| UserError::NotFound(id.clone()))?;
165
166 Ok(row)
167 }
168
169 pub async fn delete(&self, id: &UserId) -> Result<()> {
170 let result = sqlx::query!(r#"DELETE FROM users WHERE id = $1"#, id.as_str())
171 .execute(&*self.write_pool)
172 .await?;
173
174 if result.rows_affected() == 0 {
175 return Err(UserError::NotFound(id.clone()));
176 }
177
178 Ok(())
179 }
180
181 pub async fn cleanup_old_anonymous(&self, days: i32) -> Result<u64> {
182 let cutoff = Utc::now() - Duration::days(i64::from(days));
183 let anonymous_role = UserRole::Anonymous.as_str();
184 let result = sqlx::query!(
185 r#"
186 DELETE FROM users u
187 WHERE $1 = ANY(u.roles)
188 AND u.created_at < $2
189 AND NOT EXISTS (
190 SELECT 1
191 FROM user_sessions s
192 WHERE s.user_id = u.id
193 AND s.ended_at IS NULL
194 )
195 "#,
196 anonymous_role,
197 cutoff
198 )
199 .execute(&*self.write_pool)
200 .await?;
201
202 Ok(result.rows_affected())
203 }
204
205 pub async fn count_old_anonymous(&self, days: i32) -> Result<i64> {
206 let cutoff = Utc::now() - Duration::days(i64::from(days));
207 let anonymous_role = UserRole::Anonymous.as_str();
208 let count = sqlx::query_scalar!(
209 r#"
210 SELECT COUNT(*) as "count!"
211 FROM users u
212 WHERE $1 = ANY(u.roles)
213 AND u.created_at < $2
214 AND NOT EXISTS (
215 SELECT 1
216 FROM user_sessions s
217 WHERE s.user_id = u.id
218 AND s.ended_at IS NULL
219 )
220 "#,
221 anonymous_role,
222 cutoff
223 )
224 .fetch_one(&*self.write_pool)
225 .await?;
226
227 Ok(count)
228 }
229}