systemprompt_users/repository/user/
directory.rs1use crate::error::Result;
8use crate::models::{User, UserRole, UserRow, UserStatus};
9use crate::repository::UserRepository;
10
11const MAX_DIRECTORY_PAGE: i64 = 50;
12
13fn like_prefix(query: &str) -> String {
14 let escaped = query
15 .replace('\\', "\\\\")
16 .replace('%', "\\%")
17 .replace('_', "\\_");
18 format!("{escaped}%")
19}
20
21impl UserRepository {
22 pub async fn search_active_by_prefix(&self, query: &str, limit: i64) -> Result<Vec<User>> {
23 let pattern = like_prefix(query.trim());
24 let active = UserStatus::Active.as_str();
25 let anonymous = UserRole::Anonymous.as_str();
26 sqlx::query_as!(
27 UserRow,
28 r#"SELECT id, name, email, full_name, display_name, status, email_verified,
29 roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
30 FROM users
31 WHERE status = $1 AND NOT ($2 = ANY(roles)) AND NOT is_bot
32 AND (display_name ILIKE $3 OR name ILIKE $3 OR email ILIKE $3
33 OR full_name ILIKE $3)
34 ORDER BY COALESCE(display_name, full_name, name), id
35 LIMIT $4"#,
36 active,
37 anonymous,
38 pattern,
39 limit.clamp(1, MAX_DIRECTORY_PAGE),
40 )
41 .fetch_all(&*self.pool)
42 .await?
43 .into_iter()
44 .map(User::try_from)
45 .collect()
46 }
47
48 pub async fn list_active_roles(&self) -> Result<Vec<String>> {
49 let active = UserStatus::Active.as_str();
50 let anonymous = UserRole::Anonymous.as_str();
51 let roles = sqlx::query_scalar!(
52 r#"SELECT DISTINCT role AS "role!" FROM users, UNNEST(roles) AS role
53 WHERE status = $1 AND role <> $2 ORDER BY 1"#,
54 active,
55 anonymous,
56 )
57 .fetch_all(&*self.pool)
58 .await?;
59 Ok(roles)
60 }
61}