koprogo_api/infrastructure/web/handlers/
shared_object_handlers.rs

1use crate::application::dto::{BorrowObjectDto, CreateSharedObjectDto, UpdateSharedObjectDto};
2use crate::domain::entities::SharedObjectCategory;
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/// Create a new shared object
9///
10/// POST /shared-objects
11#[post("/shared-objects")]
12pub async fn create_shared_object(
13    data: web::Data<AppState>,
14    auth: AuthenticatedUser,
15    request: web::Json<CreateSharedObjectDto>,
16) -> impl Responder {
17    match data
18        .shared_object_use_cases
19        .create_shared_object(auth.user_id, request.into_inner())
20        .await
21    {
22        Ok(object) => HttpResponse::Created().json(object),
23        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
24    }
25}
26
27/// Get shared object by ID with owner/borrower name enrichment
28///
29/// GET /shared-objects/:id
30#[get("/shared-objects/{id}")]
31pub async fn get_shared_object(data: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
32    match data
33        .shared_object_use_cases
34        .get_shared_object(id.into_inner())
35        .await
36    {
37        Ok(object) => HttpResponse::Ok().json(object),
38        Err(e) => {
39            if e.contains("not found") {
40                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
41            } else {
42                HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
43            }
44        }
45    }
46}
47
48/// List all shared objects for a building
49///
50/// GET /buildings/:building_id/shared-objects
51#[get("/buildings/{building_id}/shared-objects")]
52pub async fn list_building_objects(
53    data: web::Data<AppState>,
54    building_id: web::Path<Uuid>,
55) -> impl Responder {
56    match data
57        .shared_object_use_cases
58        .list_building_objects(building_id.into_inner())
59        .await
60    {
61        Ok(objects) => HttpResponse::Ok().json(objects),
62        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
63    }
64}
65
66/// List available shared objects for a building (marketplace view)
67///
68/// GET /buildings/:building_id/shared-objects/available
69#[get("/buildings/{building_id}/shared-objects/available")]
70pub async fn list_available_objects(
71    data: web::Data<AppState>,
72    building_id: web::Path<Uuid>,
73) -> impl Responder {
74    match data
75        .shared_object_use_cases
76        .list_available_objects(building_id.into_inner())
77        .await
78    {
79        Ok(objects) => HttpResponse::Ok().json(objects),
80        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
81    }
82}
83
84/// List borrowed shared objects for a building
85///
86/// GET /buildings/:building_id/shared-objects/borrowed
87#[get("/buildings/{building_id}/shared-objects/borrowed")]
88pub async fn list_borrowed_objects(
89    data: web::Data<AppState>,
90    building_id: web::Path<Uuid>,
91) -> impl Responder {
92    match data
93        .shared_object_use_cases
94        .list_borrowed_objects(building_id.into_inner())
95        .await
96    {
97        Ok(objects) => HttpResponse::Ok().json(objects),
98        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
99    }
100}
101
102/// List overdue shared objects for a building
103///
104/// GET /buildings/:building_id/shared-objects/overdue
105#[get("/buildings/{building_id}/shared-objects/overdue")]
106pub async fn list_overdue_objects(
107    data: web::Data<AppState>,
108    building_id: web::Path<Uuid>,
109) -> impl Responder {
110    match data
111        .shared_object_use_cases
112        .list_overdue_objects(building_id.into_inner())
113        .await
114    {
115        Ok(objects) => HttpResponse::Ok().json(objects),
116        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
117    }
118}
119
120/// List free/volunteer shared objects for a building
121///
122/// GET /buildings/:building_id/shared-objects/free
123#[get("/buildings/{building_id}/shared-objects/free")]
124pub async fn list_free_objects(
125    data: web::Data<AppState>,
126    building_id: web::Path<Uuid>,
127) -> impl Responder {
128    match data
129        .shared_object_use_cases
130        .list_free_objects(building_id.into_inner())
131        .await
132    {
133        Ok(objects) => HttpResponse::Ok().json(objects),
134        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
135    }
136}
137
138/// List shared objects by category (Tools, Books, Electronics, etc.)
139///
140/// GET /buildings/:building_id/shared-objects/category/:category
141#[get("/buildings/{building_id}/shared-objects/category/{category}")]
142pub async fn list_objects_by_category(
143    data: web::Data<AppState>,
144    path: web::Path<(Uuid, String)>,
145) -> impl Responder {
146    let (building_id, category_str) = path.into_inner();
147
148    // Parse object category
149    let category = match serde_json::from_str::<SharedObjectCategory>(&format!(
150        "\"{}\"",
151        category_str
152    )) {
153        Ok(c) => c,
154        Err(_) => {
155            return HttpResponse::BadRequest().json(serde_json::json!({
156                "error": format!("Invalid shared object category: {}. Valid categories: Tools, Books, Electronics, Sports, Gardening, Kitchen, Baby, Other", category_str)
157            }))
158        }
159    };
160
161    match data
162        .shared_object_use_cases
163        .list_objects_by_category(building_id, category)
164        .await
165    {
166        Ok(objects) => HttpResponse::Ok().json(objects),
167        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
168    }
169}
170
171/// List all shared objects created by an owner
172///
173/// GET /owners/:owner_id/shared-objects
174#[get("/owners/{owner_id}/shared-objects")]
175pub async fn list_owner_objects(
176    data: web::Data<AppState>,
177    owner_id: web::Path<Uuid>,
178) -> impl Responder {
179    match data
180        .shared_object_use_cases
181        .list_owner_objects(owner_id.into_inner())
182        .await
183    {
184        Ok(objects) => HttpResponse::Ok().json(objects),
185        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
186    }
187}
188
189/// List all shared objects currently borrowed by a user
190///
191/// GET /shared-objects/my-borrowed
192#[get("/shared-objects/my-borrowed")]
193pub async fn list_my_borrowed_objects(
194    data: web::Data<AppState>,
195    auth: AuthenticatedUser,
196) -> impl Responder {
197    match data
198        .shared_object_use_cases
199        .list_user_borrowed_objects(auth.user_id)
200        .await
201    {
202        Ok(objects) => HttpResponse::Ok().json(objects),
203        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
204    }
205}
206
207/// Update a shared object
208///
209/// PUT /shared-objects/:id
210#[put("/shared-objects/{id}")]
211pub async fn update_shared_object(
212    data: web::Data<AppState>,
213    auth: AuthenticatedUser,
214    id: web::Path<Uuid>,
215    request: web::Json<UpdateSharedObjectDto>,
216) -> impl Responder {
217    match data
218        .shared_object_use_cases
219        .update_shared_object(id.into_inner(), auth.user_id, request.into_inner())
220        .await
221    {
222        Ok(object) => HttpResponse::Ok().json(object),
223        Err(e) => {
224            if e.contains("Unauthorized") {
225                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
226            } else if e.contains("not found") {
227                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
228            } else {
229                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
230            }
231        }
232    }
233}
234
235/// Mark shared object as available
236///
237/// POST /shared-objects/:id/mark-available
238#[post("/shared-objects/{id}/mark-available")]
239pub async fn mark_object_available(
240    data: web::Data<AppState>,
241    auth: AuthenticatedUser,
242    id: web::Path<Uuid>,
243) -> impl Responder {
244    match data
245        .shared_object_use_cases
246        .mark_object_available(id.into_inner(), auth.user_id)
247        .await
248    {
249        Ok(object) => HttpResponse::Ok().json(object),
250        Err(e) => {
251            if e.contains("Unauthorized") {
252                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
253            } else if e.contains("not found") {
254                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
255            } else {
256                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
257            }
258        }
259    }
260}
261
262/// Mark shared object as unavailable
263///
264/// POST /shared-objects/:id/mark-unavailable
265#[post("/shared-objects/{id}/mark-unavailable")]
266pub async fn mark_object_unavailable(
267    data: web::Data<AppState>,
268    auth: AuthenticatedUser,
269    id: web::Path<Uuid>,
270) -> impl Responder {
271    match data
272        .shared_object_use_cases
273        .mark_object_unavailable(id.into_inner(), auth.user_id)
274        .await
275    {
276        Ok(object) => HttpResponse::Ok().json(object),
277        Err(e) => {
278            if e.contains("Unauthorized") {
279                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
280            } else if e.contains("not found") {
281                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
282            } else {
283                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
284            }
285        }
286    }
287}
288
289/// Borrow a shared object
290///
291/// POST /shared-objects/:id/borrow
292#[post("/shared-objects/{id}/borrow")]
293pub async fn borrow_object(
294    data: web::Data<AppState>,
295    auth: AuthenticatedUser,
296    id: web::Path<Uuid>,
297    request: web::Json<BorrowObjectDto>,
298) -> impl Responder {
299    match data
300        .shared_object_use_cases
301        .borrow_object(id.into_inner(), auth.user_id, request.into_inner())
302        .await
303    {
304        Ok(object) => HttpResponse::Ok().json(object),
305        Err(e) => {
306            if e.contains("Owner cannot borrow") {
307                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
308            } else if e.contains("not found") {
309                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
310            } else {
311                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
312            }
313        }
314    }
315}
316
317/// Return a borrowed object
318///
319/// POST /shared-objects/:id/return
320#[post("/shared-objects/{id}/return")]
321pub async fn return_object(
322    data: web::Data<AppState>,
323    auth: AuthenticatedUser,
324    id: web::Path<Uuid>,
325) -> impl Responder {
326    match data
327        .shared_object_use_cases
328        .return_object(id.into_inner(), auth.user_id)
329        .await
330    {
331        Ok(object) => HttpResponse::Ok().json(object),
332        Err(e) => {
333            if e.contains("Only borrower can return") {
334                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
335            } else if e.contains("not found") {
336                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
337            } else {
338                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
339            }
340        }
341    }
342}
343
344/// Delete a shared object
345///
346/// DELETE /shared-objects/:id
347#[delete("/shared-objects/{id}")]
348pub async fn delete_shared_object(
349    data: web::Data<AppState>,
350    auth: AuthenticatedUser,
351    id: web::Path<Uuid>,
352) -> impl Responder {
353    match data
354        .shared_object_use_cases
355        .delete_shared_object(id.into_inner(), auth.user_id)
356        .await
357    {
358        Ok(_) => HttpResponse::NoContent().finish(),
359        Err(e) => {
360            if e.contains("Unauthorized") {
361                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
362            } else if e.contains("not found") {
363                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
364            } else {
365                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
366            }
367        }
368    }
369}
370
371/// Get shared object statistics for a building
372///
373/// GET /buildings/:building_id/shared-objects/statistics
374#[get("/buildings/{building_id}/shared-objects/statistics")]
375pub async fn get_object_statistics(
376    data: web::Data<AppState>,
377    building_id: web::Path<Uuid>,
378) -> impl Responder {
379    match data
380        .shared_object_use_cases
381        .get_object_statistics(building_id.into_inner())
382        .await
383    {
384        Ok(stats) => HttpResponse::Ok().json(stats),
385        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
386    }
387}