koprogo_api/infrastructure/database/repositories/
user_repository_impl.rs1use crate::application::ports::UserRepository;
2use crate::domain::entities::{User, UserRole};
3use crate::infrastructure::pool::DbPool;
4use async_trait::async_trait;
5use uuid::Uuid;
6
7pub struct PostgresUserRepository {
8 pool: DbPool,
9}
10
11impl PostgresUserRepository {
12 pub fn new(pool: DbPool) -> Self {
13 Self { pool }
14 }
15}
16
17#[async_trait]
18impl UserRepository for PostgresUserRepository {
19 async fn create(&self, user: &User) -> Result<User, String> {
20 sqlx::query!(
21 r#"
22 INSERT INTO users (id, email, password_hash, first_name, last_name, role, organization_id, is_active, created_at, updated_at)
23 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
24 "#,
25 user.id,
26 user.email,
27 user.password_hash,
28 user.first_name,
29 user.last_name,
30 user.role.to_string(),
31 user.organization_id,
32 user.is_active,
33 user.created_at,
34 user.updated_at,
35 )
36 .execute(&self.pool)
37 .await
38 .map_err(|e| format!("Failed to create user: {}", e))?;
39
40 Ok(user.clone())
41 }
42
43 async fn find_by_id(&self, id: Uuid) -> Result<Option<User>, String> {
44 let result = sqlx::query!(
45 r#"
46 SELECT id, email, password_hash, first_name, last_name, role, organization_id, is_active, created_at, updated_at
47 FROM users
48 WHERE id = $1
49 "#,
50 id
51 )
52 .fetch_optional(&self.pool)
53 .await
54 .map_err(|e| format!("Failed to find user: {}", e))?;
55
56 match result {
57 Some(row) => {
58 let role = row
59 .role
60 .parse::<UserRole>()
61 .map_err(|e| format!("Invalid role: {}", e))?;
62
63 Ok(Some(User {
64 id: row.id,
65 email: row.email,
66 password_hash: row.password_hash,
67 first_name: row.first_name,
68 last_name: row.last_name,
69 role,
70 organization_id: row.organization_id,
71 is_active: row.is_active,
72 created_at: row.created_at,
73 updated_at: row.updated_at,
74 }))
75 }
76 None => Ok(None),
77 }
78 }
79
80 async fn find_by_email(&self, email: &str) -> Result<Option<User>, String> {
81 let result = sqlx::query!(
82 r#"
83 SELECT id, email, password_hash, first_name, last_name, role, organization_id, is_active, created_at, updated_at
84 FROM users
85 WHERE email = $1
86 "#,
87 email
88 )
89 .fetch_optional(&self.pool)
90 .await
91 .map_err(|e| format!("Failed to find user by email: {}", e))?;
92
93 match result {
94 Some(row) => {
95 let role = row
96 .role
97 .parse::<UserRole>()
98 .map_err(|e| format!("Invalid role: {}", e))?;
99
100 Ok(Some(User {
101 id: row.id,
102 email: row.email,
103 password_hash: row.password_hash,
104 first_name: row.first_name,
105 last_name: row.last_name,
106 role,
107 organization_id: row.organization_id,
108 is_active: row.is_active,
109 created_at: row.created_at,
110 updated_at: row.updated_at,
111 }))
112 }
113 None => Ok(None),
114 }
115 }
116
117 async fn find_all(&self) -> Result<Vec<User>, String> {
118 let rows = sqlx::query!(
119 r#"
120 SELECT id, email, password_hash, first_name, last_name, role, organization_id, is_active, created_at, updated_at
121 FROM users
122 ORDER BY created_at DESC
123 "#
124 )
125 .fetch_all(&self.pool)
126 .await
127 .map_err(|e| format!("Failed to fetch users: {}", e))?;
128
129 let users = rows
130 .into_iter()
131 .filter_map(|row| {
132 let role = row.role.parse::<UserRole>().ok()?;
133 Some(User {
134 id: row.id,
135 email: row.email,
136 password_hash: row.password_hash,
137 first_name: row.first_name,
138 last_name: row.last_name,
139 role,
140 organization_id: row.organization_id,
141 is_active: row.is_active,
142 created_at: row.created_at,
143 updated_at: row.updated_at,
144 })
145 })
146 .collect();
147
148 Ok(users)
149 }
150
151 async fn find_by_organization(&self, org_id: Uuid) -> Result<Vec<User>, String> {
152 let rows = sqlx::query!(
153 r#"
154 SELECT id, email, password_hash, first_name, last_name, role, organization_id, is_active, created_at, updated_at
155 FROM users
156 WHERE organization_id = $1
157 ORDER BY created_at DESC
158 "#,
159 org_id
160 )
161 .fetch_all(&self.pool)
162 .await
163 .map_err(|e| format!("Failed to fetch users by organization: {}", e))?;
164
165 let users = rows
166 .into_iter()
167 .filter_map(|row| {
168 let role = row.role.parse::<UserRole>().ok()?;
169 Some(User {
170 id: row.id,
171 email: row.email,
172 password_hash: row.password_hash,
173 first_name: row.first_name,
174 last_name: row.last_name,
175 role,
176 organization_id: row.organization_id,
177 is_active: row.is_active,
178 created_at: row.created_at,
179 updated_at: row.updated_at,
180 })
181 })
182 .collect();
183
184 Ok(users)
185 }
186
187 async fn update(&self, user: &User) -> Result<User, String> {
188 sqlx::query!(
189 r#"
190 UPDATE users
191 SET email = $2, first_name = $3, last_name = $4, role = $5,
192 organization_id = $6, is_active = $7, updated_at = $8
193 WHERE id = $1
194 "#,
195 user.id,
196 user.email,
197 user.first_name,
198 user.last_name,
199 user.role.to_string(),
200 user.organization_id,
201 user.is_active,
202 user.updated_at,
203 )
204 .execute(&self.pool)
205 .await
206 .map_err(|e| format!("Failed to update user: {}", e))?;
207
208 Ok(user.clone())
209 }
210
211 async fn delete(&self, id: Uuid) -> Result<bool, String> {
212 let result = sqlx::query!(
213 r#"
214 DELETE FROM users
215 WHERE id = $1
216 "#,
217 id
218 )
219 .execute(&self.pool)
220 .await
221 .map_err(|e| format!("Failed to delete user: {}", e))?;
222
223 Ok(result.rows_affected() > 0)
224 }
225
226 async fn count_by_organization(&self, org_id: Uuid) -> Result<i64, String> {
227 let result = sqlx::query!(
228 r#"
229 SELECT COUNT(*) as count
230 FROM users
231 WHERE organization_id = $1
232 "#,
233 org_id
234 )
235 .fetch_one(&self.pool)
236 .await
237 .map_err(|e| format!("Failed to count users: {}", e))?;
238
239 Ok(result.count.unwrap_or(0))
240 }
241}