oxify_storage/
user_store.rs

1//! User storage implementation
2
3use crate::models::UserRow;
4use crate::{DatabasePool, Result};
5use chrono::Utc;
6use sqlx::Row;
7use uuid::Uuid;
8
9/// User storage operations
10pub struct UserStore {
11    pool: DatabasePool,
12}
13
14impl UserStore {
15    /// Create a new user store
16    pub fn new(pool: DatabasePool) -> Self {
17        Self { pool }
18    }
19
20    /// Create a new user
21    pub async fn create(
22        &self,
23        id: Uuid,
24        username: String,
25        email: String,
26        password_hash: String,
27        full_name: Option<String>,
28    ) -> Result<UserRow> {
29        let id_str = id.to_string();
30        let now = Utc::now().to_rfc3339();
31
32        sqlx::query(
33            r"
34            INSERT INTO users (id, username, email, password_hash, full_name, created_at, updated_at)
35            VALUES (?, ?, ?, ?, ?, ?, ?)
36            ",
37        )
38        .bind(&id_str)
39        .bind(&username)
40        .bind(&email)
41        .bind(&password_hash)
42        .bind(&full_name)
43        .bind(&now)
44        .bind(&now)
45        .execute(self.pool.pool())
46        .await?;
47
48        Ok(UserRow {
49            id: id_str,
50            username,
51            email,
52            password_hash,
53            full_name,
54            created_at: now.clone(),
55            updated_at: now,
56            last_login: None,
57            is_active: true,
58            is_verified: false,
59        })
60    }
61
62    /// Get a user by ID
63    pub async fn get(&self, id: &Uuid) -> Result<Option<UserRow>> {
64        let row = sqlx::query(
65            r"
66            SELECT id, username, email, password_hash, full_name, created_at, updated_at, last_login, is_active, is_verified
67            FROM users
68            WHERE id = ?
69            ",
70        )
71        .bind(id.to_string())
72        .fetch_optional(self.pool.pool())
73        .await?;
74
75        match row {
76            Some(row) => Ok(Some(UserRow {
77                id: row.get("id"),
78                username: row.get("username"),
79                email: row.get("email"),
80                password_hash: row.get("password_hash"),
81                full_name: row.get("full_name"),
82                created_at: row.get("created_at"),
83                updated_at: row.get("updated_at"),
84                last_login: row.get("last_login"),
85                is_active: row.get("is_active"),
86                is_verified: row.get("is_verified"),
87            })),
88            None => Ok(None),
89        }
90    }
91
92    /// Get a user by email
93    pub async fn get_by_email(&self, email: &str) -> Result<Option<UserRow>> {
94        let row = sqlx::query(
95            r"
96            SELECT id, username, email, password_hash, full_name, created_at, updated_at, last_login, is_active, is_verified
97            FROM users
98            WHERE email = ?
99            ",
100        )
101        .bind(email)
102        .fetch_optional(self.pool.pool())
103        .await?;
104
105        match row {
106            Some(row) => Ok(Some(UserRow {
107                id: row.get("id"),
108                username: row.get("username"),
109                email: row.get("email"),
110                password_hash: row.get("password_hash"),
111                full_name: row.get("full_name"),
112                created_at: row.get("created_at"),
113                updated_at: row.get("updated_at"),
114                last_login: row.get("last_login"),
115                is_active: row.get("is_active"),
116                is_verified: row.get("is_verified"),
117            })),
118            None => Ok(None),
119        }
120    }
121
122    /// Get a user by username
123    pub async fn get_by_username(&self, username: &str) -> Result<Option<UserRow>> {
124        let row = sqlx::query(
125            r"
126            SELECT id, username, email, password_hash, full_name, created_at, updated_at, last_login, is_active, is_verified
127            FROM users
128            WHERE username = ?
129            ",
130        )
131        .bind(username)
132        .fetch_optional(self.pool.pool())
133        .await?;
134
135        match row {
136            Some(row) => Ok(Some(UserRow {
137                id: row.get("id"),
138                username: row.get("username"),
139                email: row.get("email"),
140                password_hash: row.get("password_hash"),
141                full_name: row.get("full_name"),
142                created_at: row.get("created_at"),
143                updated_at: row.get("updated_at"),
144                last_login: row.get("last_login"),
145                is_active: row.get("is_active"),
146                is_verified: row.get("is_verified"),
147            })),
148            None => Ok(None),
149        }
150    }
151
152    /// Update user's last login timestamp
153    pub async fn update_last_login(&self, id: &Uuid) -> Result<()> {
154        let now = Utc::now().to_rfc3339();
155        sqlx::query(
156            r"
157            UPDATE users
158            SET last_login = ?
159            WHERE id = ?
160            ",
161        )
162        .bind(&now)
163        .bind(id.to_string())
164        .execute(self.pool.pool())
165        .await?;
166
167        Ok(())
168    }
169
170    /// Update user's full name
171    #[allow(dead_code)]
172    pub async fn update_full_name(&self, id: &Uuid, full_name: Option<String>) -> Result<()> {
173        sqlx::query(
174            r"
175            UPDATE users
176            SET full_name = ?
177            WHERE id = ?
178            ",
179        )
180        .bind(full_name)
181        .bind(id.to_string())
182        .execute(self.pool.pool())
183        .await?;
184
185        Ok(())
186    }
187
188    /// Delete a user
189    #[allow(dead_code)]
190    pub async fn delete(&self, id: &Uuid) -> Result<()> {
191        sqlx::query(
192            r"
193            DELETE FROM users
194            WHERE id = ?
195            ",
196        )
197        .bind(id.to_string())
198        .execute(self.pool.pool())
199        .await?;
200
201        Ok(())
202    }
203
204    /// List all users
205    #[allow(dead_code)]
206    pub async fn list(&self) -> Result<Vec<UserRow>> {
207        let rows = sqlx::query(
208            r"
209            SELECT id, username, email, password_hash, full_name, created_at, updated_at, last_login, is_active, is_verified
210            FROM users
211            ORDER BY created_at DESC
212            ",
213        )
214        .fetch_all(self.pool.pool())
215        .await?;
216
217        let users = rows
218            .into_iter()
219            .map(|row| UserRow {
220                id: row.get("id"),
221                username: row.get("username"),
222                email: row.get("email"),
223                password_hash: row.get("password_hash"),
224                full_name: row.get("full_name"),
225                created_at: row.get("created_at"),
226                updated_at: row.get("updated_at"),
227                last_login: row.get("last_login"),
228                is_active: row.get("is_active"),
229                is_verified: row.get("is_verified"),
230            })
231            .collect();
232
233        Ok(users)
234    }
235
236    /// Get user roles
237    pub async fn get_roles(&self, user_id: &str) -> Result<Vec<String>> {
238        let roles = sqlx::query(
239            r"
240            SELECT role
241            FROM user_roles
242            WHERE user_id = ?
243            ORDER BY granted_at
244            ",
245        )
246        .bind(user_id)
247        .fetch_all(self.pool.pool())
248        .await?;
249
250        Ok(roles.into_iter().map(|row| row.get("role")).collect())
251    }
252
253    /// Add role to user
254    pub async fn add_role(&self, user_id: &Uuid, role: String) -> Result<()> {
255        sqlx::query(
256            r"
257            INSERT OR IGNORE INTO user_roles (user_id, role)
258            VALUES (?, ?)
259            ",
260        )
261        .bind(user_id.to_string())
262        .bind(role)
263        .execute(self.pool.pool())
264        .await?;
265
266        Ok(())
267    }
268
269    /// Remove role from user
270    #[allow(dead_code)]
271    pub async fn remove_role(&self, user_id: &Uuid, role: &str) -> Result<()> {
272        sqlx::query(
273            r"
274            DELETE FROM user_roles
275            WHERE user_id = ? AND role = ?
276            ",
277        )
278        .bind(user_id.to_string())
279        .bind(role)
280        .execute(self.pool.pool())
281        .await?;
282
283        Ok(())
284    }
285
286    /// Get user permissions
287    pub async fn get_permissions(&self, user_id: &str) -> Result<Vec<String>> {
288        let permissions = sqlx::query(
289            r"
290            SELECT permission
291            FROM user_permissions
292            WHERE user_id = ?
293            ORDER BY granted_at
294            ",
295        )
296        .bind(user_id)
297        .fetch_all(self.pool.pool())
298        .await?;
299
300        Ok(permissions
301            .into_iter()
302            .map(|row| row.get("permission"))
303            .collect())
304    }
305
306    /// Add permission to user
307    pub async fn add_permission(&self, user_id: &Uuid, permission: String) -> Result<()> {
308        sqlx::query(
309            r"
310            INSERT OR IGNORE INTO user_permissions (user_id, permission)
311            VALUES (?, ?)
312            ",
313        )
314        .bind(user_id.to_string())
315        .bind(permission)
316        .execute(self.pool.pool())
317        .await?;
318
319        Ok(())
320    }
321
322    /// Remove permission from user
323    #[allow(dead_code)]
324    pub async fn remove_permission(&self, user_id: &Uuid, permission: &str) -> Result<()> {
325        sqlx::query(
326            r"
327            DELETE FROM user_permissions
328            WHERE user_id = ? AND permission = ?
329            ",
330        )
331        .bind(user_id.to_string())
332        .bind(permission)
333        .execute(self.pool.pool())
334        .await?;
335
336        Ok(())
337    }
338
339    /// Check if user exists by email
340    pub async fn exists_by_email(&self, email: &str) -> Result<bool> {
341        let row = sqlx::query(
342            r"
343            SELECT COUNT(*) as count
344            FROM users
345            WHERE email = ?
346            ",
347        )
348        .bind(email)
349        .fetch_one(self.pool.pool())
350        .await?;
351
352        let count: i64 = row.get("count");
353        Ok(count > 0)
354    }
355}