koprogo_api/infrastructure/database/repositories/
consent_repository_impl.rs1use crate::application::ports::consent_repository::ConsentRepository;
2use crate::domain::entities::consent::{ConsentRecord, ConsentStatus};
3use crate::infrastructure::database::pool::DbPool;
4use async_trait::async_trait;
5use sqlx::Row;
6use uuid::Uuid;
7
8pub struct PostgresConsentRepository {
9 pool: DbPool,
10}
11
12impl PostgresConsentRepository {
13 pub fn new(pool: DbPool) -> Self {
14 Self { pool }
15 }
16}
17
18fn row_to_consent_record(row: &sqlx::postgres::PgRow) -> ConsentRecord {
19 ConsentRecord {
20 id: row.get("id"),
21 user_id: row.get("user_id"),
22 organization_id: row.get("organization_id"),
23 consent_type: row.get("consent_type"),
24 accepted_at: row.get("accepted_at"),
25 ip_address: row.get("ip_address"),
26 user_agent: row.get("user_agent"),
27 policy_version: row.get("policy_version"),
28 created_at: row.get("created_at"),
29 updated_at: row.get("updated_at"),
30 }
31}
32
33#[async_trait]
34impl ConsentRepository for PostgresConsentRepository {
35 async fn create(&self, record: &ConsentRecord) -> Result<ConsentRecord, String> {
36 let row = sqlx::query(
37 r#"
38 INSERT INTO consent_records (id, user_id, organization_id, consent_type, accepted_at, ip_address, user_agent, policy_version, created_at, updated_at)
39 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
40 RETURNING id, user_id, organization_id, consent_type, accepted_at, ip_address, user_agent, policy_version, created_at, updated_at
41 "#,
42 )
43 .bind(record.id)
44 .bind(record.user_id)
45 .bind(record.organization_id)
46 .bind(&record.consent_type)
47 .bind(record.accepted_at)
48 .bind(&record.ip_address)
49 .bind(&record.user_agent)
50 .bind(&record.policy_version)
51 .bind(record.created_at)
52 .bind(record.updated_at)
53 .fetch_one(&self.pool)
54 .await
55 .map_err(|e| format!("Failed to create consent record: {}", e))?;
56
57 Ok(row_to_consent_record(&row))
58 }
59
60 async fn find_latest_by_user_and_type(
61 &self,
62 user_id: Uuid,
63 consent_type: &str,
64 ) -> Result<Option<ConsentRecord>, String> {
65 let row = sqlx::query(
66 r#"
67 SELECT id, user_id, organization_id, consent_type, accepted_at, ip_address, user_agent, policy_version, created_at, updated_at
68 FROM consent_records
69 WHERE user_id = $1 AND consent_type = $2
70 ORDER BY accepted_at DESC
71 LIMIT 1
72 "#,
73 )
74 .bind(user_id)
75 .bind(consent_type)
76 .fetch_optional(&self.pool)
77 .await
78 .map_err(|e| format!("Failed to find consent record: {}", e))?;
79
80 Ok(row.as_ref().map(row_to_consent_record))
81 }
82
83 async fn find_all_by_user(&self, user_id: Uuid) -> Result<Vec<ConsentRecord>, String> {
84 let rows = sqlx::query(
85 r#"
86 SELECT id, user_id, organization_id, consent_type, accepted_at, ip_address, user_agent, policy_version, created_at, updated_at
87 FROM consent_records
88 WHERE user_id = $1
89 ORDER BY accepted_at DESC
90 "#,
91 )
92 .bind(user_id)
93 .fetch_all(&self.pool)
94 .await
95 .map_err(|e| format!("Failed to find consent records: {}", e))?;
96
97 Ok(rows.iter().map(row_to_consent_record).collect())
98 }
99
100 async fn has_accepted(&self, user_id: Uuid, consent_type: &str) -> Result<bool, String> {
101 let row: (bool,) = sqlx::query_as(
102 r#"
103 SELECT EXISTS(
104 SELECT 1 FROM consent_records
105 WHERE user_id = $1 AND consent_type = $2
106 )
107 "#,
108 )
109 .bind(user_id)
110 .bind(consent_type)
111 .fetch_one(&self.pool)
112 .await
113 .map_err(|e| format!("Failed to check consent: {}", e))?;
114
115 Ok(row.0)
116 }
117
118 async fn get_consent_status(&self, user_id: Uuid) -> Result<ConsentStatus, String> {
119 let privacy = self
120 .find_latest_by_user_and_type(user_id, "privacy_policy")
121 .await?;
122 let terms = self.find_latest_by_user_and_type(user_id, "terms").await?;
123
124 Ok(ConsentStatus {
125 privacy_policy_accepted: privacy.is_some(),
126 terms_accepted: terms.is_some(),
127 privacy_policy_accepted_at: privacy.as_ref().map(|r| r.accepted_at),
128 terms_accepted_at: terms.as_ref().map(|r| r.accepted_at),
129 privacy_policy_version: privacy.map(|r| r.policy_version),
130 terms_version: terms.map(|r| r.policy_version),
131 })
132 }
133}