koprogo_api/infrastructure/web/handlers/
notice_handlers.rs1use crate::application::dto::{CreateNoticeDto, SetExpirationDto, UpdateNoticeDto};
2use crate::domain::entities::{NoticeCategory, NoticeStatus, NoticeType};
3use crate::infrastructure::web::app_state::AppState;
4use crate::infrastructure::web::middleware::AuthenticatedUser;
5use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
6use uuid::Uuid;
7
8#[post("/notices")]
12pub async fn create_notice(
13 data: web::Data<AppState>,
14 auth: AuthenticatedUser,
15 request: web::Json<CreateNoticeDto>,
16) -> impl Responder {
17 let org_id = match auth.require_organization() {
18 Ok(id) => id,
19 Err(e) => {
20 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
21 }
22 };
23 match data
24 .notice_use_cases
25 .create_notice(auth.user_id, org_id, request.into_inner())
26 .await
27 {
28 Ok(notice) => HttpResponse::Created().json(notice),
29 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
30 }
31}
32
33#[get("/notices/{id}")]
37pub async fn get_notice(data: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
38 match data.notice_use_cases.get_notice(id.into_inner()).await {
39 Ok(notice) => HttpResponse::Ok().json(notice),
40 Err(e) => {
41 if e.contains("not found") {
42 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
43 } else {
44 HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
45 }
46 }
47 }
48}
49
50#[get("/buildings/{building_id}/notices")]
54pub async fn list_building_notices(
55 data: web::Data<AppState>,
56 building_id: web::Path<Uuid>,
57) -> impl Responder {
58 match data
59 .notice_use_cases
60 .list_building_notices(building_id.into_inner())
61 .await
62 {
63 Ok(notices) => HttpResponse::Ok().json(notices),
64 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
65 }
66}
67
68#[get("/buildings/{building_id}/notices/published")]
72pub async fn list_published_notices(
73 data: web::Data<AppState>,
74 building_id: web::Path<Uuid>,
75) -> impl Responder {
76 match data
77 .notice_use_cases
78 .list_published_notices(building_id.into_inner())
79 .await
80 {
81 Ok(notices) => HttpResponse::Ok().json(notices),
82 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
83 }
84}
85
86#[get("/buildings/{building_id}/notices/pinned")]
90pub async fn list_pinned_notices(
91 data: web::Data<AppState>,
92 building_id: web::Path<Uuid>,
93) -> impl Responder {
94 match data
95 .notice_use_cases
96 .list_pinned_notices(building_id.into_inner())
97 .await
98 {
99 Ok(notices) => HttpResponse::Ok().json(notices),
100 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
101 }
102}
103
104#[get("/buildings/{building_id}/notices/type/{notice_type}")]
108pub async fn list_notices_by_type(
109 data: web::Data<AppState>,
110 path: web::Path<(Uuid, String)>,
111) -> impl Responder {
112 let (building_id, notice_type_str) = path.into_inner();
113
114 let notice_type = match serde_json::from_str::<NoticeType>(&format!("\"{}\"", notice_type_str))
116 {
117 Ok(nt) => nt,
118 Err(_) => {
119 return HttpResponse::BadRequest().json(serde_json::json!({
120 "error": format!("Invalid notice type: {}. Valid types: Announcement, Event, LostAndFound, ClassifiedAd", notice_type_str)
121 }))
122 }
123 };
124
125 match data
126 .notice_use_cases
127 .list_notices_by_type(building_id, notice_type)
128 .await
129 {
130 Ok(notices) => HttpResponse::Ok().json(notices),
131 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
132 }
133}
134
135#[get("/buildings/{building_id}/notices/category/{category}")]
139pub async fn list_notices_by_category(
140 data: web::Data<AppState>,
141 path: web::Path<(Uuid, String)>,
142) -> impl Responder {
143 let (building_id, category_str) = path.into_inner();
144
145 let category = match serde_json::from_str::<NoticeCategory>(&format!("\"{}\"", category_str)) {
147 Ok(c) => c,
148 Err(_) => {
149 return HttpResponse::BadRequest().json(serde_json::json!({
150 "error": format!("Invalid category: {}. Valid categories: General, Maintenance, Social, Security, Environment, Parking, Other", category_str)
151 }))
152 }
153 };
154
155 match data
156 .notice_use_cases
157 .list_notices_by_category(building_id, category)
158 .await
159 {
160 Ok(notices) => HttpResponse::Ok().json(notices),
161 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
162 }
163}
164
165#[get("/buildings/{building_id}/notices/status/{status}")]
169pub async fn list_notices_by_status(
170 data: web::Data<AppState>,
171 path: web::Path<(Uuid, String)>,
172) -> impl Responder {
173 let (building_id, status_str) = path.into_inner();
174
175 let status = match serde_json::from_str::<NoticeStatus>(&format!("\"{}\"", status_str)) {
177 Ok(s) => s,
178 Err(_) => {
179 return HttpResponse::BadRequest().json(serde_json::json!({
180 "error": format!("Invalid status: {}. Valid statuses: Draft, Published, Archived, Expired", status_str)
181 }))
182 }
183 };
184
185 match data
186 .notice_use_cases
187 .list_notices_by_status(building_id, status)
188 .await
189 {
190 Ok(notices) => HttpResponse::Ok().json(notices),
191 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
192 }
193}
194
195#[get("/owners/{author_id}/notices")]
199pub async fn list_author_notices(
200 data: web::Data<AppState>,
201 author_id: web::Path<Uuid>,
202) -> impl Responder {
203 match data
204 .notice_use_cases
205 .list_author_notices(author_id.into_inner())
206 .await
207 {
208 Ok(notices) => HttpResponse::Ok().json(notices),
209 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
210 }
211}
212
213#[put("/notices/{id}")]
217pub async fn update_notice(
218 data: web::Data<AppState>,
219 auth: AuthenticatedUser,
220 id: web::Path<Uuid>,
221 request: web::Json<UpdateNoticeDto>,
222) -> impl Responder {
223 let org_id = match auth.require_organization() {
224 Ok(id) => id,
225 Err(e) => {
226 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
227 }
228 };
229 match data
230 .notice_use_cases
231 .update_notice(id.into_inner(), auth.user_id, org_id, request.into_inner())
232 .await
233 {
234 Ok(notice) => HttpResponse::Ok().json(notice),
235 Err(e) => {
236 if e.contains("Unauthorized") {
237 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
238 } else if e.contains("not found") {
239 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
240 } else {
241 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
242 }
243 }
244 }
245}
246
247#[post("/notices/{id}/publish")]
251pub async fn publish_notice(
252 data: web::Data<AppState>,
253 auth: AuthenticatedUser,
254 id: web::Path<Uuid>,
255) -> impl Responder {
256 let org_id = match auth.require_organization() {
257 Ok(id) => id,
258 Err(e) => {
259 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
260 }
261 };
262 match data
263 .notice_use_cases
264 .publish_notice(id.into_inner(), auth.user_id, org_id)
265 .await
266 {
267 Ok(notice) => HttpResponse::Ok().json(notice),
268 Err(e) => {
269 if e.contains("Unauthorized") {
270 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
271 } else if e.contains("not found") {
272 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
273 } else {
274 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
275 }
276 }
277 }
278}
279
280#[post("/notices/{id}/archive")]
284pub async fn archive_notice(
285 data: web::Data<AppState>,
286 auth: AuthenticatedUser,
287 id: web::Path<Uuid>,
288) -> impl Responder {
289 let org_id = match auth.require_organization() {
290 Ok(id) => id,
291 Err(e) => {
292 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
293 }
294 };
295 match data
296 .notice_use_cases
297 .archive_notice(id.into_inner(), auth.user_id, org_id, &auth.role)
298 .await
299 {
300 Ok(notice) => HttpResponse::Ok().json(notice),
301 Err(e) => {
302 if e.contains("Unauthorized") {
303 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
304 } else if e.contains("not found") {
305 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
306 } else {
307 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
308 }
309 }
310 }
311}
312
313#[post("/notices/{id}/pin")]
317pub async fn pin_notice(
318 data: web::Data<AppState>,
319 auth: AuthenticatedUser,
320 id: web::Path<Uuid>,
321) -> impl Responder {
322 match data
323 .notice_use_cases
324 .pin_notice(id.into_inner(), &auth.role)
325 .await
326 {
327 Ok(notice) => HttpResponse::Ok().json(notice),
328 Err(e) => {
329 if e.contains("Unauthorized") {
330 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
331 } else if e.contains("not found") {
332 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
333 } else {
334 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
335 }
336 }
337 }
338}
339
340#[post("/notices/{id}/unpin")]
344pub async fn unpin_notice(
345 data: web::Data<AppState>,
346 auth: AuthenticatedUser,
347 id: web::Path<Uuid>,
348) -> impl Responder {
349 match data
350 .notice_use_cases
351 .unpin_notice(id.into_inner(), &auth.role)
352 .await
353 {
354 Ok(notice) => HttpResponse::Ok().json(notice),
355 Err(e) => {
356 if e.contains("Unauthorized") {
357 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
358 } else if e.contains("not found") {
359 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
360 } else {
361 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
362 }
363 }
364 }
365}
366
367#[put("/notices/{id}/expiration")]
371pub async fn set_expiration(
372 data: web::Data<AppState>,
373 auth: AuthenticatedUser,
374 id: web::Path<Uuid>,
375 request: web::Json<SetExpirationDto>,
376) -> impl Responder {
377 let org_id = match auth.require_organization() {
378 Ok(id) => id,
379 Err(e) => {
380 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
381 }
382 };
383 match data
384 .notice_use_cases
385 .set_expiration(id.into_inner(), auth.user_id, org_id, request.into_inner())
386 .await
387 {
388 Ok(notice) => HttpResponse::Ok().json(notice),
389 Err(e) => {
390 if e.contains("Unauthorized") {
391 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
392 } else if e.contains("not found") {
393 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
394 } else {
395 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
396 }
397 }
398 }
399}
400
401#[delete("/notices/{id}")]
405pub async fn delete_notice(
406 data: web::Data<AppState>,
407 auth: AuthenticatedUser,
408 id: web::Path<Uuid>,
409) -> impl Responder {
410 let org_id = match auth.require_organization() {
411 Ok(id) => id,
412 Err(e) => {
413 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
414 }
415 };
416 match data
417 .notice_use_cases
418 .delete_notice(id.into_inner(), auth.user_id, org_id)
419 .await
420 {
421 Ok(_) => HttpResponse::NoContent().finish(),
422 Err(e) => {
423 if e.contains("Unauthorized") {
424 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
425 } else if e.contains("not found") {
426 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
427 } else {
428 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
429 }
430 }
431 }
432}
433
434#[get("/buildings/{building_id}/notices/statistics")]
438pub async fn get_notice_statistics(
439 data: web::Data<AppState>,
440 building_id: web::Path<Uuid>,
441) -> impl Responder {
442 match data
443 .notice_use_cases
444 .get_statistics(building_id.into_inner())
445 .await
446 {
447 Ok(stats) => HttpResponse::Ok().json(stats),
448 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
449 }
450}