koprogo_api/infrastructure/web/handlers/
ag_session_handlers.rs

1use crate::application::dto::ag_session_dto::{
2    CreateAgSessionDto, EndAgSessionDto, RecordRemoteJoinDto,
3};
4use crate::infrastructure::web::{AppState, AuthenticatedUser};
5use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
6use uuid::Uuid;
7
8/// POST /meetings/:id/ag-session — Crée une session visio pour une réunion
9#[post("/meetings/{meeting_id}/ag-session")]
10pub async fn create_ag_session(
11    state: web::Data<AppState>,
12    user: AuthenticatedUser,
13    path: web::Path<Uuid>,
14    body: web::Json<CreateAgSessionDto>,
15) -> impl Responder {
16    let organization_id = match user.require_organization() {
17        Ok(id) => id,
18        Err(e) => {
19            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
20        }
21    };
22
23    let mut dto = body.into_inner();
24    dto.meeting_id = path.into_inner();
25
26    match state
27        .ag_session_use_cases
28        .create_session(organization_id, dto, user.user_id)
29        .await
30    {
31        Ok(session) => HttpResponse::Created().json(session),
32        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
33    }
34}
35
36/// GET /meetings/:id/ag-session — Récupère la session visio d'une réunion
37#[get("/meetings/{meeting_id}/ag-session")]
38pub async fn get_ag_session_for_meeting(
39    state: web::Data<AppState>,
40    user: AuthenticatedUser,
41    path: web::Path<Uuid>,
42) -> impl Responder {
43    let organization_id = match user.require_organization() {
44        Ok(id) => id,
45        Err(e) => {
46            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
47        }
48    };
49
50    match state
51        .ag_session_use_cases
52        .get_session_for_meeting(path.into_inner(), organization_id)
53        .await
54    {
55        Ok(Some(session)) => HttpResponse::Ok().json(session),
56        Ok(None) => HttpResponse::NotFound()
57            .json(serde_json::json!({"error": "Aucune session visio pour cette réunion"})),
58        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
59    }
60}
61
62/// GET /ag-sessions — Liste les sessions de l'organisation
63#[get("/ag-sessions")]
64pub async fn list_ag_sessions(
65    state: web::Data<AppState>,
66    user: AuthenticatedUser,
67) -> impl Responder {
68    let organization_id = match user.require_organization() {
69        Ok(id) => id,
70        Err(e) => {
71            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
72        }
73    };
74
75    match state
76        .ag_session_use_cases
77        .list_sessions(organization_id)
78        .await
79    {
80        Ok(sessions) => HttpResponse::Ok().json(sessions),
81        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
82    }
83}
84
85/// GET /ag-sessions/:id — Récupère une session par son ID
86#[get("/ag-sessions/{id}")]
87pub async fn get_ag_session(
88    state: web::Data<AppState>,
89    user: AuthenticatedUser,
90    path: web::Path<Uuid>,
91) -> impl Responder {
92    let organization_id = match user.require_organization() {
93        Ok(id) => id,
94        Err(e) => {
95            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
96        }
97    };
98
99    match state
100        .ag_session_use_cases
101        .get_session(path.into_inner(), organization_id)
102        .await
103    {
104        Ok(session) => HttpResponse::Ok().json(session),
105        Err(e) => {
106            if e.contains("introuvable") {
107                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
108            } else if e.contains("refusé") {
109                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
110            } else {
111                HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
112            }
113        }
114    }
115}
116
117/// PUT /ag-sessions/:id/start — Démarre la session (Scheduled → Live)
118#[put("/ag-sessions/{id}/start")]
119pub async fn start_ag_session(
120    state: web::Data<AppState>,
121    user: AuthenticatedUser,
122    path: web::Path<Uuid>,
123) -> impl Responder {
124    let organization_id = match user.require_organization() {
125        Ok(id) => id,
126        Err(e) => {
127            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
128        }
129    };
130
131    match state
132        .ag_session_use_cases
133        .start_session(path.into_inner(), organization_id)
134        .await
135    {
136        Ok(session) => HttpResponse::Ok().json(session),
137        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
138    }
139}
140
141/// PUT /ag-sessions/:id/end — Termine la session (Live → Ended)
142#[put("/ag-sessions/{id}/end")]
143pub async fn end_ag_session(
144    state: web::Data<AppState>,
145    user: AuthenticatedUser,
146    path: web::Path<Uuid>,
147    body: web::Json<EndAgSessionDto>,
148) -> impl Responder {
149    let organization_id = match user.require_organization() {
150        Ok(id) => id,
151        Err(e) => {
152            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
153        }
154    };
155
156    match state
157        .ag_session_use_cases
158        .end_session(path.into_inner(), organization_id, body.into_inner())
159        .await
160    {
161        Ok(session) => HttpResponse::Ok().json(session),
162        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
163    }
164}
165
166/// PUT /ag-sessions/:id/cancel — Annule la session (Scheduled → Cancelled)
167#[put("/ag-sessions/{id}/cancel")]
168pub async fn cancel_ag_session(
169    state: web::Data<AppState>,
170    user: AuthenticatedUser,
171    path: web::Path<Uuid>,
172) -> impl Responder {
173    let organization_id = match user.require_organization() {
174        Ok(id) => id,
175        Err(e) => {
176            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
177        }
178    };
179
180    match state
181        .ag_session_use_cases
182        .cancel_session(path.into_inner(), organization_id)
183        .await
184    {
185        Ok(session) => HttpResponse::Ok().json(session),
186        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
187    }
188}
189
190/// POST /ag-sessions/:id/join — Enregistre un participant distant
191#[post("/ag-sessions/{id}/join")]
192pub async fn record_remote_join(
193    state: web::Data<AppState>,
194    user: AuthenticatedUser,
195    path: web::Path<Uuid>,
196    body: web::Json<RecordRemoteJoinDto>,
197) -> impl Responder {
198    let organization_id = match user.require_organization() {
199        Ok(id) => id,
200        Err(e) => {
201            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
202        }
203    };
204
205    match state
206        .ag_session_use_cases
207        .record_remote_join(path.into_inner(), organization_id, body.into_inner())
208        .await
209    {
210        Ok(session) => HttpResponse::Ok().json(session),
211        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
212    }
213}
214
215/// GET /ag-sessions/:id/quorum — Calcule le quorum combiné (présentiel + distanciel)
216#[get("/ag-sessions/{id}/quorum")]
217pub async fn get_combined_quorum(
218    state: web::Data<AppState>,
219    user: AuthenticatedUser,
220    path: web::Path<Uuid>,
221    query: web::Query<CombinedQuorumQuery>,
222) -> impl Responder {
223    let organization_id = match user.require_organization() {
224        Ok(id) => id,
225        Err(e) => {
226            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
227        }
228    };
229
230    match state
231        .ag_session_use_cases
232        .calculate_combined_quorum(
233            path.into_inner(),
234            organization_id,
235            query.physical_quotas,
236            query.total_building_quotas,
237        )
238        .await
239    {
240        Ok(result) => HttpResponse::Ok().json(result),
241        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
242    }
243}
244
245/// DELETE /ag-sessions/:id — Supprime une session annulée ou planifiée
246#[delete("/ag-sessions/{id}")]
247pub async fn delete_ag_session(
248    state: web::Data<AppState>,
249    user: AuthenticatedUser,
250    path: web::Path<Uuid>,
251) -> impl Responder {
252    let organization_id = match user.require_organization() {
253        Ok(id) => id,
254        Err(e) => {
255            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
256        }
257    };
258
259    match state
260        .ag_session_use_cases
261        .delete_session(path.into_inner(), organization_id)
262        .await
263    {
264        Ok(()) => HttpResponse::NoContent().finish(),
265        Err(e) => {
266            if e.contains("introuvable") {
267                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
268            } else if e.contains("refusé") {
269                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
270            } else {
271                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
272            }
273        }
274    }
275}
276
277/// GET /ag-sessions/platform-stats — Platform-wide AG session statistics (SuperAdmin only)
278/// Issue #274: AG Visioconférence Refinements
279#[get("/ag-sessions/platform-stats")]
280pub async fn get_ag_session_platform_stats(
281    state: web::Data<AppState>,
282    user: AuthenticatedUser,
283) -> impl Responder {
284    // Only superadmin can see platform-wide stats
285    if user.role != "SUPERADMIN" {
286        return HttpResponse::Forbidden().json(serde_json::json!({
287            "error": "SuperAdmin only"
288        }));
289    }
290
291    // Platform stats - count pending sessions as a basic metric
292    match state.ag_session_use_cases.list_pending_sessions().await {
293        Ok(pending) => HttpResponse::Ok().json(serde_json::json!({
294            "pending_sessions_count": pending.len(),
295            "note": "Platform-wide statistics endpoint - detailed metrics coming soon"
296        })),
297        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
298    }
299}
300
301#[derive(serde::Deserialize)]
302pub struct CombinedQuorumQuery {
303    pub physical_quotas: f64,
304    pub total_building_quotas: f64,
305}