systemprompt_users/services/user/
mod.rs1mod provider;
12
13use std::collections::HashMap;
14use systemprompt_database::DbPool;
15use systemprompt_identifiers::{SessionId, UserId};
16
17use crate::error::{Result, UserError};
18use crate::models::{
19 User, UserActivity, UserCountBreakdown, UserRole, UserSession, UserStats, UserStatus,
20 UserWithSessions,
21};
22use crate::repository::{MergeResult, UpdateUserParams, UserRepository};
23
24#[derive(Debug, Clone)]
25pub struct UserService {
26 repository: UserRepository,
27}
28
29impl UserService {
30 pub fn new(db: &DbPool) -> Result<Self> {
31 Ok(Self {
32 repository: UserRepository::new(db)?,
33 })
34 }
35
36 pub async fn find_by_id(&self, id: &UserId) -> Result<Option<User>> {
37 self.repository.find_by_id(id).await
38 }
39
40 pub async fn find_by_email(&self, email: &str) -> Result<Option<User>> {
41 self.repository.find_by_email(email).await
42 }
43
44 pub async fn find_by_name(&self, name: &str) -> Result<Option<User>> {
45 self.repository.find_by_name(name).await
46 }
47
48 pub async fn find_by_role(&self, role: UserRole) -> Result<Vec<User>> {
49 self.repository.find_by_role(role).await
50 }
51
52 pub async fn find_first_user(&self) -> Result<Option<User>> {
53 self.repository.find_first_user().await
54 }
55
56 pub async fn find_first_admin(&self) -> Result<Option<User>> {
57 self.repository.find_first_admin().await
58 }
59
60 pub async fn find_authenticated_user(&self, user_id: &UserId) -> Result<Option<User>> {
61 self.repository.find_authenticated_user(user_id).await
62 }
63
64 pub async fn find_with_sessions(&self, user_id: &UserId) -> Result<Option<UserWithSessions>> {
65 self.repository.find_with_sessions(user_id).await
66 }
67
68 pub async fn get_activity(&self, user_id: &UserId) -> Result<UserActivity> {
69 self.repository.get_activity(user_id).await
70 }
71
72 pub async fn list(&self, limit: i64, offset: i64) -> Result<Vec<User>> {
73 self.repository.list(limit, offset).await
74 }
75
76 pub async fn list_all(&self) -> Result<Vec<User>> {
77 self.repository.list_all().await
78 }
79
80 pub async fn search(&self, query: &str, limit: i64) -> Result<Vec<User>> {
81 self.repository.search(query, limit).await
82 }
83
84 pub async fn count(&self) -> Result<i64> {
85 self.repository.count().await
86 }
87
88 pub async fn is_temporary_anonymous(&self, id: &UserId) -> Result<bool> {
89 self.repository.is_temporary_anonymous(id).await
90 }
91
92 pub async fn list_non_anonymous_with_sessions(
93 &self,
94 limit: i64,
95 ) -> Result<Vec<UserWithSessions>> {
96 self.repository
97 .list_non_anonymous_with_sessions(limit)
98 .await
99 }
100
101 pub async fn list_sessions(&self, user_id: &UserId) -> Result<Vec<UserSession>> {
102 self.repository.list_sessions(user_id).await
103 }
104
105 pub async fn list_active_sessions(&self, user_id: &UserId) -> Result<Vec<UserSession>> {
106 self.repository.list_active_sessions(user_id).await
107 }
108
109 pub async fn list_recent_sessions(
110 &self,
111 user_id: &UserId,
112 limit: i64,
113 ) -> Result<Vec<UserSession>> {
114 self.repository.list_recent_sessions(user_id, limit).await
115 }
116
117 pub async fn session_exists(&self, session_id: &SessionId) -> Result<bool> {
118 self.repository.session_exists(session_id).await
119 }
120
121 pub async fn end_session(&self, session_id: &SessionId) -> Result<bool> {
122 self.repository.end_session(session_id).await
123 }
124
125 pub async fn end_all_sessions(&self, user_id: &UserId) -> Result<u64> {
126 self.repository.end_all_sessions(user_id).await
127 }
128
129 pub async fn create(
130 &self,
131 name: &str,
132 email: &str,
133 full_name: Option<&str>,
134 display_name: Option<&str>,
135 ) -> Result<User> {
136 self.repository
137 .create(name, email, full_name, display_name)
138 .await
139 }
140
141 pub async fn create_if_absent(
142 &self,
143 name: &str,
144 email: &str,
145 full_name: Option<&str>,
146 display_name: Option<&str>,
147 ) -> Result<Option<User>> {
148 self.repository
149 .create_if_absent(name, email, full_name, display_name)
150 .await
151 }
152
153 pub async fn create_anonymous(&self, fingerprint: &str) -> Result<User> {
154 self.repository.create_anonymous(fingerprint).await
155 }
156
157 pub async fn find_or_create_federated(
158 &self,
159 issuer: &str,
160 external_sub: &str,
161 claims: &systemprompt_traits::FederatedIdentityClaims,
162 ) -> Result<User> {
163 self.repository
164 .find_or_create_federated(issuer, external_sub, claims)
165 .await
166 }
167
168 pub async fn update_email(&self, id: &UserId, email: &str) -> Result<User> {
169 self.repository.update_email(id, email).await
170 }
171
172 pub async fn update_full_name(&self, id: &UserId, full_name: &str) -> Result<User> {
173 self.repository.update_full_name(id, full_name).await
174 }
175
176 pub async fn update_status(&self, id: &UserId, status: UserStatus) -> Result<User> {
177 self.repository.update_status(id, status).await
178 }
179
180 pub async fn update_email_verified(&self, id: &UserId, verified: bool) -> Result<User> {
181 self.repository.update_email_verified(id, verified).await
182 }
183
184 pub async fn update_display_name(&self, id: &UserId, display_name: &str) -> Result<User> {
185 self.repository.update_display_name(id, display_name).await
186 }
187
188 pub async fn update_all_fields(
189 &self,
190 id: &UserId,
191 params: UpdateUserParams<'_>,
192 ) -> Result<User> {
193 self.repository.update_all_fields(id, params).await
194 }
195
196 pub async fn assign_roles(&self, id: &UserId, roles: &[String]) -> Result<User> {
197 self.repository.assign_roles(id, roles).await
198 }
199
200 pub async fn delete(&self, id: &UserId) -> Result<()> {
201 self.repository.delete(id).await
202 }
203
204 pub async fn cleanup_old_anonymous(&self, days: i32) -> Result<u64> {
205 self.repository.cleanup_old_anonymous(days).await
206 }
207
208 pub async fn count_old_anonymous(&self, days: i32) -> Result<i64> {
209 self.repository.count_old_anonymous(days).await
210 }
211
212 pub async fn count_with_breakdown(&self) -> Result<UserCountBreakdown> {
213 let total = self.repository.count().await?;
214 let by_status_vec = self.repository.count_by_status().await?;
215 let by_role_vec = self.repository.count_by_role().await?;
216
217 let by_status: HashMap<String, i64> = by_status_vec.into_iter().collect();
218 let by_role: HashMap<String, i64> = by_role_vec.into_iter().collect();
219
220 Ok(UserCountBreakdown {
221 total,
222 by_status,
223 by_role,
224 })
225 }
226
227 pub async fn get_stats(&self) -> Result<UserStats> {
228 self.repository.get_stats().await
229 }
230
231 pub async fn list_by_filter(
232 &self,
233 status: Option<&str>,
234 role: Option<&str>,
235 older_than_days: Option<i64>,
236 limit: i64,
237 ) -> Result<Vec<User>> {
238 self.repository
239 .list_by_filter(status, role, older_than_days, limit)
240 .await
241 }
242
243 pub async fn bulk_update_status(&self, user_ids: &[UserId], new_status: &str) -> Result<u64> {
244 self.repository
245 .bulk_update_status(user_ids, new_status)
246 .await
247 }
248
249 pub async fn bulk_delete(&self, user_ids: &[UserId]) -> Result<u64> {
250 self.repository.bulk_delete(user_ids).await
251 }
252
253 pub async fn merge_users(&self, source_id: &UserId, target_id: &UserId) -> Result<MergeResult> {
254 self.repository.merge_users(source_id, target_id).await
255 }
256
257 pub async fn promote_anonymous(
261 &self,
262 source_id: &UserId,
263 target_id: &UserId,
264 ) -> Result<MergeResult> {
265 if source_id == target_id {
266 return Err(UserError::Validation(
267 "cannot promote a user onto itself".to_owned(),
268 ));
269 }
270 let source = self
271 .repository
272 .find_by_id(source_id)
273 .await?
274 .ok_or_else(|| UserError::NotFound(source_id.clone()))?;
275 if !source.has_role(UserRole::Anonymous) {
276 return Err(UserError::Validation(format!(
277 "user {} is not anonymous; use an explicit admin merge instead",
278 source_id
279 )));
280 }
281 self.repository.merge_users(source_id, target_id).await
282 }
283}