koprogo_api/infrastructure/database/repositories/
consent_repository_impl.rs

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