1use crate::application::dto::{
2 CancelExchangeDto, CompleteExchangeDto, CreateLocalExchangeDto, DeleteExchangeDto,
3 RateExchangeDto, RequestExchangeDto,
4};
5use crate::domain::entities::ExchangeType;
6use crate::infrastructure::web::classification_erreurs;
7use crate::infrastructure::web::middleware::scope_guard::{
8 verify_building_org_access, verify_exchange_org_access, verify_owner_org_access,
9};
10use crate::infrastructure::web::{AppState, AuthenticatedUser};
11use actix_web::{delete, get, post, put, web, HttpResponse, Responder, ResponseError};
12use uuid::Uuid;
13
14#[post("/exchanges")]
17pub async fn create_exchange(
18 data: web::Data<AppState>,
19 auth: AuthenticatedUser,
20 request: web::Json<CreateLocalExchangeDto>,
21) -> impl Responder {
22 match data
23 .local_exchange_use_cases
24 .create_exchange(auth.user_id, request.into_inner())
25 .await
26 {
27 Ok(exchange) => HttpResponse::Created().json(exchange),
28 Err(e) => {
29 if classification_erreurs::est_refus_owner_requis(&e)
35 || classification_erreurs::est_interdit(&e)
36 {
37 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
38 } else {
39 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
40 }
41 }
42 }
43}
44
45#[get("/exchanges/{id}")]
48pub async fn get_exchange(
49 data: web::Data<AppState>,
50 auth: AuthenticatedUser,
51 id: web::Path<Uuid>,
52) -> impl Responder {
53 if let Err(err) = verify_exchange_org_access(
55 &auth,
56 *id,
57 &data.local_exchange_use_cases,
58 &data.building_use_cases,
59 &data.acp_use_cases,
60 )
61 .await
62 {
63 return err.error_response();
64 }
65
66 match data
67 .local_exchange_use_cases
68 .get_exchange(id.into_inner())
69 .await
70 {
71 Ok(exchange) => HttpResponse::Ok().json(exchange),
72 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
73 }
74}
75
76#[get("/buildings/{building_id}/exchanges")]
79pub async fn list_building_exchanges(
80 data: web::Data<AppState>,
81 auth: AuthenticatedUser,
82 building_id: web::Path<Uuid>,
83) -> impl Responder {
84 if let Err(err) = verify_building_org_access(
89 &auth,
90 *building_id,
91 &data.building_use_cases,
92 &data.acp_use_cases,
93 )
94 .await
95 {
96 return err.error_response();
97 }
98
99 match data
100 .local_exchange_use_cases
101 .list_building_exchanges(building_id.into_inner())
102 .await
103 {
104 Ok(exchanges) => HttpResponse::Ok().json(exchanges),
105 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
106 }
107}
108
109#[get("/buildings/{building_id}/exchanges/available")]
112pub async fn list_available_exchanges(
113 data: web::Data<AppState>,
114 auth: AuthenticatedUser,
115 building_id: web::Path<Uuid>,
116) -> impl Responder {
117 if let Err(err) = verify_building_org_access(
122 &auth,
123 *building_id,
124 &data.building_use_cases,
125 &data.acp_use_cases,
126 )
127 .await
128 {
129 return err.error_response();
130 }
131
132 match data
133 .local_exchange_use_cases
134 .list_available_exchanges(building_id.into_inner())
135 .await
136 {
137 Ok(exchanges) => HttpResponse::Ok().json(exchanges),
138 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
139 }
140}
141
142#[get("/owners/{owner_id}/exchanges")]
145pub async fn list_owner_exchanges(
146 data: web::Data<AppState>,
147 auth: AuthenticatedUser,
148 owner_id: web::Path<Uuid>,
149) -> impl Responder {
150 if let Err(err) = verify_owner_org_access(&auth, *owner_id, &data.owner_use_cases).await {
155 return err.error_response();
156 }
157
158 let owner_id = owner_id.into_inner();
159
160 let owner = match data.owner_use_cases.get_owner(owner_id).await {
163 Ok(Some(owner)) => owner,
164 Ok(None) => {
165 return HttpResponse::NotFound().json(serde_json::json!({
166 "error": format!("Owner not found: {}", owner_id)
167 }))
168 }
169 Err(e) => {
170 return HttpResponse::InternalServerError().json(serde_json::json!({
171 "error": format!("Failed to fetch owner: {}", e)
172 }))
173 }
174 };
175
176 let owner_user_id = owner
178 .user_id
179 .as_ref()
180 .and_then(|id| Uuid::parse_str(id).ok());
181 if owner_user_id != Some(auth.user_id) {
182 return HttpResponse::Forbidden().json(serde_json::json!({
183 "error": "You can only view your own exchanges"
184 }));
185 }
186
187 match data
188 .local_exchange_use_cases
189 .list_owner_exchanges(owner_id)
190 .await
191 {
192 Ok(exchanges) => HttpResponse::Ok().json(exchanges),
193 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
194 }
195}
196
197#[get("/buildings/{building_id}/exchanges/type/{exchange_type}")]
200pub async fn list_exchanges_by_type(
201 data: web::Data<AppState>,
202 auth: AuthenticatedUser,
203 path: web::Path<(Uuid, String)>,
204) -> impl Responder {
205 if let Err(err) =
210 verify_building_org_access(&auth, path.0, &data.building_use_cases, &data.acp_use_cases)
211 .await
212 {
213 return err.error_response();
214 }
215
216 let (building_id, exchange_type_str) = path.into_inner();
217
218 let exchange_type = match exchange_type_str.as_str() {
220 "Service" => ExchangeType::Service,
221 "ObjectLoan" => ExchangeType::ObjectLoan,
222 "SharedPurchase" => ExchangeType::SharedPurchase,
223 _ => {
224 return HttpResponse::BadRequest().json(serde_json::json!({
225 "error": "Invalid exchange type. Must be Service, ObjectLoan, or SharedPurchase"
226 }));
227 }
228 };
229
230 match data
231 .local_exchange_use_cases
232 .list_exchanges_by_type(building_id, exchange_type)
233 .await
234 {
235 Ok(exchanges) => HttpResponse::Ok().json(exchanges),
236 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
237 }
238}
239
240#[post("/exchanges/{id}/request")]
243pub async fn request_exchange(
244 data: web::Data<AppState>,
245 auth: AuthenticatedUser,
246 id: web::Path<Uuid>,
247 request: web::Json<RequestExchangeDto>,
248) -> impl Responder {
249 if let Err(err) = verify_exchange_org_access(
256 &auth,
257 *id,
258 &data.local_exchange_use_cases,
259 &data.building_use_cases,
260 &data.acp_use_cases,
261 )
262 .await
263 {
264 return err.error_response();
265 }
266
267 match data
268 .local_exchange_use_cases
269 .request_exchange(id.into_inner(), auth.user_id, request.into_inner())
270 .await
271 {
272 Ok(exchange) => HttpResponse::Ok().json(exchange),
273 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
274 }
275}
276
277#[post("/exchanges/{id}/start")]
281pub async fn start_exchange(
282 data: web::Data<AppState>,
283 auth: AuthenticatedUser,
284 id: web::Path<Uuid>,
285) -> impl Responder {
286 if let Err(err) = verify_exchange_org_access(
293 &auth,
294 *id,
295 &data.local_exchange_use_cases,
296 &data.building_use_cases,
297 &data.acp_use_cases,
298 )
299 .await
300 {
301 return err.error_response();
302 }
303
304 match data
305 .local_exchange_use_cases
306 .start_exchange(id.into_inner(), auth.user_id)
307 .await
308 {
309 Ok(exchange) => HttpResponse::Ok().json(exchange),
310 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
311 }
312}
313
314#[post("/exchanges/{id}/complete")]
318pub async fn complete_exchange(
319 data: web::Data<AppState>,
320 auth: AuthenticatedUser,
321 id: web::Path<Uuid>,
322 request: web::Json<CompleteExchangeDto>,
323) -> impl Responder {
324 if let Err(err) = verify_exchange_org_access(
331 &auth,
332 *id,
333 &data.local_exchange_use_cases,
334 &data.building_use_cases,
335 &data.acp_use_cases,
336 )
337 .await
338 {
339 return err.error_response();
340 }
341
342 match data
343 .local_exchange_use_cases
344 .complete_exchange(id.into_inner(), auth.user_id, request.into_inner())
345 .await
346 {
347 Ok(exchange) => HttpResponse::Ok().json(exchange),
348 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
349 }
350}
351
352#[post("/exchanges/{id}/cancel")]
355pub async fn cancel_exchange(
356 data: web::Data<AppState>,
357 auth: AuthenticatedUser,
358 id: web::Path<Uuid>,
359 request: web::Json<CancelExchangeDto>,
360) -> impl Responder {
361 if let Err(err) = verify_exchange_org_access(
368 &auth,
369 *id,
370 &data.local_exchange_use_cases,
371 &data.building_use_cases,
372 &data.acp_use_cases,
373 )
374 .await
375 {
376 return err.error_response();
377 }
378
379 match data
380 .local_exchange_use_cases
381 .cancel_exchange(
382 id.into_inner(),
383 auth.user_id,
384 &auth.role,
385 request.into_inner(),
386 )
387 .await
388 {
389 Ok(exchange) => HttpResponse::Ok().json(exchange),
390 Err(e) => {
391 if classification_erreurs::est_motif_manquant(&e) {
392 HttpResponse::UnprocessableEntity().json(serde_json::json!({"error": e}))
393 } else if classification_erreurs::est_interdit(&e) {
394 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
395 } else if classification_erreurs::est_introuvable(&e) {
396 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
397 } else {
398 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
399 }
400 }
401 }
402}
403
404#[put("/exchanges/{id}/rate-provider")]
407pub async fn rate_provider(
408 data: web::Data<AppState>,
409 auth: AuthenticatedUser,
410 id: web::Path<Uuid>,
411 request: web::Json<RateExchangeDto>,
412) -> impl Responder {
413 if let Err(err) = verify_exchange_org_access(
420 &auth,
421 *id,
422 &data.local_exchange_use_cases,
423 &data.building_use_cases,
424 &data.acp_use_cases,
425 )
426 .await
427 {
428 return err.error_response();
429 }
430
431 match data
432 .local_exchange_use_cases
433 .rate_provider(id.into_inner(), auth.user_id, request.into_inner())
434 .await
435 {
436 Ok(exchange) => HttpResponse::Ok().json(exchange),
437 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
438 }
439}
440
441#[put("/exchanges/{id}/rate-requester")]
444pub async fn rate_requester(
445 data: web::Data<AppState>,
446 auth: AuthenticatedUser,
447 id: web::Path<Uuid>,
448 request: web::Json<RateExchangeDto>,
449) -> impl Responder {
450 if let Err(err) = verify_exchange_org_access(
457 &auth,
458 *id,
459 &data.local_exchange_use_cases,
460 &data.building_use_cases,
461 &data.acp_use_cases,
462 )
463 .await
464 {
465 return err.error_response();
466 }
467
468 match data
469 .local_exchange_use_cases
470 .rate_requester(id.into_inner(), auth.user_id, request.into_inner())
471 .await
472 {
473 Ok(exchange) => HttpResponse::Ok().json(exchange),
474 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
475 }
476}
477
478#[delete("/exchanges/{id}")]
481pub async fn delete_exchange(
482 data: web::Data<AppState>,
483 auth: AuthenticatedUser,
484 id: web::Path<Uuid>,
485 body: Option<web::Json<DeleteExchangeDto>>,
492) -> impl Responder {
493 let reason = body.and_then(|b| b.into_inner().reason);
494
495 match data
496 .local_exchange_use_cases
497 .delete_exchange(id.into_inner(), auth.user_id, &auth.role, reason)
498 .await
499 {
500 Ok(_) => HttpResponse::NoContent().finish(),
501 Err(e) => {
502 if classification_erreurs::est_motif_manquant(&e) {
503 HttpResponse::UnprocessableEntity().json(serde_json::json!({"error": e}))
504 } else if classification_erreurs::est_interdit(&e) {
505 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
506 } else if classification_erreurs::est_introuvable(&e) {
507 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
508 } else {
509 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
510 }
511 }
512 }
513}
514
515#[get("/owners/{owner_id}/buildings/{building_id}/credit-balance")]
519pub async fn get_credit_balance(
520 data: web::Data<AppState>,
521 auth: AuthenticatedUser,
522 path: web::Path<(Uuid, Uuid)>,
523) -> impl Responder {
524 if let Err(err) = verify_owner_org_access(&auth, path.0, &data.owner_use_cases).await {
529 return err.error_response();
530 }
531
532 let (path_id, building_id) = path.into_inner();
533
534 let owner = match data.owner_use_cases.get_owner(path_id).await {
537 Ok(Some(owner)) => owner,
538 Ok(None) => {
539 match data.owner_use_cases.find_owner_by_user_id(path_id).await {
541 Ok(Some(owner)) => owner,
542 Ok(None) => {
543 return HttpResponse::NotFound().json(serde_json::json!({
544 "error": format!("Owner not found for id: {}", path_id)
545 }))
546 }
547 Err(e) => {
548 return HttpResponse::InternalServerError().json(serde_json::json!({
549 "error": format!("Failed to fetch owner: {}", e)
550 }))
551 }
552 }
553 }
554 Err(e) => {
555 return HttpResponse::InternalServerError().json(serde_json::json!({
556 "error": format!("Failed to fetch owner: {}", e)
557 }))
558 }
559 };
560
561 let owner_user_id = owner
563 .user_id
564 .as_ref()
565 .and_then(|id| Uuid::parse_str(id).ok());
566 if owner_user_id != Some(auth.user_id) {
567 return HttpResponse::Forbidden().json(serde_json::json!({
568 "error": "You can only view your own credit balance"
569 }));
570 }
571
572 let owner_uuid = match Uuid::parse_str(&owner.id) {
573 Ok(id) => id,
574 Err(_) => {
575 return HttpResponse::InternalServerError().json(serde_json::json!({
576 "error": "Invalid owner ID format"
577 }))
578 }
579 };
580
581 match data
582 .local_exchange_use_cases
583 .get_credit_balance(owner_uuid, building_id)
584 .await
585 {
586 Ok(balance) => HttpResponse::Ok().json(balance),
587 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
588 }
589}
590
591#[get("/buildings/{building_id}/leaderboard")]
594pub async fn get_leaderboard(
595 data: web::Data<AppState>,
596 auth: AuthenticatedUser,
597 building_id: web::Path<Uuid>,
598 query: web::Query<std::collections::HashMap<String, String>>,
599) -> impl Responder {
600 if let Err(err) = verify_building_org_access(
605 &auth,
606 *building_id,
607 &data.building_use_cases,
608 &data.acp_use_cases,
609 )
610 .await
611 {
612 return err.error_response();
613 }
614
615 let limit = query
616 .get("limit")
617 .and_then(|l| l.parse::<i32>().ok())
618 .unwrap_or(10);
619
620 match data
621 .local_exchange_use_cases
622 .get_leaderboard(building_id.into_inner(), limit)
623 .await
624 {
625 Ok(leaderboard) => HttpResponse::Ok().json(leaderboard),
626 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
627 }
628}
629
630#[get("/buildings/{building_id}/sel-statistics")]
633pub async fn get_sel_statistics(
634 data: web::Data<AppState>,
635 auth: AuthenticatedUser,
636 building_id: web::Path<Uuid>,
637) -> impl Responder {
638 if let Err(err) = verify_building_org_access(
643 &auth,
644 *building_id,
645 &data.building_use_cases,
646 &data.acp_use_cases,
647 )
648 .await
649 {
650 return err.error_response();
651 }
652
653 match data
654 .local_exchange_use_cases
655 .get_statistics(building_id.into_inner())
656 .await
657 {
658 Ok(stats) => HttpResponse::Ok().json(stats),
659 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
660 }
661}
662
663#[get("/owners/{owner_id}/exchange-summary")]
666pub async fn get_owner_summary(
667 data: web::Data<AppState>,
668 auth: AuthenticatedUser,
669 owner_id: web::Path<Uuid>,
670) -> impl Responder {
671 if let Err(err) = verify_owner_org_access(&auth, *owner_id, &data.owner_use_cases).await {
676 return err.error_response();
677 }
678
679 let owner_id = owner_id.into_inner();
680
681 let owner = match data.owner_use_cases.get_owner(owner_id).await {
683 Ok(Some(owner)) => owner,
684 Ok(None) => {
685 return HttpResponse::NotFound().json(serde_json::json!({
686 "error": format!("Owner not found: {}", owner_id)
687 }))
688 }
689 Err(e) => {
690 return HttpResponse::InternalServerError().json(serde_json::json!({
691 "error": format!("Failed to fetch owner: {}", e)
692 }))
693 }
694 };
695
696 let owner_user_id = owner
697 .user_id
698 .as_ref()
699 .and_then(|id| Uuid::parse_str(id).ok());
700 if owner_user_id != Some(auth.user_id) {
701 return HttpResponse::Forbidden().json(serde_json::json!({
702 "error": "You can only view your own exchange summary"
703 }));
704 }
705
706 match data
707 .local_exchange_use_cases
708 .get_owner_summary(owner_id)
709 .await
710 {
711 Ok(summary) => HttpResponse::Ok().json(summary),
712 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
713 }
714}