1use async_trait::async_trait;
2use sqlx::Row;
3
4use rs_auth_core::error::AuthError;
5use rs_auth_core::store::UserStore;
6use rs_auth_core::types::User;
7
8use crate::db::AuthDb;
9
10#[async_trait]
11impl UserStore for AuthDb {
12 async fn create_user(
13 &self,
14 email: &str,
15 name: Option<&str>,
16 password_hash: Option<&str>,
17 ) -> Result<User, AuthError> {
18 sqlx::query(
19 r#"
20 INSERT INTO users (email, name, password_hash)
21 VALUES (LOWER($1), $2, $3)
22 RETURNING id, email, name, password_hash, email_verified_at, image, created_at, updated_at
23 "#,
24 )
25 .bind(email)
26 .bind(name)
27 .bind(password_hash)
28 .fetch_one(&self.pool)
29 .await
30 .map(|row| User {
31 id: row.get("id"),
32 email: row.get("email"),
33 name: row.get("name"),
34 password_hash: row.get("password_hash"),
35 email_verified_at: row.get("email_verified_at"),
36 image: row.get("image"),
37 created_at: row.get("created_at"),
38 updated_at: row.get("updated_at"),
39 })
40 .map_err(|error| match error {
41 sqlx::Error::Database(database_error) if database_error.is_unique_violation() => {
42 AuthError::EmailTaken
43 }
44 other => AuthError::Store(other.to_string()),
45 })
46 }
47
48 async fn find_by_email(&self, email: &str) -> Result<Option<User>, AuthError> {
49 sqlx::query(
50 r#"
51 SELECT id, email, name, password_hash, email_verified_at, image, created_at, updated_at
52 FROM users
53 WHERE LOWER(email) = LOWER($1)
54 "#,
55 )
56 .bind(email)
57 .fetch_optional(&self.pool)
58 .await
59 .map(|row| {
60 row.map(|row| User {
61 id: row.get("id"),
62 email: row.get("email"),
63 name: row.get("name"),
64 password_hash: row.get("password_hash"),
65 email_verified_at: row.get("email_verified_at"),
66 image: row.get("image"),
67 created_at: row.get("created_at"),
68 updated_at: row.get("updated_at"),
69 })
70 })
71 .map_err(|error| AuthError::Store(error.to_string()))
72 }
73
74 async fn find_by_id(&self, id: i64) -> Result<Option<User>, AuthError> {
75 sqlx::query(
76 r#"
77 SELECT id, email, name, password_hash, email_verified_at, image, created_at, updated_at
78 FROM users
79 WHERE id = $1
80 "#,
81 )
82 .bind(id)
83 .fetch_optional(&self.pool)
84 .await
85 .map(|row| {
86 row.map(|row| User {
87 id: row.get("id"),
88 email: row.get("email"),
89 name: row.get("name"),
90 password_hash: row.get("password_hash"),
91 email_verified_at: row.get("email_verified_at"),
92 image: row.get("image"),
93 created_at: row.get("created_at"),
94 updated_at: row.get("updated_at"),
95 })
96 })
97 .map_err(|error| AuthError::Store(error.to_string()))
98 }
99
100 async fn set_email_verified(&self, user_id: i64) -> Result<(), AuthError> {
101 sqlx::query(
102 r#"UPDATE users SET email_verified_at = now(), updated_at = now() WHERE id = $1"#,
103 )
104 .bind(user_id)
105 .execute(&self.pool)
106 .await
107 .map(|_| ())
108 .map_err(|error| AuthError::Store(error.to_string()))
109 }
110
111 async fn update_password(&self, user_id: i64, password_hash: &str) -> Result<(), AuthError> {
112 sqlx::query(r#"UPDATE users SET password_hash = $1, updated_at = now() WHERE id = $2"#)
113 .bind(password_hash)
114 .bind(user_id)
115 .execute(&self.pool)
116 .await
117 .map(|_| ())
118 .map_err(|error| AuthError::Store(error.to_string()))
119 }
120
121 async fn delete_user(&self, user_id: i64) -> Result<(), AuthError> {
122 sqlx::query(r#"DELETE FROM users WHERE id = $1"#)
123 .bind(user_id)
124 .execute(&self.pool)
125 .await
126 .map(|_| ())
127 .map_err(|error| AuthError::Store(error.to_string()))
128 }
129}