koprogo_api/infrastructure/web/handlers/
resource_booking_handlers.rs1use crate::application::dto::{CreateResourceBookingDto, UpdateResourceBookingDto};
2use crate::domain::entities::{BookingStatus, ResourceType};
3use crate::infrastructure::web::app_state::AppState;
4use crate::infrastructure::web::middleware::AuthenticatedUser;
5use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
6use chrono::{DateTime, Utc};
7use serde::Deserialize;
8use uuid::Uuid;
9
10#[post("/resource-bookings")]
31pub async fn create_booking(
32 data: web::Data<AppState>,
33 auth: AuthenticatedUser,
34 request: web::Json<CreateResourceBookingDto>,
35) -> impl Responder {
36 match data
37 .resource_booking_use_cases
38 .create_booking(auth.user_id, request.into_inner())
39 .await
40 {
41 Ok(booking) => HttpResponse::Created().json(booking),
42 Err(e) => {
43 if e.contains("conflicts with") {
44 HttpResponse::Conflict().json(serde_json::json!({"error": e}))
45 } else if e.contains("not found") {
46 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
47 } else {
48 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
49 }
50 }
51 }
52}
53
54#[get("/resource-bookings/{id}")]
62pub async fn get_booking(
63 data: web::Data<AppState>,
64 _auth: AuthenticatedUser,
65 id: web::Path<Uuid>,
66) -> impl Responder {
67 match data
68 .resource_booking_use_cases
69 .get_booking(id.into_inner())
70 .await
71 {
72 Ok(booking) => HttpResponse::Ok().json(booking),
73 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
74 }
75}
76
77#[get("/buildings/{building_id}/resource-bookings")]
84pub async fn list_building_bookings(
85 data: web::Data<AppState>,
86 _auth: AuthenticatedUser,
87 building_id: web::Path<Uuid>,
88) -> impl Responder {
89 match data
90 .resource_booking_use_cases
91 .list_building_bookings(building_id.into_inner())
92 .await
93 {
94 Ok(bookings) => HttpResponse::Ok().json(bookings),
95 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
96 }
97}
98
99#[get("/buildings/{building_id}/resource-bookings/type/{resource_type}")]
106pub async fn list_by_resource_type(
107 data: web::Data<AppState>,
108 _auth: AuthenticatedUser,
109 path: web::Path<(Uuid, String)>,
110) -> impl Responder {
111 let (building_id, resource_type_str) = path.into_inner();
112
113 let resource_type: ResourceType =
115 match serde_json::from_str(&format!("\"{}\"", resource_type_str)) {
116 Ok(rt) => rt,
117 Err(_) => {
118 return HttpResponse::BadRequest().json(serde_json::json!({
119 "error": format!("Invalid resource type: {}", resource_type_str)
120 }))
121 }
122 };
123
124 match data
125 .resource_booking_use_cases
126 .list_by_resource_type(building_id, resource_type)
127 .await
128 {
129 Ok(bookings) => HttpResponse::Ok().json(bookings),
130 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
131 }
132}
133
134#[get("/buildings/{building_id}/resource-bookings/resource/{resource_type}/{resource_name}")]
141pub async fn list_by_resource(
142 data: web::Data<AppState>,
143 _auth: AuthenticatedUser,
144 path: web::Path<(Uuid, String, String)>,
145) -> impl Responder {
146 let (building_id, resource_type_str, resource_name) = path.into_inner();
147
148 let resource_type: ResourceType =
150 match serde_json::from_str(&format!("\"{}\"", resource_type_str)) {
151 Ok(rt) => rt,
152 Err(_) => {
153 return HttpResponse::BadRequest().json(serde_json::json!({
154 "error": format!("Invalid resource type: {}", resource_type_str)
155 }))
156 }
157 };
158
159 match data
160 .resource_booking_use_cases
161 .list_by_resource(building_id, resource_type, resource_name)
162 .await
163 {
164 Ok(bookings) => HttpResponse::Ok().json(bookings),
165 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
166 }
167}
168
169#[get("/resource-bookings/my")]
176pub async fn list_my_bookings(
177 data: web::Data<AppState>,
178 auth: AuthenticatedUser,
179) -> impl Responder {
180 match data
181 .resource_booking_use_cases
182 .list_user_bookings(auth.user_id)
183 .await
184 {
185 Ok(bookings) => HttpResponse::Ok().json(bookings),
186 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
187 }
188}
189
190#[get("/resource-bookings/my/status/{status}")]
197pub async fn list_my_bookings_by_status(
198 data: web::Data<AppState>,
199 auth: AuthenticatedUser,
200 status: web::Path<String>,
201) -> impl Responder {
202 let booking_status: BookingStatus =
204 match serde_json::from_str(&format!("\"{}\"", status.into_inner())) {
205 Ok(s) => s,
206 Err(_) => {
207 return HttpResponse::BadRequest()
208 .json(serde_json::json!({"error": "Invalid status"}))
209 }
210 };
211
212 match data
213 .resource_booking_use_cases
214 .list_user_bookings_by_status(auth.user_id, booking_status)
215 .await
216 {
217 Ok(bookings) => HttpResponse::Ok().json(bookings),
218 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
219 }
220}
221
222#[get("/buildings/{building_id}/resource-bookings/status/{status}")]
229pub async fn list_building_bookings_by_status(
230 data: web::Data<AppState>,
231 _auth: AuthenticatedUser,
232 path: web::Path<(Uuid, String)>,
233) -> impl Responder {
234 let (building_id, status_str) = path.into_inner();
235
236 let booking_status: BookingStatus = match serde_json::from_str(&format!("\"{}\"", status_str)) {
238 Ok(s) => s,
239 Err(_) => {
240 return HttpResponse::BadRequest().json(serde_json::json!({
241 "error": format!("Invalid status: {}", status_str)
242 }))
243 }
244 };
245
246 match data
247 .resource_booking_use_cases
248 .list_building_bookings_by_status(building_id, booking_status)
249 .await
250 {
251 Ok(bookings) => HttpResponse::Ok().json(bookings),
252 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
253 }
254}
255
256#[derive(Deserialize)]
257pub struct UpcomingQuery {
258 limit: Option<i64>,
259}
260
261#[get("/buildings/{building_id}/resource-bookings/upcoming")]
268pub async fn list_upcoming_bookings(
269 data: web::Data<AppState>,
270 _auth: AuthenticatedUser,
271 building_id: web::Path<Uuid>,
272 query: web::Query<UpcomingQuery>,
273) -> impl Responder {
274 match data
275 .resource_booking_use_cases
276 .list_upcoming_bookings(building_id.into_inner(), query.limit)
277 .await
278 {
279 Ok(bookings) => HttpResponse::Ok().json(bookings),
280 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
281 }
282}
283
284#[get("/buildings/{building_id}/resource-bookings/active")]
291pub async fn list_active_bookings(
292 data: web::Data<AppState>,
293 _auth: AuthenticatedUser,
294 building_id: web::Path<Uuid>,
295) -> impl Responder {
296 match data
297 .resource_booking_use_cases
298 .list_active_bookings(building_id.into_inner())
299 .await
300 {
301 Ok(bookings) => HttpResponse::Ok().json(bookings),
302 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
303 }
304}
305
306#[derive(Deserialize)]
307pub struct PastQuery {
308 limit: Option<i64>,
309}
310
311#[get("/buildings/{building_id}/resource-bookings/past")]
318pub async fn list_past_bookings(
319 data: web::Data<AppState>,
320 _auth: AuthenticatedUser,
321 building_id: web::Path<Uuid>,
322 query: web::Query<PastQuery>,
323) -> impl Responder {
324 match data
325 .resource_booking_use_cases
326 .list_past_bookings(building_id.into_inner(), query.limit)
327 .await
328 {
329 Ok(bookings) => HttpResponse::Ok().json(bookings),
330 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
331 }
332}
333
334#[put("/resource-bookings/{id}")]
348pub async fn update_booking(
349 data: web::Data<AppState>,
350 auth: AuthenticatedUser,
351 id: web::Path<Uuid>,
352 request: web::Json<UpdateResourceBookingDto>,
353) -> impl Responder {
354 match data
355 .resource_booking_use_cases
356 .update_booking(id.into_inner(), auth.user_id, request.into_inner())
357 .await
358 {
359 Ok(booking) => HttpResponse::Ok().json(booking),
360 Err(e) => {
361 if e.contains("Only the booking owner") {
362 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
363 } else if e.contains("not found") {
364 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
365 } else {
366 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
367 }
368 }
369 }
370}
371
372#[post("/resource-bookings/{id}/cancel")]
382pub async fn cancel_booking(
383 data: web::Data<AppState>,
384 auth: AuthenticatedUser,
385 id: web::Path<Uuid>,
386) -> impl Responder {
387 match data
388 .resource_booking_use_cases
389 .cancel_booking(id.into_inner(), auth.user_id)
390 .await
391 {
392 Ok(booking) => HttpResponse::Ok().json(booking),
393 Err(e) => {
394 if e.contains("Only the booking owner") {
395 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
396 } else if e.contains("not found") {
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("/resource-bookings/{id}/complete")]
414pub async fn complete_booking(
415 data: web::Data<AppState>,
416 _auth: AuthenticatedUser,
417 id: web::Path<Uuid>,
418) -> impl Responder {
419 match data
420 .resource_booking_use_cases
421 .complete_booking(id.into_inner())
422 .await
423 {
424 Ok(booking) => HttpResponse::Ok().json(booking),
425 Err(e) => {
426 if e.contains("not found") {
427 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
428 } else {
429 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
430 }
431 }
432 }
433}
434
435#[post("/resource-bookings/{id}/no-show")]
444pub async fn mark_no_show(
445 data: web::Data<AppState>,
446 _auth: AuthenticatedUser,
447 id: web::Path<Uuid>,
448) -> impl Responder {
449 match data
450 .resource_booking_use_cases
451 .mark_no_show(id.into_inner())
452 .await
453 {
454 Ok(booking) => HttpResponse::Ok().json(booking),
455 Err(e) => {
456 if e.contains("not found") {
457 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
458 } else {
459 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
460 }
461 }
462 }
463}
464
465#[post("/resource-bookings/{id}/confirm")]
474pub async fn confirm_booking(
475 data: web::Data<AppState>,
476 _auth: AuthenticatedUser,
477 id: web::Path<Uuid>,
478) -> impl Responder {
479 match data
480 .resource_booking_use_cases
481 .confirm_booking(id.into_inner())
482 .await
483 {
484 Ok(booking) => HttpResponse::Ok().json(booking),
485 Err(e) => {
486 if e.contains("not found") {
487 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
488 } else {
489 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
490 }
491 }
492 }
493}
494
495#[delete("/resource-bookings/{id}")]
504pub async fn delete_booking(
505 data: web::Data<AppState>,
506 auth: AuthenticatedUser,
507 id: web::Path<Uuid>,
508) -> impl Responder {
509 match data
510 .resource_booking_use_cases
511 .delete_booking(id.into_inner(), auth.user_id)
512 .await
513 {
514 Ok(()) => HttpResponse::NoContent().finish(),
515 Err(e) => {
516 if e.contains("Only the booking owner") {
517 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
518 } else if e.contains("not found") {
519 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
520 } else {
521 HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
522 }
523 }
524 }
525}
526
527#[derive(Deserialize)]
528pub struct CheckConflictsQuery {
529 pub building_id: Uuid,
530 pub resource_type: String,
531 pub resource_name: String,
532 pub start_time: DateTime<Utc>,
533 pub end_time: DateTime<Utc>,
534 pub exclude_booking_id: Option<Uuid>,
535}
536
537#[get("/resource-bookings/check-conflicts")]
552pub async fn check_conflicts(
553 data: web::Data<AppState>,
554 _auth: AuthenticatedUser,
555 query: web::Query<CheckConflictsQuery>,
556) -> impl Responder {
557 let resource_type: ResourceType =
559 match serde_json::from_str(&format!("\"{}\"", query.resource_type)) {
560 Ok(rt) => rt,
561 Err(_) => {
562 return HttpResponse::BadRequest().json(serde_json::json!({
563 "error": format!("Invalid resource type: {}", query.resource_type)
564 }))
565 }
566 };
567
568 match data
569 .resource_booking_use_cases
570 .check_conflicts(
571 query.building_id,
572 resource_type,
573 query.resource_name.clone(),
574 query.start_time,
575 query.end_time,
576 query.exclude_booking_id,
577 )
578 .await
579 {
580 Ok(conflicts) => HttpResponse::Ok().json(conflicts),
581 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
582 }
583}
584
585#[get("/buildings/{building_id}/resource-bookings/statistics")]
592pub async fn get_booking_statistics(
593 data: web::Data<AppState>,
594 _auth: AuthenticatedUser,
595 building_id: web::Path<Uuid>,
596) -> impl Responder {
597 match data
598 .resource_booking_use_cases
599 .get_statistics(building_id.into_inner())
600 .await
601 {
602 Ok(stats) => HttpResponse::Ok().json(stats),
603 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
604 }
605}