koprogo_api/infrastructure/database/repositories/
meeting_repository_impl.rs

1use crate::application::ports::MeetingRepository;
2use crate::domain::entities::{Meeting, MeetingStatus, MeetingType};
3use crate::infrastructure::database::pool::DbPool;
4use async_trait::async_trait;
5use sqlx::Row;
6use uuid::Uuid;
7
8pub struct PostgresMeetingRepository {
9    pool: DbPool,
10}
11
12impl PostgresMeetingRepository {
13    pub fn new(pool: DbPool) -> Self {
14        Self { pool }
15    }
16}
17
18#[async_trait]
19impl MeetingRepository for PostgresMeetingRepository {
20    async fn create(&self, meeting: &Meeting) -> Result<Meeting, String> {
21        let meeting_type_str = match meeting.meeting_type {
22            MeetingType::Ordinary => "ordinary",
23            MeetingType::Extraordinary => "extraordinary",
24        };
25
26        let status_str = match meeting.status {
27            MeetingStatus::Scheduled => "scheduled",
28            MeetingStatus::Completed => "completed",
29            MeetingStatus::Cancelled => "cancelled",
30        };
31
32        let agenda_json = serde_json::to_value(&meeting.agenda)
33            .map_err(|e| format!("JSON serialization error: {}", e))?;
34
35        sqlx::query(
36            r#"
37            INSERT INTO meetings (id, organization_id, building_id, meeting_type, title, description, scheduled_date, location, status, agenda, attendees_count, created_at, updated_at)
38            VALUES ($1, $2, $3, CAST($4 AS meeting_type), $5, $6, $7, $8, CAST($9 AS meeting_status), $10, $11, $12, $13)
39            "#,
40        )
41        .bind(meeting.id)
42        .bind(meeting.organization_id)
43        .bind(meeting.building_id)
44        .bind(meeting_type_str)
45        .bind(&meeting.title)
46        .bind(&meeting.description)
47        .bind(meeting.scheduled_date)
48        .bind(&meeting.location)
49        .bind(status_str)
50        .bind(agenda_json)
51        .bind(meeting.attendees_count)
52        .bind(meeting.created_at)
53        .bind(meeting.updated_at)
54        .execute(&self.pool)
55        .await
56        .map_err(|e| format!("Database error: {}", e))?;
57
58        Ok(meeting.clone())
59    }
60
61    async fn find_by_id(&self, id: Uuid) -> Result<Option<Meeting>, String> {
62        let row = sqlx::query(
63            r#"
64            SELECT id, organization_id, building_id, meeting_type::text AS meeting_type, title, description, scheduled_date, location, status::text AS status, agenda, attendees_count, created_at, updated_at
65            FROM meetings
66            WHERE id = $1
67            "#,
68        )
69        .bind(id)
70        .fetch_optional(&self.pool)
71        .await
72        .map_err(|e| format!("Database error: {}", e))?;
73
74        Ok(row.map(|row| {
75            let meeting_type_str: String = row.get("meeting_type");
76            let meeting_type = match meeting_type_str.as_str() {
77                "extraordinary" => MeetingType::Extraordinary,
78                _ => MeetingType::Ordinary,
79            };
80
81            let status_str: String = row.get("status");
82            let status = match status_str.as_str() {
83                "completed" => MeetingStatus::Completed,
84                "cancelled" => MeetingStatus::Cancelled,
85                _ => MeetingStatus::Scheduled,
86            };
87
88            let agenda_json: serde_json::Value = row.get("agenda");
89            let agenda: Vec<String> = serde_json::from_value(agenda_json).unwrap_or_default();
90
91            Meeting {
92                id: row.get("id"),
93                organization_id: row.get("organization_id"),
94                building_id: row.get("building_id"),
95                meeting_type,
96                title: row.get("title"),
97                description: row.get("description"),
98                scheduled_date: row.get("scheduled_date"),
99                location: row.get("location"),
100                status,
101                agenda,
102                attendees_count: row.get("attendees_count"),
103                created_at: row.get("created_at"),
104                updated_at: row.get("updated_at"),
105            }
106        }))
107    }
108
109    async fn find_by_building(&self, building_id: Uuid) -> Result<Vec<Meeting>, String> {
110        let rows = sqlx::query(
111            r#"
112            SELECT id, organization_id, building_id, meeting_type::text AS meeting_type, title, description, scheduled_date, location, status::text AS status, agenda, attendees_count, created_at, updated_at
113            FROM meetings
114            WHERE building_id = $1
115            ORDER BY scheduled_date DESC
116            "#,
117        )
118        .bind(building_id)
119        .fetch_all(&self.pool)
120        .await
121        .map_err(|e| format!("Database error: {}", e))?;
122
123        Ok(rows
124            .iter()
125            .map(|row| {
126                let meeting_type_str: String = row.get("meeting_type");
127                let meeting_type = match meeting_type_str.as_str() {
128                    "extraordinary" => MeetingType::Extraordinary,
129                    _ => MeetingType::Ordinary,
130                };
131
132                let status_str: String = row.get("status");
133                let status = match status_str.as_str() {
134                    "completed" => MeetingStatus::Completed,
135                    "cancelled" => MeetingStatus::Cancelled,
136                    _ => MeetingStatus::Scheduled,
137                };
138
139                let agenda_json: serde_json::Value = row.get("agenda");
140                let agenda: Vec<String> = serde_json::from_value(agenda_json).unwrap_or_default();
141
142                Meeting {
143                    id: row.get("id"),
144                    organization_id: row.get("organization_id"),
145                    building_id: row.get("building_id"),
146                    meeting_type,
147                    title: row.get("title"),
148                    description: row.get("description"),
149                    scheduled_date: row.get("scheduled_date"),
150                    location: row.get("location"),
151                    status,
152                    agenda,
153                    attendees_count: row.get("attendees_count"),
154                    created_at: row.get("created_at"),
155                    updated_at: row.get("updated_at"),
156                }
157            })
158            .collect())
159    }
160
161    async fn update(&self, meeting: &Meeting) -> Result<Meeting, String> {
162        let status_str = match meeting.status {
163            MeetingStatus::Scheduled => "scheduled",
164            MeetingStatus::Completed => "completed",
165            MeetingStatus::Cancelled => "cancelled",
166        };
167
168        let agenda_json = serde_json::to_value(&meeting.agenda)
169            .map_err(|e| format!("JSON serialization error: {}", e))?;
170
171        sqlx::query(
172            r#"
173            UPDATE meetings
174            SET title = $2,
175                description = $3,
176                scheduled_date = $4,
177                location = $5,
178                status = CAST($6 AS meeting_status),
179                agenda = $7,
180                attendees_count = $8,
181                updated_at = $9
182            WHERE id = $1
183            "#,
184        )
185        .bind(meeting.id)
186        .bind(&meeting.title)
187        .bind(&meeting.description)
188        .bind(meeting.scheduled_date)
189        .bind(&meeting.location)
190        .bind(status_str)
191        .bind(agenda_json)
192        .bind(meeting.attendees_count)
193        .bind(meeting.updated_at)
194        .execute(&self.pool)
195        .await
196        .map_err(|e| format!("Database error: {}", e))?;
197
198        Ok(meeting.clone())
199    }
200
201    async fn delete(&self, id: Uuid) -> Result<bool, String> {
202        let result = sqlx::query("DELETE FROM meetings WHERE id = $1")
203            .bind(id)
204            .execute(&self.pool)
205            .await
206            .map_err(|e| format!("Database error: {}", e))?;
207
208        Ok(result.rows_affected() > 0)
209    }
210
211    async fn find_all_paginated(
212        &self,
213        page_request: &crate::application::dto::PageRequest,
214        organization_id: Option<Uuid>,
215    ) -> Result<(Vec<Meeting>, i64), String> {
216        // Validate page request
217        page_request.validate()?;
218
219        // Build WHERE clause
220        let where_clause = if let Some(org_id) = organization_id {
221            format!("WHERE organization_id = '{}'", org_id)
222        } else {
223            String::new()
224        };
225
226        // Count total items
227        let count_query = format!("SELECT COUNT(*) FROM meetings {}", where_clause);
228        let total_items = sqlx::query_scalar::<_, i64>(&count_query)
229            .fetch_one(&self.pool)
230            .await
231            .map_err(|e| format!("Database error: {}", e))?;
232
233        // Fetch paginated data
234        let data_query = format!(
235            "SELECT id, organization_id, building_id, meeting_type, title, description, scheduled_date, location, status, agenda, attendees_count, created_at, updated_at \
236             FROM meetings {} ORDER BY scheduled_date DESC LIMIT {} OFFSET {}",
237            where_clause,
238            page_request.limit(),
239            page_request.offset()
240        );
241
242        let rows = sqlx::query(&data_query)
243            .fetch_all(&self.pool)
244            .await
245            .map_err(|e| format!("Database error: {}", e))?;
246
247        let meetings: Vec<Meeting> = rows
248            .iter()
249            .map(|row| {
250                let meeting_type_str: String = row
251                    .try_get("meeting_type")
252                    .unwrap_or_else(|_| "ordinary".to_string());
253                let meeting_type = match meeting_type_str.as_str() {
254                    "extraordinary" => crate::domain::entities::MeetingType::Extraordinary,
255                    _ => crate::domain::entities::MeetingType::Ordinary,
256                };
257
258                let status_str: String = row
259                    .try_get("status")
260                    .unwrap_or_else(|_| "scheduled".to_string());
261                let status = match status_str.as_str() {
262                    "completed" => crate::domain::entities::MeetingStatus::Completed,
263                    "cancelled" => crate::domain::entities::MeetingStatus::Cancelled,
264                    _ => crate::domain::entities::MeetingStatus::Scheduled,
265                };
266
267                let agenda_json: serde_json::Value = row.get("agenda");
268                let agenda: Vec<String> = serde_json::from_value(agenda_json).unwrap_or_default();
269
270                Meeting {
271                    id: row.get("id"),
272                    organization_id: row.get("organization_id"),
273                    building_id: row.get("building_id"),
274                    meeting_type,
275                    title: row.get("title"),
276                    description: row.get("description"),
277                    scheduled_date: row.get("scheduled_date"),
278                    location: row.get("location"),
279                    status,
280                    agenda,
281                    attendees_count: row.get("attendees_count"),
282                    created_at: row.get("created_at"),
283                    updated_at: row.get("updated_at"),
284                }
285            })
286            .collect();
287
288        Ok((meetings, total_items))
289    }
290}