Skip to main content

systemprompt_users/repository/user/
find.rs

1//! User lookup queries.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use systemprompt_identifiers::UserId;
7
8use crate::error::Result;
9use crate::models::{User, UserRole, 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            User,
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
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
29        Ok(row)
30    }
31
32    pub async fn find_by_email(&self, email: &str) -> Result<Option<User>> {
33        let email = crate::models::normalise_email(email);
34        let deleted_status = UserStatus::Deleted.as_str();
35        let row = sqlx::query_as!(
36            User,
37            r#"
38            SELECT id, name, email, full_name, display_name, status, email_verified,
39                   roles, avatar_url, is_bot, is_scanner, created_at, updated_at
40            FROM users
41            WHERE email = $1 AND status != $2
42            "#,
43            email,
44            deleted_status
45        )
46        .fetch_optional(&*self.pool)
47        .await?;
48
49        Ok(row)
50    }
51
52    // Why: names are not unique; when several accounts share one, the oldest
53    // wins so the resolution is stable across calls.
54    pub async fn find_by_name(&self, name: &str) -> Result<Option<User>> {
55        let deleted_status = UserStatus::Deleted.as_str();
56        let row = sqlx::query_as!(
57            User,
58            r#"
59            SELECT id, name, email, full_name, display_name, status, email_verified,
60                   roles, avatar_url, is_bot, is_scanner, created_at, updated_at
61            FROM users
62            WHERE name = $1 AND status != $2
63            ORDER BY created_at ASC
64            LIMIT 1
65            "#,
66            name,
67            deleted_status
68        )
69        .fetch_optional(&*self.pool)
70        .await?;
71
72        Ok(row)
73    }
74
75    pub async fn find_by_role(&self, role: UserRole) -> Result<Vec<User>> {
76        let deleted_status = UserStatus::Deleted.as_str();
77        let rows = sqlx::query_as!(
78            User,
79            r#"
80            SELECT id, name, email, full_name, display_name, status, email_verified,
81                   roles, avatar_url, is_bot, is_scanner, created_at, updated_at
82            FROM users
83            WHERE $1 = ANY(roles) AND status != $2
84            ORDER BY created_at DESC
85            "#,
86            role.as_str(),
87            deleted_status
88        )
89        .fetch_all(&*self.pool)
90        .await?;
91
92        Ok(rows)
93    }
94
95    pub async fn find_first_user(&self) -> Result<Option<User>> {
96        let deleted_status = UserStatus::Deleted.as_str();
97        let row = sqlx::query_as!(
98            User,
99            r#"
100            SELECT id, name, email, full_name, display_name, status, email_verified,
101                   roles, avatar_url, is_bot, is_scanner, created_at, updated_at
102            FROM users
103            WHERE status != $1
104            ORDER BY created_at ASC
105            LIMIT 1
106            "#,
107            deleted_status
108        )
109        .fetch_optional(&*self.pool)
110        .await?;
111
112        Ok(row)
113    }
114
115    pub async fn find_first_admin(&self) -> Result<Option<User>> {
116        let deleted_status = UserStatus::Deleted.as_str();
117        let admin_role = UserRole::Admin.as_str();
118        let row = sqlx::query_as!(
119            User,
120            r#"
121            SELECT id, name, email, full_name, display_name, status, email_verified,
122                   roles, avatar_url, is_bot, is_scanner, created_at, updated_at
123            FROM users
124            WHERE $1 = ANY(roles) AND status != $2
125            ORDER BY created_at ASC
126            LIMIT 1
127            "#,
128            admin_role,
129            deleted_status
130        )
131        .fetch_optional(&*self.pool)
132        .await?;
133
134        Ok(row)
135    }
136
137    pub async fn find_authenticated_user(&self, user_id: &UserId) -> Result<Option<User>> {
138        let active_status = UserStatus::Active.as_str();
139        let row = sqlx::query_as!(
140            User,
141            r#"
142            SELECT id, name, email, full_name, display_name, status, email_verified,
143                   roles, avatar_url, is_bot, is_scanner, created_at, updated_at
144            FROM users
145            WHERE id = $1 AND status = $2
146            "#,
147            user_id.as_str(),
148            active_status
149        )
150        .fetch_optional(&*self.pool)
151        .await?;
152
153        Ok(row)
154    }
155}