koprogo_api/infrastructure/database/repositories/
user_role_repository_impl.rs

1use crate::application::ports::UserRoleRepository;
2use crate::domain::entities::{UserRole, UserRoleAssignment};
3use crate::infrastructure::pool::DbPool;
4use async_trait::async_trait;
5use sqlx::Row;
6use uuid::Uuid;
7
8pub struct PostgresUserRoleRepository {
9    pool: DbPool,
10}
11
12impl PostgresUserRoleRepository {
13    pub fn new(pool: DbPool) -> Self {
14        Self { pool }
15    }
16
17    fn map_row(row: sqlx::postgres::PgRow) -> Result<UserRoleAssignment, String> {
18        let role: UserRole = row
19            .try_get::<String, _>("role")
20            .map_err(|e| format!("Failed to read role: {}", e))?
21            .parse()
22            .map_err(|e| format!("Invalid role: {}", e))?;
23
24        Ok(UserRoleAssignment {
25            id: row
26                .try_get("id")
27                .map_err(|e| format!("Failed to read id: {}", e))?,
28            user_id: row
29                .try_get("user_id")
30                .map_err(|e| format!("Failed to read user_id: {}", e))?,
31            role,
32            organization_id: row
33                .try_get("organization_id")
34                .map_err(|e| format!("Failed to read organization_id: {}", e))?,
35            is_primary: row
36                .try_get("is_primary")
37                .map_err(|e| format!("Failed to read is_primary: {}", e))?,
38            created_at: row
39                .try_get("created_at")
40                .map_err(|e| format!("Failed to read created_at: {}", e))?,
41            updated_at: row
42                .try_get("updated_at")
43                .map_err(|e| format!("Failed to read updated_at: {}", e))?,
44        })
45    }
46}
47
48#[async_trait]
49impl UserRoleRepository for PostgresUserRoleRepository {
50    async fn create(&self, assignment: &UserRoleAssignment) -> Result<UserRoleAssignment, String> {
51        let row = sqlx::query(
52            r#"
53            INSERT INTO user_roles (id, user_id, role, organization_id, is_primary, created_at, updated_at)
54            VALUES ($1, $2, $3, $4, $5, $6, $7)
55            RETURNING id, user_id, role, organization_id, is_primary, created_at, updated_at
56            "#,
57        )
58        .bind(assignment.id)
59        .bind(assignment.user_id)
60        .bind(assignment.role.to_string())
61        .bind(assignment.organization_id)
62        .bind(assignment.is_primary)
63        .bind(assignment.created_at)
64        .bind(assignment.updated_at)
65        .fetch_one(&self.pool)
66        .await
67        .map_err(|e| format!("Failed to create user role: {}", e))?;
68
69        Self::map_row(row)
70    }
71
72    async fn list_for_user(&self, user_id: Uuid) -> Result<Vec<UserRoleAssignment>, String> {
73        let rows = sqlx::query(
74            r#"
75            SELECT id, user_id, role, organization_id, is_primary, created_at, updated_at
76            FROM user_roles
77            WHERE user_id = $1
78            ORDER BY is_primary DESC, created_at ASC
79            "#,
80        )
81        .bind(user_id)
82        .fetch_all(&self.pool)
83        .await
84        .map_err(|e| format!("Failed to list user roles: {}", e))?;
85
86        rows.into_iter()
87            .map(Self::map_row)
88            .collect::<Result<Vec<_>, _>>()
89    }
90
91    async fn find_by_id(&self, id: Uuid) -> Result<Option<UserRoleAssignment>, String> {
92        let row = sqlx::query(
93            r#"
94            SELECT id, user_id, role, organization_id, is_primary, created_at, updated_at
95            FROM user_roles
96            WHERE id = $1
97            "#,
98        )
99        .bind(id)
100        .fetch_optional(&self.pool)
101        .await
102        .map_err(|e| format!("Failed to find user role: {}", e))?;
103
104        match row {
105            Some(row) => Ok(Some(Self::map_row(row)?)),
106            None => Ok(None),
107        }
108    }
109
110    async fn set_primary_role(
111        &self,
112        user_id: Uuid,
113        role_id: Uuid,
114    ) -> Result<UserRoleAssignment, String> {
115        let mut tx = self
116            .pool
117            .begin()
118            .await
119            .map_err(|e| format!("Failed to begin transaction: {}", e))?;
120
121        sqlx::query(
122            r#"
123            UPDATE user_roles
124            SET is_primary = false, updated_at = NOW()
125            WHERE user_id = $1
126            "#,
127        )
128        .bind(user_id)
129        .execute(&mut *tx)
130        .await
131        .map_err(|e| format!("Failed to clear primary roles: {}", e))?;
132
133        let row = sqlx::query(
134            r#"
135            UPDATE user_roles
136            SET is_primary = true, updated_at = NOW()
137            WHERE id = $1 AND user_id = $2
138            RETURNING id, user_id, role, organization_id, is_primary, created_at, updated_at
139            "#,
140        )
141        .bind(role_id)
142        .bind(user_id)
143        .fetch_one(&mut *tx)
144        .await
145        .map_err(|e| format!("Failed to set primary role: {}", e))?;
146
147        tx.commit()
148            .await
149            .map_err(|e| format!("Failed to commit transaction: {}", e))?;
150
151        Self::map_row(row)
152    }
153}