koprogo_api/infrastructure/database/repositories/
syndic_response_repository_impl.rs1use crate::application::error::AppError;
10use crate::application::ports::SyndicResponseRepository;
11use crate::domain::entities::SyndicResponse;
12use crate::infrastructure::database::pool::DbPool;
13use async_trait::async_trait;
14use chrono::{DateTime, Utc};
15use sqlx::Row;
16use uuid::Uuid;
17
18pub struct PostgresSyndicResponseRepository {
19 pool: DbPool,
20}
21
22impl PostgresSyndicResponseRepository {
23 pub fn new(pool: DbPool) -> Self {
24 Self { pool }
25 }
26
27 fn row_to_response(row: &sqlx::postgres::PgRow) -> SyndicResponse {
28 SyndicResponse {
29 id: row.get("id"),
30 ticket_id: row.get("ticket_id"),
31 syndic_user_id: row.get("syndic_user_id"),
32 body: row.get("body"),
33 action_proposed: row.get("action_proposed"),
34 created_at: row.get("created_at"),
35 }
36 }
37}
38
39#[async_trait]
40impl SyndicResponseRepository for PostgresSyndicResponseRepository {
41 async fn save(&self, response: &SyndicResponse) -> Result<(), AppError> {
42 sqlx::query(
43 r#"
44 INSERT INTO syndic_responses (
45 id, ticket_id, syndic_user_id, body, action_proposed, created_at
46 )
47 VALUES ($1, $2, $3, $4, $5, $6)
48 "#,
49 )
50 .bind(response.id)
51 .bind(response.ticket_id)
52 .bind(response.syndic_user_id)
53 .bind(&response.body)
54 .bind(response.action_proposed.as_deref())
55 .bind(response.created_at)
56 .execute(&self.pool)
57 .await
58 .map_err(|e| AppError::Database(e.to_string()))?;
59 Ok(())
60 }
61
62 async fn list_for_ticket(&self, ticket_id: Uuid) -> Result<Vec<SyndicResponse>, AppError> {
63 let rows = sqlx::query(
64 r#"
65 SELECT id, ticket_id, syndic_user_id, body, action_proposed, created_at
66 FROM syndic_responses
67 WHERE ticket_id = $1
68 ORDER BY created_at ASC
69 "#,
70 )
71 .bind(ticket_id)
72 .fetch_all(&self.pool)
73 .await
74 .map_err(|e| AppError::Database(e.to_string()))?;
75
76 Ok(rows.iter().map(Self::row_to_response).collect())
77 }
78
79 async fn find_overdue_tickets(&self, now: DateTime<Utc>) -> Result<Vec<Uuid>, AppError> {
80 let rows = sqlx::query(
81 r#"
82 SELECT id
83 FROM tickets
84 WHERE sla_due_at IS NOT NULL
85 AND sla_due_at <= $1
86 AND sla_escalated_at IS NULL
87 "#,
88 )
89 .bind(now)
90 .fetch_all(&self.pool)
91 .await
92 .map_err(|e| AppError::Database(e.to_string()))?;
93
94 Ok(rows.iter().map(|r| r.get::<Uuid, _>("id")).collect())
95 }
96
97 async fn mark_ticket_escalated(
98 &self,
99 ticket_id: Uuid,
100 escalated_at: DateTime<Utc>,
101 ) -> Result<(), AppError> {
102 sqlx::query(
104 r#"
105 UPDATE tickets
106 SET sla_escalated_at = $2
107 WHERE id = $1 AND sla_escalated_at IS NULL
108 "#,
109 )
110 .bind(ticket_id)
111 .bind(escalated_at)
112 .execute(&self.pool)
113 .await
114 .map_err(|e| AppError::Database(e.to_string()))?;
115 Ok(())
116 }
117}