koprogo_api/infrastructure/database/repositories/
role_delegation_repository_impl.rs1use crate::application::error::AppError;
10use crate::application::ports::RoleDelegationRepository;
11use crate::domain::entities::{UserRole, UserRoleAssignment};
12use crate::infrastructure::pool::DbPool;
13use async_trait::async_trait;
14use chrono::{DateTime, Utc};
15use sqlx::Row;
16use std::str::FromStr;
17use uuid::Uuid;
18
19pub struct PostgresRoleDelegationRepository {
20 pool: DbPool,
21}
22
23impl PostgresRoleDelegationRepository {
24 pub fn new(pool: DbPool) -> Self {
25 Self { pool }
26 }
27
28 fn row_to_assignment(row: &sqlx::postgres::PgRow) -> Result<UserRoleAssignment, AppError> {
29 let role_str: String = row
30 .try_get("role")
31 .map_err(|e| AppError::Database(format!("Failed to read role: {}", e)))?;
32 let role = UserRole::from_str(&role_str)
33 .map_err(|e| AppError::Database(format!("Invalid role in DB: {}", e)))?;
34 let valid_until: Option<DateTime<Utc>> = row
35 .try_get("valid_until")
36 .map_err(|e| AppError::Database(format!("Failed to read valid_until: {}", e)))?;
37 let delegated_from_user_id: Option<Uuid> =
38 row.try_get("delegated_from_user_id").map_err(|e| {
39 AppError::Database(format!("Failed to read delegated_from_user_id: {}", e))
40 })?;
41 Ok(UserRoleAssignment {
42 id: row
43 .try_get("id")
44 .map_err(|e| AppError::Database(format!("Failed to read id: {}", e)))?,
45 user_id: row
46 .try_get("user_id")
47 .map_err(|e| AppError::Database(format!("Failed to read user_id: {}", e)))?,
48 role,
49 organization_id: row.try_get("organization_id").map_err(|e| {
50 AppError::Database(format!("Failed to read organization_id: {}", e))
51 })?,
52 is_primary: row
53 .try_get("is_primary")
54 .map_err(|e| AppError::Database(format!("Failed to read is_primary: {}", e)))?,
55 valid_until,
56 delegated_from_user_id,
57 created_at: row
58 .try_get("created_at")
59 .map_err(|e| AppError::Database(format!("Failed to read created_at: {}", e)))?,
60 updated_at: row
61 .try_get("updated_at")
62 .map_err(|e| AppError::Database(format!("Failed to read updated_at: {}", e)))?,
63 })
64 }
65}
66
67#[async_trait]
68impl RoleDelegationRepository for PostgresRoleDelegationRepository {
69 async fn save(&self, a: &UserRoleAssignment) -> Result<(), AppError> {
70 sqlx::query(
71 r#"
72 INSERT INTO user_roles (
73 id, user_id, role, organization_id, is_primary,
74 valid_until, delegated_from_user_id,
75 created_at, updated_at
76 )
77 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
78 "#,
79 )
80 .bind(a.id)
81 .bind(a.user_id)
82 .bind(a.role.to_string())
83 .bind(a.organization_id)
84 .bind(a.is_primary)
85 .bind(a.valid_until)
86 .bind(a.delegated_from_user_id)
87 .bind(a.created_at)
88 .bind(a.updated_at)
89 .execute(&self.pool)
90 .await
91 .map_err(|e| AppError::Database(format!("Failed to save delegation: {}", e)))?;
92 Ok(())
93 }
94
95 async fn find_by_id(&self, id: Uuid) -> Result<Option<UserRoleAssignment>, AppError> {
96 let row = sqlx::query(
97 r#"
98 SELECT id, user_id, role, organization_id, is_primary,
99 valid_until, delegated_from_user_id,
100 created_at, updated_at
101 FROM user_roles
102 WHERE id = $1
103 "#,
104 )
105 .bind(id)
106 .fetch_optional(&self.pool)
107 .await
108 .map_err(|e| AppError::Database(format!("Failed to find delegation: {}", e)))?;
109 match row {
110 None => Ok(None),
111 Some(row) => Ok(Some(Self::row_to_assignment(&row)?)),
112 }
113 }
114
115 async fn find_active_by_user_and_role(
116 &self,
117 user_id: Uuid,
118 role: &UserRole,
119 organization_id: Option<Uuid>,
120 ) -> Result<Vec<UserRoleAssignment>, AppError> {
121 let rows = sqlx::query(
124 r#"
125 SELECT id, user_id, role, organization_id, is_primary,
126 valid_until, delegated_from_user_id,
127 created_at, updated_at
128 FROM user_roles
129 WHERE user_id = $1
130 AND role = $2
131 AND (organization_id IS NOT DISTINCT FROM $3)
132 AND (valid_until IS NULL OR valid_until > NOW())
133 ORDER BY created_at ASC
134 "#,
135 )
136 .bind(user_id)
137 .bind(role.to_string())
138 .bind(organization_id)
139 .fetch_all(&self.pool)
140 .await
141 .map_err(|e| AppError::Database(format!("Failed to load assignments: {}", e)))?;
142
143 rows.iter().map(Self::row_to_assignment).collect()
144 }
145
146 async fn list_delegations_of(
147 &self,
148 user_id: Uuid,
149 ) -> Result<Vec<UserRoleAssignment>, AppError> {
150 let rows = sqlx::query(
151 r#"
152 SELECT id, user_id, role, organization_id, is_primary,
153 valid_until, delegated_from_user_id,
154 created_at, updated_at
155 FROM user_roles
156 WHERE delegated_from_user_id IS NOT NULL
157 AND valid_until IS NOT NULL
158 AND valid_until > NOW()
159 AND (user_id = $1 OR delegated_from_user_id = $1)
160 ORDER BY created_at DESC
161 "#,
162 )
163 .bind(user_id)
164 .fetch_all(&self.pool)
165 .await
166 .map_err(|e| AppError::Database(format!("Failed to list delegations: {}", e)))?;
167
168 rows.iter().map(Self::row_to_assignment).collect()
169 }
170
171 async fn revoke(&self, id: Uuid) -> Result<(), AppError> {
172 sqlx::query(
178 r#"
179 DELETE FROM user_roles
180 WHERE id = $1 AND delegated_from_user_id IS NOT NULL
181 "#,
182 )
183 .bind(id)
184 .execute(&self.pool)
185 .await
186 .map_err(|e| AppError::Database(format!("Failed to revoke delegation: {}", e)))?;
187 Ok(())
188 }
189}