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 page_request.validate()?;
218
219 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 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 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}