koprogo_api/infrastructure/database/repositories/
user_repository_impl.rs

1use 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}