koprogo_api/infrastructure/web/handlers/
meeting_handlers.rs

1use crate::application::dto::{
2    AddAgendaItemRequest, CompleteMeetingRequest, CreateMeetingRequest, PageRequest, PageResponse,
3    UpdateMeetingRequest,
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("/meetings")]
11pub async fn create_meeting(
12    state: web::Data<AppState>,
13    user: AuthenticatedUser, // JWT-extracted user info (SECURE!)
14    mut request: web::Json<CreateMeetingRequest>,
15) -> impl Responder {
16    // Override the organization_id from request with the one from JWT token
17    // This prevents users from creating meetings in other organizations
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!({
22                "error": e.to_string()
23            }))
24        }
25    };
26    request.organization_id = organization_id;
27
28    match state
29        .meeting_use_cases
30        .create_meeting(request.into_inner())
31        .await
32    {
33        Ok(meeting) => {
34            // Audit log: successful meeting creation
35            AuditLogEntry::new(
36                AuditEventType::MeetingCreated,
37                Some(user.user_id),
38                Some(organization_id),
39            )
40            .with_resource("Meeting", meeting.id)
41            .log();
42
43            HttpResponse::Created().json(meeting)
44        }
45        Err(err) => {
46            // Audit log: failed meeting creation
47            AuditLogEntry::new(
48                AuditEventType::MeetingCreated,
49                Some(user.user_id),
50                Some(organization_id),
51            )
52            .with_error(err.clone())
53            .log();
54
55            HttpResponse::BadRequest().json(serde_json::json!({
56                "error": err
57            }))
58        }
59    }
60}
61
62#[get("/meetings/{id}")]
63pub async fn get_meeting(state: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
64    match state.meeting_use_cases.get_meeting(*id).await {
65        Ok(Some(meeting)) => HttpResponse::Ok().json(meeting),
66        Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
67            "error": "Meeting not found"
68        })),
69        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
70            "error": err
71        })),
72    }
73}
74
75#[get("/meetings")]
76pub async fn list_meetings(
77    state: web::Data<AppState>,
78    user: AuthenticatedUser,
79    page_request: web::Query<PageRequest>,
80) -> impl Responder {
81    let organization_id = user.organization_id;
82
83    match state
84        .meeting_use_cases
85        .list_meetings_paginated(&page_request, organization_id)
86        .await
87    {
88        Ok((meetings, total)) => {
89            let response =
90                PageResponse::new(meetings, page_request.page, page_request.per_page, total);
91            HttpResponse::Ok().json(response)
92        }
93        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
94            "error": err
95        })),
96    }
97}
98
99#[get("/buildings/{building_id}/meetings")]
100pub async fn list_meetings_by_building(
101    state: web::Data<AppState>,
102    building_id: web::Path<Uuid>,
103) -> impl Responder {
104    match state
105        .meeting_use_cases
106        .list_meetings_by_building(*building_id)
107        .await
108    {
109        Ok(meetings) => HttpResponse::Ok().json(meetings),
110        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
111            "error": err
112        })),
113    }
114}
115
116#[put("/meetings/{id}")]
117pub async fn update_meeting(
118    state: web::Data<AppState>,
119    id: web::Path<Uuid>,
120    request: web::Json<UpdateMeetingRequest>,
121) -> impl Responder {
122    match state
123        .meeting_use_cases
124        .update_meeting(*id, request.into_inner())
125        .await
126    {
127        Ok(meeting) => HttpResponse::Ok().json(meeting),
128        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({
129            "error": err
130        })),
131    }
132}
133
134#[post("/meetings/{id}/agenda")]
135pub async fn add_agenda_item(
136    state: web::Data<AppState>,
137    id: web::Path<Uuid>,
138    request: web::Json<AddAgendaItemRequest>,
139) -> impl Responder {
140    match state
141        .meeting_use_cases
142        .add_agenda_item(*id, request.into_inner())
143        .await
144    {
145        Ok(meeting) => HttpResponse::Ok().json(meeting),
146        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({
147            "error": err
148        })),
149    }
150}
151
152#[post("/meetings/{id}/complete")]
153pub async fn complete_meeting(
154    state: web::Data<AppState>,
155    user: AuthenticatedUser,
156    id: web::Path<Uuid>,
157    request: web::Json<CompleteMeetingRequest>,
158) -> impl Responder {
159    match state
160        .meeting_use_cases
161        .complete_meeting(*id, request.into_inner())
162        .await
163    {
164        Ok(meeting) => {
165            // Audit log: successful meeting completion
166            AuditLogEntry::new(
167                AuditEventType::MeetingCompleted,
168                Some(user.user_id),
169                user.organization_id,
170            )
171            .with_resource("Meeting", *id)
172            .log();
173
174            HttpResponse::Ok().json(meeting)
175        }
176        Err(err) => {
177            // Audit log: failed meeting completion
178            AuditLogEntry::new(
179                AuditEventType::MeetingCompleted,
180                Some(user.user_id),
181                user.organization_id,
182            )
183            .with_resource("Meeting", *id)
184            .with_error(err.clone())
185            .log();
186
187            HttpResponse::BadRequest().json(serde_json::json!({
188                "error": err
189            }))
190        }
191    }
192}
193
194#[post("/meetings/{id}/cancel")]
195pub async fn cancel_meeting(state: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
196    match state.meeting_use_cases.cancel_meeting(*id).await {
197        Ok(meeting) => HttpResponse::Ok().json(meeting),
198        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({
199            "error": err
200        })),
201    }
202}
203
204#[delete("/meetings/{id}")]
205pub async fn delete_meeting(state: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
206    match state.meeting_use_cases.delete_meeting(*id).await {
207        Ok(true) => HttpResponse::NoContent().finish(),
208        Ok(false) => HttpResponse::NotFound().json(serde_json::json!({
209            "error": "Meeting not found"
210        })),
211        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
212            "error": err
213        })),
214    }
215}