koprogo_api/infrastructure/web/handlers/
shared_object_handlers.rs1use 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#[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 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 .shared_object_use_cases
25 .create_shared_object(auth.user_id, org_id, request.into_inner())
26 .await
27 {
28 Ok(object) => HttpResponse::Created().json(object),
29 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
30 }
31}
32
33#[get("/shared-objects/{id}")]
37pub async fn get_shared_object(data: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
38 match data
39 .shared_object_use_cases
40 .get_shared_object(id.into_inner())
41 .await
42 {
43 Ok(object) => HttpResponse::Ok().json(object),
44 Err(e) => {
45 if e.contains("not found") {
46 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
47 } else {
48 HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
49 }
50 }
51 }
52}
53
54#[get("/buildings/{building_id}/shared-objects")]
58pub async fn list_building_objects(
59 data: web::Data<AppState>,
60 building_id: web::Path<Uuid>,
61) -> impl Responder {
62 match data
63 .shared_object_use_cases
64 .list_building_objects(building_id.into_inner())
65 .await
66 {
67 Ok(objects) => HttpResponse::Ok().json(objects),
68 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
69 }
70}
71
72#[get("/buildings/{building_id}/shared-objects/available")]
76pub async fn list_available_objects(
77 data: web::Data<AppState>,
78 building_id: web::Path<Uuid>,
79) -> impl Responder {
80 match data
81 .shared_object_use_cases
82 .list_available_objects(building_id.into_inner())
83 .await
84 {
85 Ok(objects) => HttpResponse::Ok().json(objects),
86 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
87 }
88}
89
90#[get("/buildings/{building_id}/shared-objects/borrowed")]
94pub async fn list_borrowed_objects(
95 data: web::Data<AppState>,
96 building_id: web::Path<Uuid>,
97) -> impl Responder {
98 match data
99 .shared_object_use_cases
100 .list_borrowed_objects(building_id.into_inner())
101 .await
102 {
103 Ok(objects) => HttpResponse::Ok().json(objects),
104 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
105 }
106}
107
108#[get("/buildings/{building_id}/shared-objects/overdue")]
112pub async fn list_overdue_objects(
113 data: web::Data<AppState>,
114 building_id: web::Path<Uuid>,
115) -> impl Responder {
116 match data
117 .shared_object_use_cases
118 .list_overdue_objects(building_id.into_inner())
119 .await
120 {
121 Ok(objects) => HttpResponse::Ok().json(objects),
122 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
123 }
124}
125
126#[get("/buildings/{building_id}/shared-objects/free")]
130pub async fn list_free_objects(
131 data: web::Data<AppState>,
132 building_id: web::Path<Uuid>,
133) -> impl Responder {
134 match data
135 .shared_object_use_cases
136 .list_free_objects(building_id.into_inner())
137 .await
138 {
139 Ok(objects) => HttpResponse::Ok().json(objects),
140 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
141 }
142}
143
144#[get("/buildings/{building_id}/shared-objects/category/{category}")]
148pub async fn list_objects_by_category(
149 data: web::Data<AppState>,
150 path: web::Path<(Uuid, String)>,
151) -> impl Responder {
152 let (building_id, category_str) = path.into_inner();
153
154 let category = match serde_json::from_str::<SharedObjectCategory>(&format!(
156 "\"{}\"",
157 category_str
158 )) {
159 Ok(c) => c,
160 Err(_) => {
161 return HttpResponse::BadRequest().json(serde_json::json!({
162 "error": format!("Invalid shared object category: {}. Valid categories: Tools, Books, Electronics, Sports, Gardening, Kitchen, Baby, Other", category_str)
163 }))
164 }
165 };
166
167 match data
168 .shared_object_use_cases
169 .list_objects_by_category(building_id, category)
170 .await
171 {
172 Ok(objects) => HttpResponse::Ok().json(objects),
173 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
174 }
175}
176
177#[get("/owners/{owner_id}/shared-objects")]
181pub async fn list_owner_objects(
182 data: web::Data<AppState>,
183 owner_id: web::Path<Uuid>,
184) -> impl Responder {
185 match data
186 .shared_object_use_cases
187 .list_owner_objects(owner_id.into_inner())
188 .await
189 {
190 Ok(objects) => HttpResponse::Ok().json(objects),
191 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
192 }
193}
194
195#[get("/shared-objects/my-borrowed")]
199pub async fn list_my_borrowed_objects(
200 data: web::Data<AppState>,
201 auth: AuthenticatedUser,
202) -> impl Responder {
203 let org_id = match auth.require_organization() {
204 Ok(id) => id,
205 Err(e) => {
206 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
207 }
208 };
209 match data
210 .shared_object_use_cases
211 .list_user_borrowed_objects(auth.user_id, org_id)
212 .await
213 {
214 Ok(objects) => HttpResponse::Ok().json(objects),
215 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
216 }
217}
218
219#[put("/shared-objects/{id}")]
223pub async fn update_shared_object(
224 data: web::Data<AppState>,
225 auth: AuthenticatedUser,
226 id: web::Path<Uuid>,
227 request: web::Json<UpdateSharedObjectDto>,
228) -> impl Responder {
229 let org_id = match auth.require_organization() {
230 Ok(id) => id,
231 Err(e) => {
232 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
233 }
234 };
235 match data
236 .shared_object_use_cases
237 .update_shared_object(id.into_inner(), auth.user_id, org_id, request.into_inner())
238 .await
239 {
240 Ok(object) => HttpResponse::Ok().json(object),
241 Err(e) => {
242 if e.contains("Unauthorized") {
243 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
244 } else if e.contains("not found") {
245 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
246 } else {
247 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
248 }
249 }
250 }
251}
252
253#[post("/shared-objects/{id}/mark-available")]
257pub async fn mark_object_available(
258 data: web::Data<AppState>,
259 auth: AuthenticatedUser,
260 id: web::Path<Uuid>,
261) -> impl Responder {
262 let org_id = match auth.require_organization() {
263 Ok(id) => id,
264 Err(e) => {
265 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
266 }
267 };
268 match data
269 .shared_object_use_cases
270 .mark_object_available(id.into_inner(), auth.user_id, org_id)
271 .await
272 {
273 Ok(object) => HttpResponse::Ok().json(object),
274 Err(e) => {
275 if e.contains("Unauthorized") {
276 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
277 } else if e.contains("not found") {
278 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
279 } else {
280 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
281 }
282 }
283 }
284}
285
286#[post("/shared-objects/{id}/mark-unavailable")]
290pub async fn mark_object_unavailable(
291 data: web::Data<AppState>,
292 auth: AuthenticatedUser,
293 id: web::Path<Uuid>,
294) -> impl Responder {
295 let org_id = match auth.require_organization() {
296 Ok(id) => id,
297 Err(e) => {
298 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
299 }
300 };
301 match data
302 .shared_object_use_cases
303 .mark_object_unavailable(id.into_inner(), auth.user_id, org_id)
304 .await
305 {
306 Ok(object) => HttpResponse::Ok().json(object),
307 Err(e) => {
308 if e.contains("Unauthorized") {
309 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
310 } else if e.contains("not found") {
311 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
312 } else {
313 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
314 }
315 }
316 }
317}
318
319#[post("/shared-objects/{id}/borrow")]
323pub async fn borrow_object(
324 data: web::Data<AppState>,
325 auth: AuthenticatedUser,
326 id: web::Path<Uuid>,
327 request: web::Json<BorrowObjectDto>,
328) -> impl Responder {
329 let org_id = match auth.require_organization() {
330 Ok(id) => id,
331 Err(e) => {
332 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
333 }
334 };
335 match data
336 .shared_object_use_cases
337 .borrow_object(id.into_inner(), auth.user_id, org_id, request.into_inner())
338 .await
339 {
340 Ok(object) => HttpResponse::Ok().json(object),
341 Err(e) => {
342 if e.contains("Owner cannot borrow") {
343 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
344 } else if e.contains("not found") {
345 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
346 } else {
347 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
348 }
349 }
350 }
351}
352
353#[post("/shared-objects/{id}/return")]
357pub async fn return_object(
358 data: web::Data<AppState>,
359 auth: AuthenticatedUser,
360 id: web::Path<Uuid>,
361) -> impl Responder {
362 let org_id = match auth.require_organization() {
363 Ok(id) => id,
364 Err(e) => {
365 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
366 }
367 };
368 match data
369 .shared_object_use_cases
370 .return_object(id.into_inner(), auth.user_id, org_id)
371 .await
372 {
373 Ok(object) => HttpResponse::Ok().json(object),
374 Err(e) => {
375 if e.contains("Only borrower can return") {
376 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
377 } else if e.contains("not found") {
378 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
379 } else {
380 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
381 }
382 }
383 }
384}
385
386#[delete("/shared-objects/{id}")]
390pub async fn delete_shared_object(
391 data: web::Data<AppState>,
392 auth: AuthenticatedUser,
393 id: web::Path<Uuid>,
394) -> impl Responder {
395 let org_id = match auth.require_organization() {
396 Ok(id) => id,
397 Err(e) => {
398 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
399 }
400 };
401 match data
402 .shared_object_use_cases
403 .delete_shared_object(id.into_inner(), auth.user_id, org_id)
404 .await
405 {
406 Ok(_) => HttpResponse::NoContent().finish(),
407 Err(e) => {
408 if e.contains("Unauthorized") {
409 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
410 } else if e.contains("not found") {
411 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
412 } else {
413 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
414 }
415 }
416 }
417}
418
419#[get("/buildings/{building_id}/shared-objects/statistics")]
423pub async fn get_object_statistics(
424 data: web::Data<AppState>,
425 building_id: web::Path<Uuid>,
426) -> impl Responder {
427 match data
428 .shared_object_use_cases
429 .get_object_statistics(building_id.into_inner())
430 .await
431 {
432 Ok(stats) => HttpResponse::Ok().json(stats),
433 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
434 }
435}