systemprompt_users/repository/user/
find.rs1use systemprompt_identifiers::UserId;
7
8use crate::error::Result;
9use crate::models::{User, UserRole, UserRow, UserStatus};
10use crate::repository::UserRepository;
11
12impl UserRepository {
13 pub async fn find_by_id(&self, id: &UserId) -> Result<Option<User>> {
14 let deleted_status = UserStatus::Deleted.as_str();
15 let row = sqlx::query_as!(
16 UserRow,
17 r#"
18 SELECT id, name, email, full_name, display_name, status, email_verified,
19 roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
20 FROM users
21 WHERE id = $1 AND status != $2
22 "#,
23 id.as_str(),
24 deleted_status
25 )
26 .fetch_optional(&*self.pool)
27 .await?
28 .map(User::try_from)
29 .transpose()?;
30
31 Ok(row)
32 }
33
34 pub async fn find_by_email(&self, email: &str) -> Result<Option<User>> {
35 let email = crate::models::normalise_email(email);
36 let deleted_status = UserStatus::Deleted.as_str();
37 let row = sqlx::query_as!(
38 UserRow,
39 r#"
40 SELECT id, name, email, full_name, display_name, status, email_verified,
41 roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
42 FROM users
43 WHERE email = $1 AND status != $2
44 "#,
45 email,
46 deleted_status
47 )
48 .fetch_optional(&*self.pool)
49 .await?
50 .map(User::try_from)
51 .transpose()?;
52
53 Ok(row)
54 }
55
56 pub async fn find_by_name(&self, name: &str) -> Result<Option<User>> {
57 let deleted_status = UserStatus::Deleted.as_str();
58 let row = sqlx::query_as!(
59 UserRow,
60 r#"
61 SELECT id, name, email, full_name, display_name, status, email_verified,
62 roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
63 FROM users
64 WHERE name = $1 AND status != $2
65 ORDER BY created_at ASC
66 LIMIT 1
67 "#,
68 name,
69 deleted_status
70 )
71 .fetch_optional(&*self.pool)
72 .await?
73 .map(User::try_from)
74 .transpose()?;
75
76 Ok(row)
77 }
78
79 pub async fn find_by_reference_any_status(&self, reference: &str) -> Result<Option<User>> {
80 let row = sqlx::query_as!(
81 UserRow,
82 r#"
83 SELECT id, name, email, full_name, display_name, status, email_verified,
84 roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
85 FROM users
86 WHERE id = $1 OR email = $1 OR name = $1
87 ORDER BY (id = $1) DESC, (email = $1) DESC, created_at ASC
88 LIMIT 1
89 "#,
90 reference
91 )
92 .fetch_optional(&*self.pool)
93 .await?
94 .map(User::try_from)
95 .transpose()?;
96
97 Ok(row)
98 }
99
100 pub async fn list_by_role(&self, role: UserRole) -> Result<Vec<User>> {
101 let deleted_status = UserStatus::Deleted.as_str();
102 let rows = sqlx::query_as!(
103 UserRow,
104 r#"
105 SELECT id, name, email, full_name, display_name, status, email_verified,
106 roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
107 FROM users
108 WHERE $1 = ANY(roles) AND status != $2
109 ORDER BY created_at DESC
110 "#,
111 role.as_str(),
112 deleted_status
113 )
114 .fetch_all(&*self.pool)
115 .await?
116 .into_iter()
117 .map(User::try_from)
118 .collect::<Result<Vec<_>>>()?;
119
120 Ok(rows)
121 }
122
123 pub async fn find_first_user(&self) -> Result<Option<User>> {
124 let deleted_status = UserStatus::Deleted.as_str();
125 let row = sqlx::query_as!(
126 UserRow,
127 r#"
128 SELECT id, name, email, full_name, display_name, status, email_verified,
129 roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
130 FROM users
131 WHERE status != $1
132 ORDER BY created_at ASC
133 LIMIT 1
134 "#,
135 deleted_status
136 )
137 .fetch_optional(&*self.pool)
138 .await?
139 .map(User::try_from)
140 .transpose()?;
141
142 Ok(row)
143 }
144
145 pub async fn find_first_admin(&self) -> Result<Option<User>> {
146 let deleted_status = UserStatus::Deleted.as_str();
147 let admin_role = UserRole::Admin.as_str();
148 let row = sqlx::query_as!(
149 UserRow,
150 r#"
151 SELECT id, name, email, full_name, display_name, status, email_verified,
152 roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
153 FROM users
154 WHERE $1 = ANY(roles) AND status != $2
155 ORDER BY created_at ASC
156 LIMIT 1
157 "#,
158 admin_role,
159 deleted_status
160 )
161 .fetch_optional(&*self.pool)
162 .await?
163 .map(User::try_from)
164 .transpose()?;
165
166 Ok(row)
167 }
168
169 pub async fn find_authenticated_user(&self, user_id: &UserId) -> Result<Option<User>> {
170 let active_status = UserStatus::Active.as_str();
171 let row = sqlx::query_as!(
172 UserRow,
173 r#"
174 SELECT id, name, email, full_name, display_name, status, email_verified,
175 roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
176 FROM users
177 WHERE id = $1 AND status = $2
178 "#,
179 user_id.as_str(),
180 active_status
181 )
182 .fetch_optional(&*self.pool)
183 .await?
184 .map(User::try_from)
185 .transpose()?;
186
187 Ok(row)
188 }
189}
190
191impl UserRepository {
192 pub async fn missing_ids(&self, candidates: &[UserId]) -> Result<Vec<UserId>> {
193 let ids: Vec<String> = candidates.iter().map(ToString::to_string).collect();
194 let rows = sqlx::query_scalar!(
195 r#"
196 SELECT candidate AS "candidate!"
197 FROM UNNEST($1::text[]) AS candidate
198 WHERE NOT EXISTS (SELECT 1 FROM users WHERE users.id = candidate)
199 "#,
200 &ids[..]
201 )
202 .fetch_all(&*self.pool)
203 .await?;
204 Ok(rows.into_iter().map(UserId::new).collect())
205 }
206}