Skip to main content

systemprompt_users/services/user/
mod.rs

1//! User account service.
2//!
3//! [`UserService`] is the primary entry point for the users domain, delegating
4//! to [`UserRepository`] for lookups, listing and search, session management,
5//! account creation (including anonymous and federated identities), field
6//! updates, bulk operations, statistics, and account merging.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11mod provider;
12
13use std::collections::HashMap;
14use systemprompt_database::DbPool;
15use systemprompt_identifiers::{SessionId, UserId};
16
17use crate::error::Result;
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_with_breakdown(&self) -> Result<UserCountBreakdown> {
209        let total = self.repository.count().await?;
210        let by_status_vec = self.repository.count_by_status().await?;
211        let by_role_vec = self.repository.count_by_role().await?;
212
213        let by_status: HashMap<String, i64> = by_status_vec.into_iter().collect();
214        let by_role: HashMap<String, i64> = by_role_vec.into_iter().collect();
215
216        Ok(UserCountBreakdown {
217            total,
218            by_status,
219            by_role,
220        })
221    }
222
223    pub async fn get_stats(&self) -> Result<UserStats> {
224        self.repository.get_stats().await
225    }
226
227    pub async fn list_by_filter(
228        &self,
229        status: Option<&str>,
230        role: Option<&str>,
231        older_than_days: Option<i64>,
232        limit: i64,
233    ) -> Result<Vec<User>> {
234        self.repository
235            .list_by_filter(status, role, older_than_days, limit)
236            .await
237    }
238
239    pub async fn bulk_update_status(&self, user_ids: &[UserId], new_status: &str) -> Result<u64> {
240        self.repository
241            .bulk_update_status(user_ids, new_status)
242            .await
243    }
244
245    pub async fn bulk_delete(&self, user_ids: &[UserId]) -> Result<u64> {
246        self.repository.bulk_delete(user_ids).await
247    }
248
249    pub async fn merge_users(&self, source_id: &UserId, target_id: &UserId) -> Result<MergeResult> {
250        self.repository.merge_users(source_id, target_id).await
251    }
252}