1use crate::application::dto::{
2 ArchiveNoticeDto, CreateNoticeDto, SetExpirationDto, UpdateNoticeDto,
3};
4use crate::domain::entities::{NoticeCategory, NoticeStatus, NoticeType};
5use crate::infrastructure::web::app_state::AppState;
6use crate::infrastructure::web::classification_erreurs;
7use crate::infrastructure::web::middleware::scope_guard::verify_notice_org_access;
8use crate::infrastructure::web::middleware::AuthenticatedUser;
9use actix_web::{delete, get, post, put, web, HttpResponse, Responder, ResponseError};
10use uuid::Uuid;
11
12#[post("/notices")]
16pub async fn create_notice(
17 data: web::Data<AppState>,
18 auth: AuthenticatedUser,
19 request: web::Json<CreateNoticeDto>,
20) -> impl Responder {
21 let org_id = match auth.require_organization() {
22 Ok(id) => id,
23 Err(e) => {
24 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
25 }
26 };
27 match data
28 .notice_use_cases
29 .create_notice(auth.user_id, org_id, request.into_inner())
30 .await
31 {
32 Ok(notice) => HttpResponse::Created().json(notice),
33 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
34 }
35}
36
37#[get("/notices/{id}")]
41pub async fn get_notice(
42 data: web::Data<AppState>,
43 user: AuthenticatedUser,
44 id: web::Path<Uuid>,
45) -> impl Responder {
46 let identifiant = id.into_inner();
47
48 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_notice_org_access(
53 &user,
54 identifiant,
55 &data.notice_use_cases,
56 &data.building_use_cases,
57 &data.acp_use_cases,
58 )
59 .await
60 {
61 return err.error_response();
62 }
63
64 match data.notice_use_cases.get_notice(identifiant).await {
65 Ok(notice) => HttpResponse::Ok().json(notice),
66 Err(e) => {
67 if classification_erreurs::est_introuvable(&e) {
68 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
69 } else {
70 HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
71 }
72 }
73 }
74}
75
76#[get("/buildings/{building_id}/notices")]
80pub async fn list_building_notices(
81 data: web::Data<AppState>,
82 building_id: web::Path<Uuid>,
83 user: AuthenticatedUser,
84) -> impl Responder {
85 if let Err(err) =
89 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
90 &user,
91 *building_id,
92 &data.building_use_cases,
93 &data.acp_use_cases,
94 )
95 .await
96 {
97 return err.error_response();
98 }
99
100 match data
101 .notice_use_cases
102 .list_building_notices(building_id.into_inner())
103 .await
104 {
105 Ok(notices) => HttpResponse::Ok().json(notices),
106 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
107 }
108}
109
110#[get("/buildings/{building_id}/notices/published")]
114pub async fn list_published_notices(
115 data: web::Data<AppState>,
116 building_id: web::Path<Uuid>,
117 user: AuthenticatedUser,
118) -> impl Responder {
119 if let Err(err) =
123 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
124 &user,
125 *building_id,
126 &data.building_use_cases,
127 &data.acp_use_cases,
128 )
129 .await
130 {
131 return err.error_response();
132 }
133
134 match data
135 .notice_use_cases
136 .list_published_notices(building_id.into_inner())
137 .await
138 {
139 Ok(notices) => HttpResponse::Ok().json(notices),
140 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
141 }
142}
143
144#[get("/buildings/{building_id}/notices/pinned")]
148pub async fn list_pinned_notices(
149 data: web::Data<AppState>,
150 building_id: web::Path<Uuid>,
151 user: AuthenticatedUser,
152) -> impl Responder {
153 if let Err(err) =
157 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
158 &user,
159 *building_id,
160 &data.building_use_cases,
161 &data.acp_use_cases,
162 )
163 .await
164 {
165 return err.error_response();
166 }
167
168 match data
169 .notice_use_cases
170 .list_pinned_notices(building_id.into_inner())
171 .await
172 {
173 Ok(notices) => HttpResponse::Ok().json(notices),
174 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
175 }
176}
177
178#[get("/buildings/{building_id}/notices/type/{notice_type}")]
182pub async fn list_notices_by_type(
183 data: web::Data<AppState>,
184 path: web::Path<(Uuid, String)>,
185 user: AuthenticatedUser,
186) -> impl Responder {
187 let (building_id, notice_type_str) = path.into_inner();
188 if let Err(err) =
192 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
193 &user,
194 building_id,
195 &data.building_use_cases,
196 &data.acp_use_cases,
197 )
198 .await
199 {
200 return err.error_response();
201 }
202
203 let notice_type = match serde_json::from_str::<NoticeType>(&format!("\"{}\"", notice_type_str))
205 {
206 Ok(nt) => nt,
207 Err(_) => {
208 return HttpResponse::BadRequest().json(serde_json::json!({
209 "error": format!("Invalid notice type: {}. Valid types: Announcement, Event, LostAndFound, ClassifiedAd", notice_type_str)
210 }))
211 }
212 };
213
214 match data
215 .notice_use_cases
216 .list_notices_by_type(building_id, notice_type)
217 .await
218 {
219 Ok(notices) => HttpResponse::Ok().json(notices),
220 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
221 }
222}
223
224#[get("/buildings/{building_id}/notices/category/{category}")]
228pub async fn list_notices_by_category(
229 data: web::Data<AppState>,
230 path: web::Path<(Uuid, String)>,
231 user: AuthenticatedUser,
232) -> impl Responder {
233 let (building_id, category_str) = path.into_inner();
234 if let Err(err) =
238 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
239 &user,
240 building_id,
241 &data.building_use_cases,
242 &data.acp_use_cases,
243 )
244 .await
245 {
246 return err.error_response();
247 }
248
249 let category = match serde_json::from_str::<NoticeCategory>(&format!("\"{}\"", category_str)) {
251 Ok(c) => c,
252 Err(_) => {
253 return HttpResponse::BadRequest().json(serde_json::json!({
254 "error": format!("Invalid category: {}. Valid categories: General, Maintenance, Social, Security, Environment, Parking, Other", category_str)
255 }))
256 }
257 };
258
259 match data
260 .notice_use_cases
261 .list_notices_by_category(building_id, category)
262 .await
263 {
264 Ok(notices) => HttpResponse::Ok().json(notices),
265 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
266 }
267}
268
269#[get("/buildings/{building_id}/notices/status/{status}")]
273pub async fn list_notices_by_status(
274 data: web::Data<AppState>,
275 path: web::Path<(Uuid, String)>,
276 user: AuthenticatedUser,
277) -> impl Responder {
278 let (building_id, status_str) = path.into_inner();
279 if let Err(err) =
283 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
284 &user,
285 building_id,
286 &data.building_use_cases,
287 &data.acp_use_cases,
288 )
289 .await
290 {
291 return err.error_response();
292 }
293
294 let status = match serde_json::from_str::<NoticeStatus>(&format!("\"{}\"", status_str)) {
296 Ok(s) => s,
297 Err(_) => {
298 return HttpResponse::BadRequest().json(serde_json::json!({
299 "error": format!("Invalid status: {}. Valid statuses: Draft, Published, Archived, Expired", status_str)
300 }))
301 }
302 };
303
304 match data
305 .notice_use_cases
306 .list_notices_by_status(building_id, status)
307 .await
308 {
309 Ok(notices) => HttpResponse::Ok().json(notices),
310 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
311 }
312}
313
314#[get("/owners/{author_id}/notices")]
318pub async fn list_author_notices(
319 data: web::Data<AppState>,
320 author_id: web::Path<Uuid>,
321 user: AuthenticatedUser,
322) -> impl Responder {
323 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
327 &user,
328 *author_id,
329 &data.owner_use_cases,
330 )
331 .await
332 {
333 return err.error_response();
334 }
335
336 match data
337 .notice_use_cases
338 .list_author_notices(author_id.into_inner())
339 .await
340 {
341 Ok(notices) => HttpResponse::Ok().json(notices),
342 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
343 }
344}
345
346#[put("/notices/{id}")]
350pub async fn update_notice(
351 data: web::Data<AppState>,
352 auth: AuthenticatedUser,
353 id: web::Path<Uuid>,
354 request: web::Json<UpdateNoticeDto>,
355) -> impl Responder {
356 let org_id = match auth.require_organization() {
357 Ok(id) => id,
358 Err(e) => {
359 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
360 }
361 };
362 match data
363 .notice_use_cases
364 .update_notice(id.into_inner(), auth.user_id, org_id, request.into_inner())
365 .await
366 {
367 Ok(notice) => HttpResponse::Ok().json(notice),
368 Err(e) => {
369 if classification_erreurs::est_interdit(&e) {
370 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
371 } else if classification_erreurs::est_introuvable(&e) {
372 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
373 } else {
374 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
375 }
376 }
377 }
378}
379
380#[post("/notices/{id}/publish")]
384pub async fn publish_notice(
385 data: web::Data<AppState>,
386 auth: AuthenticatedUser,
387 id: web::Path<Uuid>,
388) -> impl Responder {
389 let org_id = match auth.require_organization() {
390 Ok(id) => id,
391 Err(e) => {
392 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
393 }
394 };
395 match data
396 .notice_use_cases
397 .publish_notice(id.into_inner(), auth.user_id, org_id)
398 .await
399 {
400 Ok(notice) => HttpResponse::Ok().json(notice),
401 Err(e) => {
402 if classification_erreurs::est_interdit(&e) {
403 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
404 } else if classification_erreurs::est_introuvable(&e) {
405 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
406 } else {
407 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
408 }
409 }
410 }
411}
412
413#[post("/notices/{id}/archive")]
417pub async fn archive_notice(
418 data: web::Data<AppState>,
419 auth: AuthenticatedUser,
420 id: web::Path<Uuid>,
421 body: Option<web::Json<ArchiveNoticeDto>>,
428) -> impl Responder {
429 let org_id = match auth.require_organization() {
430 Ok(id) => id,
431 Err(e) => {
432 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
433 }
434 };
435 let reason = body.and_then(|b| b.into_inner().reason);
436 match data
437 .notice_use_cases
438 .archive_notice(id.into_inner(), auth.user_id, org_id, &auth.role, reason)
439 .await
440 {
441 Ok(notice) => HttpResponse::Ok().json(notice),
442 Err(e) => {
443 if classification_erreurs::est_motif_manquant(&e) {
444 HttpResponse::UnprocessableEntity().json(serde_json::json!({"error": e}))
445 } else if classification_erreurs::est_interdit(&e) {
446 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
447 } else if classification_erreurs::est_introuvable(&e) {
448 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
449 } else {
450 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
451 }
452 }
453 }
454}
455
456#[post("/notices/{id}/pin")]
460pub async fn pin_notice(
461 data: web::Data<AppState>,
462 auth: AuthenticatedUser,
463 id: web::Path<Uuid>,
464) -> impl Responder {
465 if let Err(err) = verify_notice_org_access(
470 &auth,
471 *id,
472 &data.notice_use_cases,
473 &data.building_use_cases,
474 &data.acp_use_cases,
475 )
476 .await
477 {
478 return err.error_response();
479 }
480
481 match data
482 .notice_use_cases
483 .pin_notice(id.into_inner(), &auth.role)
484 .await
485 {
486 Ok(notice) => HttpResponse::Ok().json(notice),
487 Err(e) => {
488 if classification_erreurs::est_interdit(&e) {
489 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
490 } else if classification_erreurs::est_introuvable(&e) {
491 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
492 } else {
493 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
494 }
495 }
496 }
497}
498
499#[post("/notices/{id}/unpin")]
503pub async fn unpin_notice(
504 data: web::Data<AppState>,
505 auth: AuthenticatedUser,
506 id: web::Path<Uuid>,
507) -> impl Responder {
508 if let Err(err) = verify_notice_org_access(
513 &auth,
514 *id,
515 &data.notice_use_cases,
516 &data.building_use_cases,
517 &data.acp_use_cases,
518 )
519 .await
520 {
521 return err.error_response();
522 }
523
524 match data
525 .notice_use_cases
526 .unpin_notice(id.into_inner(), &auth.role)
527 .await
528 {
529 Ok(notice) => HttpResponse::Ok().json(notice),
530 Err(e) => {
531 if classification_erreurs::est_interdit(&e) {
532 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
533 } else if classification_erreurs::est_introuvable(&e) {
534 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
535 } else {
536 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
537 }
538 }
539 }
540}
541
542#[put("/notices/{id}/expiration")]
546pub async fn set_expiration(
547 data: web::Data<AppState>,
548 auth: AuthenticatedUser,
549 id: web::Path<Uuid>,
550 request: web::Json<SetExpirationDto>,
551) -> impl Responder {
552 let org_id = match auth.require_organization() {
553 Ok(id) => id,
554 Err(e) => {
555 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
556 }
557 };
558 match data
559 .notice_use_cases
560 .set_expiration(id.into_inner(), auth.user_id, org_id, request.into_inner())
561 .await
562 {
563 Ok(notice) => HttpResponse::Ok().json(notice),
564 Err(e) => {
565 if classification_erreurs::est_interdit(&e) {
566 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
567 } else if classification_erreurs::est_introuvable(&e) {
568 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
569 } else {
570 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
571 }
572 }
573 }
574}
575
576#[delete("/notices/{id}")]
580pub async fn delete_notice(
581 data: web::Data<AppState>,
582 auth: AuthenticatedUser,
583 id: web::Path<Uuid>,
584) -> impl Responder {
585 let org_id = match auth.require_organization() {
586 Ok(id) => id,
587 Err(e) => {
588 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
589 }
590 };
591 match data
592 .notice_use_cases
593 .delete_notice(id.into_inner(), auth.user_id, org_id)
594 .await
595 {
596 Ok(_) => HttpResponse::NoContent().finish(),
597 Err(e) => {
598 if classification_erreurs::est_interdit(&e) {
599 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
600 } else if classification_erreurs::est_introuvable(&e) {
601 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
602 } else {
603 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
604 }
605 }
606 }
607}
608
609#[get("/buildings/{building_id}/notices/statistics")]
613pub async fn get_notice_statistics(
614 data: web::Data<AppState>,
615 building_id: web::Path<Uuid>,
616 user: AuthenticatedUser,
617) -> impl Responder {
618 if let Err(err) =
622 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
623 &user,
624 *building_id,
625 &data.building_use_cases,
626 &data.acp_use_cases,
627 )
628 .await
629 {
630 return err.error_response();
631 }
632
633 match data
634 .notice_use_cases
635 .get_statistics(building_id.into_inner())
636 .await
637 {
638 Ok(stats) => HttpResponse::Ok().json(stats),
639 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
640 }
641}