koprogo_api/infrastructure/web/handlers/
ag_session_handlers.rs1use crate::application::dto::ag_session_dto::{
2 CreateAgSessionDto, EndAgSessionDto, RecordRemoteJoinDto,
3};
4use crate::infrastructure::web::classification_erreurs;
5use crate::infrastructure::web::{AppState, AuthenticatedUser};
6use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
7use rust_decimal::Decimal;
8use uuid::Uuid;
9
10#[post("/meetings/{meeting_id}/ag-session")]
12pub async fn create_ag_session(
13 state: web::Data<AppState>,
14 user: AuthenticatedUser,
15 path: web::Path<Uuid>,
16 body: web::Json<CreateAgSessionDto>,
17) -> impl Responder {
18 let organization_id = match user.require_organization() {
19 Ok(id) => id,
20 Err(e) => {
21 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
22 }
23 };
24
25 let mut dto = body.into_inner();
26 dto.meeting_id = path.into_inner();
27
28 match state
29 .ag_session_use_cases
30 .create_session(organization_id, dto, user.user_id)
31 .await
32 {
33 Ok(session) => HttpResponse::Created().json(session),
34 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
35 }
36}
37
38#[get("/meetings/{meeting_id}/ag-session")]
40pub async fn get_ag_session_for_meeting(
41 state: web::Data<AppState>,
42 user: AuthenticatedUser,
43 path: web::Path<Uuid>,
44) -> impl Responder {
45 let organization_id = match user.require_organization() {
46 Ok(id) => id,
47 Err(e) => {
48 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
49 }
50 };
51
52 match state
53 .ag_session_use_cases
54 .get_session_for_meeting(path.into_inner(), organization_id)
55 .await
56 {
57 Ok(Some(session)) => HttpResponse::Ok().json(session),
58 Ok(None) => HttpResponse::NotFound()
59 .json(serde_json::json!({"error": "Aucune session visio pour cette réunion"})),
60 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
61 }
62}
63
64#[get("/ag-sessions")]
66pub async fn list_ag_sessions(
67 state: web::Data<AppState>,
68 user: AuthenticatedUser,
69) -> impl Responder {
70 let organization_id = match user.require_organization() {
71 Ok(id) => id,
72 Err(e) => {
73 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
74 }
75 };
76
77 match state
78 .ag_session_use_cases
79 .list_sessions(organization_id)
80 .await
81 {
82 Ok(sessions) => HttpResponse::Ok().json(sessions),
83 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
84 }
85}
86
87#[get("/ag-sessions/{id}")]
89pub async fn get_ag_session(
90 state: web::Data<AppState>,
91 user: AuthenticatedUser,
92 path: web::Path<Uuid>,
93) -> impl Responder {
94 let organization_id = match user.require_organization() {
95 Ok(id) => id,
96 Err(e) => {
97 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
98 }
99 };
100
101 match state
102 .ag_session_use_cases
103 .get_session(path.into_inner(), organization_id)
104 .await
105 {
106 Ok(session) => HttpResponse::Ok().json(session),
107 Err(e) => {
108 if classification_erreurs::est_introuvable(&e) {
109 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
110 } else if classification_erreurs::est_interdit(&e) {
111 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
112 } else {
113 HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
114 }
115 }
116 }
117}
118
119#[put("/ag-sessions/{id}/start")]
121pub async fn start_ag_session(
122 state: web::Data<AppState>,
123 user: AuthenticatedUser,
124 path: web::Path<Uuid>,
125) -> impl Responder {
126 let organization_id = match user.require_organization() {
127 Ok(id) => id,
128 Err(e) => {
129 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
130 }
131 };
132
133 match state
134 .ag_session_use_cases
135 .start_session(path.into_inner(), organization_id)
136 .await
137 {
138 Ok(session) => HttpResponse::Ok().json(session),
139 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
140 }
141}
142
143#[put("/ag-sessions/{id}/end")]
145pub async fn end_ag_session(
146 state: web::Data<AppState>,
147 user: AuthenticatedUser,
148 path: web::Path<Uuid>,
149 body: web::Json<EndAgSessionDto>,
150) -> impl Responder {
151 let organization_id = match user.require_organization() {
152 Ok(id) => id,
153 Err(e) => {
154 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
155 }
156 };
157
158 match state
159 .ag_session_use_cases
160 .end_session(path.into_inner(), organization_id, body.into_inner())
161 .await
162 {
163 Ok(session) => HttpResponse::Ok().json(session),
164 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
165 }
166}
167
168#[put("/ag-sessions/{id}/cancel")]
170pub async fn cancel_ag_session(
171 state: web::Data<AppState>,
172 user: AuthenticatedUser,
173 path: web::Path<Uuid>,
174) -> impl Responder {
175 let organization_id = match user.require_organization() {
176 Ok(id) => id,
177 Err(e) => {
178 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
179 }
180 };
181
182 match state
183 .ag_session_use_cases
184 .cancel_session(path.into_inner(), organization_id)
185 .await
186 {
187 Ok(session) => HttpResponse::Ok().json(session),
188 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
189 }
190}
191
192#[post("/ag-sessions/{id}/join")]
194pub async fn record_remote_join(
195 state: web::Data<AppState>,
196 user: AuthenticatedUser,
197 path: web::Path<Uuid>,
198 body: web::Json<RecordRemoteJoinDto>,
199) -> impl Responder {
200 let organization_id = match user.require_organization() {
201 Ok(id) => id,
202 Err(e) => {
203 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
204 }
205 };
206
207 match state
208 .ag_session_use_cases
209 .record_remote_join(path.into_inner(), organization_id, body.into_inner())
210 .await
211 {
212 Ok(session) => HttpResponse::Ok().json(session),
213 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
214 }
215}
216
217#[get("/ag-sessions/{id}/quorum")]
219pub async fn get_combined_quorum(
220 state: web::Data<AppState>,
221 user: AuthenticatedUser,
222 path: web::Path<Uuid>,
223 query: web::Query<CombinedQuorumQuery>,
224) -> impl Responder {
225 let organization_id = match user.require_organization() {
226 Ok(id) => id,
227 Err(e) => {
228 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
229 }
230 };
231
232 match state
233 .ag_session_use_cases
234 .calculate_combined_quorum(
235 path.into_inner(),
236 organization_id,
237 query.physical_quotas,
238 query.total_building_quotas,
239 query.physical_owners_count,
240 query.total_owners_count,
241 )
242 .await
243 {
244 Ok(result) => HttpResponse::Ok().json(result),
245 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
246 }
247}
248
249#[delete("/ag-sessions/{id}")]
251pub async fn delete_ag_session(
252 state: web::Data<AppState>,
253 user: AuthenticatedUser,
254 path: web::Path<Uuid>,
255) -> impl Responder {
256 let organization_id = match user.require_organization() {
257 Ok(id) => id,
258 Err(e) => {
259 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
260 }
261 };
262
263 match state
264 .ag_session_use_cases
265 .delete_session(path.into_inner(), organization_id)
266 .await
267 {
268 Ok(()) => HttpResponse::NoContent().finish(),
269 Err(e) => {
270 if classification_erreurs::est_introuvable(&e) {
271 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
272 } else if classification_erreurs::est_interdit(&e) {
273 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
274 } else {
275 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
276 }
277 }
278 }
279}
280
281#[get("/ag-sessions/platform-stats")]
284pub async fn get_ag_session_platform_stats(
285 state: web::Data<AppState>,
286 user: AuthenticatedUser,
287) -> impl Responder {
288 if !user.role.eq_ignore_ascii_case("superadmin") {
294 return HttpResponse::Forbidden().json(serde_json::json!({
295 "error": "SuperAdmin only"
296 }));
297 }
298
299 match state.ag_session_use_cases.list_pending_sessions().await {
301 Ok(pending) => HttpResponse::Ok().json(serde_json::json!({
302 "pending_sessions_count": pending.len(),
303 "note": "Platform-wide statistics endpoint - detailed metrics coming soon"
304 })),
305 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
306 }
307}
308
309#[derive(serde::Deserialize)]
310pub struct CombinedQuorumQuery {
311 pub physical_quotas: Decimal,
314 pub total_building_quotas: Decimal,
315 #[serde(default)]
319 pub physical_owners_count: i32,
320 #[serde(default)]
321 pub total_owners_count: i32,
322}