koprogo_api/infrastructure/database/repositories/
user_role_repository_impl.rs1use 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}