1use crate::application::dto::{
2 BorrowObjectDto, CreateSharedObjectDto, DeleteSharedObjectDto, UpdateSharedObjectDto,
3};
4use crate::domain::entities::SharedObjectCategory;
5use crate::infrastructure::web::app_state::AppState;
6use crate::infrastructure::web::classification_erreurs;
7use crate::infrastructure::web::middleware::AuthenticatedUser;
8use actix_web::{delete, get, post, put, web, HttpResponse, Responder, ResponseError};
9use uuid::Uuid;
10
11#[post("/shared-objects")]
15pub async fn create_shared_object(
16 data: web::Data<AppState>,
17 auth: AuthenticatedUser,
18 request: web::Json<CreateSharedObjectDto>,
19) -> impl Responder {
20 let org_id = match auth.require_organization() {
21 Ok(id) => id,
22 Err(e) => {
23 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
24 }
25 };
26 match data
27 .shared_object_use_cases
28 .create_shared_object(auth.user_id, org_id, request.into_inner())
29 .await
30 {
31 Ok(object) => HttpResponse::Created().json(object),
32 Err(e) => {
33 if classification_erreurs::est_refus_owner_requis(&e) {
38 HttpResponse::Forbidden().json(serde_json::json!({
39 "error": e,
40 "kind": "owner_profile_required",
41 }))
42 } else if classification_erreurs::est_interdit(&e) {
43 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
44 } else if classification_erreurs::est_introuvable(&e) {
45 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
46 } else {
47 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
48 }
49 }
50 }
51}
52
53#[get("/shared-objects/{id}")]
57pub async fn get_shared_object(
58 data: web::Data<AppState>,
59 user: AuthenticatedUser,
60 id: web::Path<Uuid>,
61) -> impl Responder {
62 let identifiant = id.into_inner();
63
64 if let Err(err) =
69 crate::infrastructure::web::middleware::scope_guard::verify_shared_object_org_access(
70 &user,
71 identifiant,
72 &data.shared_object_use_cases,
73 &data.building_use_cases,
74 &data.acp_use_cases,
75 )
76 .await
77 {
78 return err.error_response();
79 }
80
81 match data
82 .shared_object_use_cases
83 .get_shared_object(identifiant)
84 .await
85 {
86 Ok(object) => HttpResponse::Ok().json(object),
87 Err(e) => {
88 if classification_erreurs::est_introuvable(&e) {
89 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
90 } else {
91 HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
92 }
93 }
94 }
95}
96
97#[get("/buildings/{building_id}/shared-objects")]
101pub async fn list_building_objects(
102 data: web::Data<AppState>,
103 building_id: web::Path<Uuid>,
104 user: AuthenticatedUser,
105) -> impl Responder {
106 if let Err(err) =
110 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
111 &user,
112 *building_id,
113 &data.building_use_cases,
114 &data.acp_use_cases,
115 )
116 .await
117 {
118 return err.error_response();
119 }
120
121 match data
122 .shared_object_use_cases
123 .list_building_objects(building_id.into_inner())
124 .await
125 {
126 Ok(objects) => HttpResponse::Ok().json(objects),
127 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
128 }
129}
130
131#[get("/buildings/{building_id}/shared-objects/available")]
135pub async fn list_available_objects(
136 data: web::Data<AppState>,
137 building_id: web::Path<Uuid>,
138 user: AuthenticatedUser,
139) -> impl Responder {
140 if let Err(err) =
144 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
145 &user,
146 *building_id,
147 &data.building_use_cases,
148 &data.acp_use_cases,
149 )
150 .await
151 {
152 return err.error_response();
153 }
154
155 match data
156 .shared_object_use_cases
157 .list_available_objects(building_id.into_inner())
158 .await
159 {
160 Ok(objects) => HttpResponse::Ok().json(objects),
161 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
162 }
163}
164
165#[get("/buildings/{building_id}/shared-objects/borrowed")]
169pub async fn list_borrowed_objects(
170 data: web::Data<AppState>,
171 building_id: web::Path<Uuid>,
172 user: AuthenticatedUser,
173) -> impl Responder {
174 if let Err(err) =
178 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
179 &user,
180 *building_id,
181 &data.building_use_cases,
182 &data.acp_use_cases,
183 )
184 .await
185 {
186 return err.error_response();
187 }
188
189 match data
190 .shared_object_use_cases
191 .list_borrowed_objects(building_id.into_inner())
192 .await
193 {
194 Ok(objects) => HttpResponse::Ok().json(objects),
195 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
196 }
197}
198
199#[get("/buildings/{building_id}/shared-objects/overdue")]
203pub async fn list_overdue_objects(
204 data: web::Data<AppState>,
205 building_id: web::Path<Uuid>,
206 user: AuthenticatedUser,
207) -> impl Responder {
208 if let Err(err) =
212 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
213 &user,
214 *building_id,
215 &data.building_use_cases,
216 &data.acp_use_cases,
217 )
218 .await
219 {
220 return err.error_response();
221 }
222
223 match data
224 .shared_object_use_cases
225 .list_overdue_objects(building_id.into_inner())
226 .await
227 {
228 Ok(objects) => HttpResponse::Ok().json(objects),
229 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
230 }
231}
232
233#[get("/buildings/{building_id}/shared-objects/free")]
237pub async fn list_free_objects(
238 data: web::Data<AppState>,
239 building_id: web::Path<Uuid>,
240 user: AuthenticatedUser,
241) -> impl Responder {
242 if let Err(err) =
246 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
247 &user,
248 *building_id,
249 &data.building_use_cases,
250 &data.acp_use_cases,
251 )
252 .await
253 {
254 return err.error_response();
255 }
256
257 match data
258 .shared_object_use_cases
259 .list_free_objects(building_id.into_inner())
260 .await
261 {
262 Ok(objects) => HttpResponse::Ok().json(objects),
263 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
264 }
265}
266
267#[get("/buildings/{building_id}/shared-objects/category/{category}")]
271pub async fn list_objects_by_category(
272 data: web::Data<AppState>,
273 path: web::Path<(Uuid, String)>,
274 user: AuthenticatedUser,
275) -> impl Responder {
276 let (building_id, category_str) = path.into_inner();
277 if let Err(err) =
281 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
282 &user,
283 building_id,
284 &data.building_use_cases,
285 &data.acp_use_cases,
286 )
287 .await
288 {
289 return err.error_response();
290 }
291
292 let category = match serde_json::from_str::<SharedObjectCategory>(&format!(
294 "\"{}\"",
295 category_str
296 )) {
297 Ok(c) => c,
298 Err(_) => {
299 return HttpResponse::BadRequest().json(serde_json::json!({
300 "error": format!("Invalid shared object category: {}. Valid categories: Tools, Books, Electronics, Sports, Gardening, Kitchen, Baby, Other", category_str)
301 }))
302 }
303 };
304
305 match data
306 .shared_object_use_cases
307 .list_objects_by_category(building_id, category)
308 .await
309 {
310 Ok(objects) => HttpResponse::Ok().json(objects),
311 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
312 }
313}
314
315#[get("/owners/{owner_id}/shared-objects")]
319pub async fn list_owner_objects(
320 data: web::Data<AppState>,
321 owner_id: web::Path<Uuid>,
322 user: AuthenticatedUser,
323) -> impl Responder {
324 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
328 &user,
329 *owner_id,
330 &data.owner_use_cases,
331 )
332 .await
333 {
334 return err.error_response();
335 }
336
337 match data
338 .shared_object_use_cases
339 .list_owner_objects(owner_id.into_inner())
340 .await
341 {
342 Ok(objects) => HttpResponse::Ok().json(objects),
343 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
344 }
345}
346
347#[get("/shared-objects/my-borrowed")]
351pub async fn list_my_borrowed_objects(
352 data: web::Data<AppState>,
353 auth: AuthenticatedUser,
354) -> impl Responder {
355 let org_id = match auth.require_organization() {
356 Ok(id) => id,
357 Err(e) => {
358 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
359 }
360 };
361 match data
362 .shared_object_use_cases
363 .list_user_borrowed_objects(auth.user_id, org_id)
364 .await
365 {
366 Ok(objects) => HttpResponse::Ok().json(objects),
367 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
368 }
369}
370
371#[put("/shared-objects/{id}")]
375pub async fn update_shared_object(
376 data: web::Data<AppState>,
377 auth: AuthenticatedUser,
378 id: web::Path<Uuid>,
379 request: web::Json<UpdateSharedObjectDto>,
380) -> impl Responder {
381 let org_id = match auth.require_organization() {
382 Ok(id) => id,
383 Err(e) => {
384 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
385 }
386 };
387 match data
388 .shared_object_use_cases
389 .update_shared_object(id.into_inner(), auth.user_id, org_id, request.into_inner())
390 .await
391 {
392 Ok(object) => HttpResponse::Ok().json(object),
393 Err(e) => {
394 if classification_erreurs::est_interdit(&e) {
395 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
396 } else if classification_erreurs::est_introuvable(&e) {
397 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
398 } else {
399 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
400 }
401 }
402 }
403}
404
405#[post("/shared-objects/{id}/mark-available")]
409pub async fn mark_object_available(
410 data: web::Data<AppState>,
411 auth: AuthenticatedUser,
412 id: web::Path<Uuid>,
413) -> impl Responder {
414 let org_id = match auth.require_organization() {
415 Ok(id) => id,
416 Err(e) => {
417 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
418 }
419 };
420 match data
421 .shared_object_use_cases
422 .mark_object_available(id.into_inner(), auth.user_id, org_id)
423 .await
424 {
425 Ok(object) => HttpResponse::Ok().json(object),
426 Err(e) => {
427 if classification_erreurs::est_interdit(&e) {
428 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
429 } else if classification_erreurs::est_introuvable(&e) {
430 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
431 } else {
432 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
433 }
434 }
435 }
436}
437
438#[post("/shared-objects/{id}/mark-unavailable")]
442pub async fn mark_object_unavailable(
443 data: web::Data<AppState>,
444 auth: AuthenticatedUser,
445 id: web::Path<Uuid>,
446) -> impl Responder {
447 let org_id = match auth.require_organization() {
448 Ok(id) => id,
449 Err(e) => {
450 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
451 }
452 };
453 match data
454 .shared_object_use_cases
455 .mark_object_unavailable(id.into_inner(), auth.user_id, org_id)
456 .await
457 {
458 Ok(object) => HttpResponse::Ok().json(object),
459 Err(e) => {
460 if classification_erreurs::est_interdit(&e) {
461 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
462 } else if classification_erreurs::est_introuvable(&e) {
463 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
464 } else {
465 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
466 }
467 }
468 }
469}
470
471#[post("/shared-objects/{id}/borrow")]
475pub async fn borrow_object(
476 data: web::Data<AppState>,
477 auth: AuthenticatedUser,
478 id: web::Path<Uuid>,
479 request: web::Json<BorrowObjectDto>,
480) -> impl Responder {
481 let org_id = match auth.require_organization() {
482 Ok(id) => id,
483 Err(e) => {
484 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
485 }
486 };
487 match data
488 .shared_object_use_cases
489 .borrow_object(id.into_inner(), auth.user_id, org_id, request.into_inner())
490 .await
491 {
492 Ok(object) => HttpResponse::Ok().json(object),
493 Err(e) => {
494 if e.contains("Owner cannot borrow") {
495 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
496 } else if classification_erreurs::est_introuvable(&e) {
497 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
498 } else {
499 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
500 }
501 }
502 }
503}
504
505#[post("/shared-objects/{id}/return")]
509pub async fn return_object(
510 data: web::Data<AppState>,
511 auth: AuthenticatedUser,
512 id: web::Path<Uuid>,
513) -> impl Responder {
514 let org_id = match auth.require_organization() {
515 Ok(id) => id,
516 Err(e) => {
517 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
518 }
519 };
520 match data
521 .shared_object_use_cases
522 .return_object(id.into_inner(), auth.user_id, org_id)
523 .await
524 {
525 Ok(object) => HttpResponse::Ok().json(object),
526 Err(e) => {
527 if e.contains("Only borrower can return") {
528 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
529 } else if classification_erreurs::est_introuvable(&e) {
530 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
531 } else {
532 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
533 }
534 }
535 }
536}
537
538#[delete("/shared-objects/{id}")]
542pub async fn delete_shared_object(
543 data: web::Data<AppState>,
544 auth: AuthenticatedUser,
545 id: web::Path<Uuid>,
546 body: Option<web::Json<DeleteSharedObjectDto>>,
553) -> impl Responder {
554 let org_id = match auth.require_organization() {
555 Ok(id) => id,
556 Err(e) => {
557 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
558 }
559 };
560 let reason = body.and_then(|b| b.into_inner().reason);
561 match data
562 .shared_object_use_cases
563 .delete_shared_object(id.into_inner(), auth.user_id, org_id, &auth.role, reason)
564 .await
565 {
566 Ok(_) => HttpResponse::NoContent().finish(),
567 Err(e) => {
568 if classification_erreurs::est_motif_manquant(&e) {
569 HttpResponse::UnprocessableEntity().json(serde_json::json!({"error": e}))
570 } else if classification_erreurs::est_refus_owner_requis(&e)
571 || classification_erreurs::est_interdit(&e)
572 {
573 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
574 } else if classification_erreurs::est_introuvable(&e) {
575 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
576 } else {
577 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
578 }
579 }
580 }
581}
582
583#[get("/buildings/{building_id}/shared-objects/statistics")]
587pub async fn get_object_statistics(
588 data: web::Data<AppState>,
589 building_id: web::Path<Uuid>,
590 user: AuthenticatedUser,
591) -> impl Responder {
592 if let Err(err) =
596 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
597 &user,
598 *building_id,
599 &data.building_use_cases,
600 &data.acp_use_cases,
601 )
602 .await
603 {
604 return err.error_response();
605 }
606
607 match data
608 .shared_object_use_cases
609 .get_object_statistics(building_id.into_inner())
610 .await
611 {
612 Ok(stats) => HttpResponse::Ok().json(stats),
613 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
614 }
615}