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