koprogo_api/infrastructure/web/handlers/
convocation_handlers.rs1use crate::application::dto::{
2 CreateConvocationRequest, ScheduleConvocationRequest, SendConvocationRequest, SetProxyRequest,
3 UpdateAttendanceRequest,
4};
5use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
6use crate::infrastructure::web::{AppState, AuthenticatedUser};
7use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
8use uuid::Uuid;
9
10#[post("/convocations")]
13pub async fn create_convocation(
14 state: web::Data<AppState>,
15 user: AuthenticatedUser,
16 request: web::Json<CreateConvocationRequest>,
17) -> impl Responder {
18 let organization_id = match user.require_organization() {
19 Ok(org_id) => org_id,
20 Err(e) => {
21 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
22 }
23 };
24
25 let created_by = user.user_id;
26
27 match state
28 .convocation_use_cases
29 .create_convocation(organization_id, request.into_inner(), created_by)
30 .await
31 {
32 Ok(convocation) => {
33 AuditLogEntry::new(
34 AuditEventType::ConvocationCreated,
35 Some(user.user_id),
36 Some(organization_id),
37 )
38 .with_resource("Convocation", convocation.id)
39 .log();
40
41 HttpResponse::Created().json(convocation)
42 }
43 Err(err) => {
44 AuditLogEntry::new(
45 AuditEventType::ConvocationCreated,
46 Some(user.user_id),
47 Some(organization_id),
48 )
49 .with_error(err.clone())
50 .log();
51
52 HttpResponse::BadRequest().json(serde_json::json!({"error": err}))
53 }
54 }
55}
56
57#[get("/convocations/{id}")]
58pub async fn get_convocation(state: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
59 match state.convocation_use_cases.get_convocation(*id).await {
60 Ok(convocation) => HttpResponse::Ok().json(convocation),
61 Err(err) => HttpResponse::NotFound().json(serde_json::json!({"error": err})),
62 }
63}
64
65#[get("/meetings/{meeting_id}/convocation")]
66pub async fn get_convocation_by_meeting(
67 state: web::Data<AppState>,
68 meeting_id: web::Path<Uuid>,
69) -> impl Responder {
70 match state
71 .convocation_use_cases
72 .get_convocation_by_meeting(*meeting_id)
73 .await
74 {
75 Ok(Some(convocation)) => HttpResponse::Ok().json(convocation),
76 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
77 "error": "Convocation not found for this meeting"
78 })),
79 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
80 }
81}
82
83#[get("/buildings/{building_id}/convocations")]
84pub async fn list_building_convocations(
85 state: web::Data<AppState>,
86 building_id: web::Path<Uuid>,
87) -> impl Responder {
88 match state
89 .convocation_use_cases
90 .list_building_convocations(*building_id)
91 .await
92 {
93 Ok(convocations) => HttpResponse::Ok().json(convocations),
94 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
95 }
96}
97
98#[get("/organizations/{organization_id}/convocations")]
99pub async fn list_organization_convocations(
100 state: web::Data<AppState>,
101 organization_id: web::Path<Uuid>,
102) -> impl Responder {
103 match state
104 .convocation_use_cases
105 .list_organization_convocations(*organization_id)
106 .await
107 {
108 Ok(convocations) => HttpResponse::Ok().json(convocations),
109 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
110 }
111}
112
113#[delete("/convocations/{id}")]
114pub async fn delete_convocation(
115 state: web::Data<AppState>,
116 user: AuthenticatedUser,
117 id: web::Path<Uuid>,
118) -> impl Responder {
119 let organization_id = match user.require_organization() {
120 Ok(org_id) => org_id,
121 Err(e) => {
122 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
123 }
124 };
125
126 match state.convocation_use_cases.delete_convocation(*id).await {
127 Ok(true) => {
128 AuditLogEntry::new(
129 AuditEventType::ConvocationDeleted,
130 Some(user.user_id),
131 Some(organization_id),
132 )
133 .with_resource("Convocation", *id)
134 .log();
135
136 HttpResponse::NoContent().finish()
137 }
138 Ok(false) => HttpResponse::NotFound().json(serde_json::json!({
139 "error": "Convocation not found"
140 })),
141 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
142 }
143}
144
145#[put("/convocations/{id}/schedule")]
148pub async fn schedule_convocation(
149 state: web::Data<AppState>,
150 user: AuthenticatedUser,
151 id: web::Path<Uuid>,
152 request: web::Json<ScheduleConvocationRequest>,
153) -> impl Responder {
154 let organization_id = match user.require_organization() {
155 Ok(org_id) => org_id,
156 Err(e) => {
157 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
158 }
159 };
160
161 match state
162 .convocation_use_cases
163 .schedule_convocation(*id, request.into_inner())
164 .await
165 {
166 Ok(convocation) => {
167 AuditLogEntry::new(
168 AuditEventType::ConvocationScheduled,
169 Some(user.user_id),
170 Some(organization_id),
171 )
172 .with_resource("Convocation", convocation.id)
173 .log();
174
175 HttpResponse::Ok().json(convocation)
176 }
177 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
178 }
179}
180
181#[post("/convocations/{id}/send")]
182pub async fn send_convocation(
183 state: web::Data<AppState>,
184 user: AuthenticatedUser,
185 id: web::Path<Uuid>,
186 request: web::Json<SendConvocationRequest>,
187) -> impl Responder {
188 let organization_id = match user.require_organization() {
189 Ok(org_id) => org_id,
190 Err(e) => {
191 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
192 }
193 };
194
195 match state
197 .convocation_use_cases
198 .send_convocation(*id, request.into_inner())
199 .await
200 {
201 Ok(convocation) => {
202 AuditLogEntry::new(
203 AuditEventType::ConvocationSent,
204 Some(user.user_id),
205 Some(organization_id),
206 )
207 .with_resource("Convocation", convocation.id)
208 .with_details(format!("recipients: {}", convocation.total_recipients))
209 .log();
210
211 HttpResponse::Ok().json(convocation)
212 }
213 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
214 }
215}
216
217#[put("/convocations/{id}/cancel")]
218pub async fn cancel_convocation(
219 state: web::Data<AppState>,
220 user: AuthenticatedUser,
221 id: web::Path<Uuid>,
222) -> impl Responder {
223 let organization_id = match user.require_organization() {
224 Ok(org_id) => org_id,
225 Err(e) => {
226 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
227 }
228 };
229
230 match state.convocation_use_cases.cancel_convocation(*id).await {
231 Ok(convocation) => {
232 AuditLogEntry::new(
233 AuditEventType::ConvocationCancelled,
234 Some(user.user_id),
235 Some(organization_id),
236 )
237 .with_resource("Convocation", convocation.id)
238 .log();
239
240 HttpResponse::Ok().json(convocation)
241 }
242 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
243 }
244}
245
246#[get("/convocations/{id}/recipients")]
249pub async fn list_convocation_recipients(
250 state: web::Data<AppState>,
251 id: web::Path<Uuid>,
252) -> impl Responder {
253 match state
254 .convocation_use_cases
255 .list_convocation_recipients(*id)
256 .await
257 {
258 Ok(recipients) => HttpResponse::Ok().json(recipients),
259 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
260 }
261}
262
263#[get("/convocations/{id}/tracking-summary")]
264pub async fn get_convocation_tracking_summary(
265 state: web::Data<AppState>,
266 id: web::Path<Uuid>,
267) -> impl Responder {
268 match state.convocation_use_cases.get_tracking_summary(*id).await {
269 Ok(summary) => HttpResponse::Ok().json(summary),
270 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
271 }
272}
273
274#[put("/convocation-recipients/{id}/email-opened")]
275pub async fn mark_recipient_email_opened(
276 state: web::Data<AppState>,
277 id: web::Path<Uuid>,
278) -> impl Responder {
279 match state
280 .convocation_use_cases
281 .mark_recipient_email_opened(*id)
282 .await
283 {
284 Ok(recipient) => HttpResponse::Ok().json(recipient),
285 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
286 }
287}
288
289#[put("/convocation-recipients/{id}/attendance")]
290pub async fn update_recipient_attendance(
291 state: web::Data<AppState>,
292 user: AuthenticatedUser,
293 id: web::Path<Uuid>,
294 request: web::Json<UpdateAttendanceRequest>,
295) -> impl Responder {
296 let organization_id = match user.require_organization() {
297 Ok(org_id) => org_id,
298 Err(e) => {
299 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
300 }
301 };
302
303 match state
304 .convocation_use_cases
305 .update_recipient_attendance(*id, request.attendance_status.clone())
306 .await
307 {
308 Ok(recipient) => {
309 AuditLogEntry::new(
310 AuditEventType::ConvocationAttendanceUpdated,
311 Some(user.user_id),
312 Some(organization_id),
313 )
314 .with_resource("ConvocationRecipient", recipient.id)
315 .with_details(format!("status: {:?}", recipient.attendance_status))
316 .log();
317
318 HttpResponse::Ok().json(recipient)
319 }
320 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
321 }
322}
323
324#[put("/convocation-recipients/{id}/proxy")]
325pub async fn set_recipient_proxy(
326 state: web::Data<AppState>,
327 user: AuthenticatedUser,
328 id: web::Path<Uuid>,
329 request: web::Json<SetProxyRequest>,
330) -> impl Responder {
331 let organization_id = match user.require_organization() {
332 Ok(org_id) => org_id,
333 Err(e) => {
334 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
335 }
336 };
337
338 match state
339 .convocation_use_cases
340 .set_recipient_proxy(*id, request.proxy_owner_id)
341 .await
342 {
343 Ok(recipient) => {
344 AuditLogEntry::new(
345 AuditEventType::ConvocationProxySet,
346 Some(user.user_id),
347 Some(organization_id),
348 )
349 .with_resource("ConvocationRecipient", recipient.id)
350 .with_details(format!("proxy_owner_id: {:?}", recipient.proxy_owner_id))
351 .log();
352
353 HttpResponse::Ok().json(recipient)
354 }
355 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
356 }
357}
358
359#[post("/convocations/{id}/send-reminders")]
360pub async fn send_convocation_reminders(
361 state: web::Data<AppState>,
362 user: AuthenticatedUser,
363 id: web::Path<Uuid>,
364) -> impl Responder {
365 let organization_id = match user.require_organization() {
366 Ok(org_id) => org_id,
367 Err(e) => {
368 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
369 }
370 };
371
372 match state.convocation_use_cases.send_reminders(*id).await {
373 Ok(recipients) => {
374 AuditLogEntry::new(
375 AuditEventType::ConvocationReminderSent,
376 Some(user.user_id),
377 Some(organization_id),
378 )
379 .with_resource("Convocation", *id)
380 .with_details(format!("recipients: {}", recipients.len()))
381 .log();
382
383 HttpResponse::Ok().json(recipients)
384 }
385 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
386 }
387}