using Fitness_core_BE.DTOs.Booking; using Fitness_core_BE.Helpers; using Fitness_core_BE.Interfaces.Services; using Fitness_core_BE.Models.IOs; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using static Fitness_core_BE.Helpers.JwtMiddleware; using static Fitness_core_BE.Helpers.ApiResponseHelper; namespace Fitness_core_BE.Controllers { // Booking spans three audiences with very different auth needs, so routes are absolute // per-action rather than grouped under one controller-level [Route] prefix: "api/app/..." // for logged-in members (same convention as CustomerController), "api/public/..." for a // non-member's anonymous guest booking, "api/client/..." for staff (check-in, cancel, // session roster, manual payment recording). [ApiController] public class BookingController : ControllerBase { private readonly IBookingService _bookingService; public BookingController(IBookingService bookingService) { _bookingService = bookingService; } // ---------- Member (logged-in) ---------- [CustomMiddleware] [TenantResolution] [HttpPost("api/app/bookings")] public async Task>> CreateMemberBooking([FromBody] CreateMemberBookingRequest request) => Response(await _bookingService.CreateMemberBooking(new { facilitySessionId = request.FacilitySessionId }, HttpContext)); [CustomMiddleware] [TenantResolution] [HttpPost("api/app/bookings/adhoc")] public async Task>> CreateMemberAdhocBooking([FromBody] CreateAdhocBookingRequest request) => Response(await _bookingService.CreateMemberAdhocBooking(new { facilityId = request.FacilityId, startTime = request.StartTime.ToString("o"), durationMinutes = request.DurationMinutes, partySize = request.PartySize }, HttpContext)); [CustomMiddleware] [TenantResolution] [HttpGet("api/app/bookings/mine")] public async Task>> GetMyBookings() => Response(await _bookingService.GetMyBookings(new { }, HttpContext)); [CustomMiddleware] [TenantResolution] [HttpPost("api/app/bookings/{id:guid}/cancel")] public async Task>> CancelMyBooking(Guid id) => Response(await _bookingService.CancelBooking(new { bookingId = id }, HttpContext)); // ---------- Guest (no account at all) ---------- [AllowAnonymous] [HttpPost("api/public/bookings/guest")] public async Task>> CreateGuestBooking([FromBody] CreateGuestBookingRequest request) => Response(await _bookingService.CreateGuestBooking(new { facilitySessionId = request.FacilitySessionId, guestName = request.GuestName, guestPhone = request.GuestPhone }, HttpContext)); [AllowAnonymous] [HttpPost("api/public/bookings/guest/adhoc")] public async Task>> CreateGuestAdhocBooking([FromBody] CreateGuestAdhocBookingRequest request) => Response(await _bookingService.CreateGuestAdhocBooking(new { facilityId = request.FacilityId, startTime = request.StartTime.ToString("o"), durationMinutes = request.DurationMinutes, guestName = request.GuestName, guestPhone = request.GuestPhone, partySize = request.PartySize }, HttpContext)); // The actual "copy the link and share it" page -- BookingDto.ShareUrl points here. // Plain HTML, not the JSON ApiResponse envelope, since this is meant to be opened // directly in a browser (or a chat app's link preview), not consumed by a client SDK. [AllowAnonymous] [HttpGet("api/public/bookings/{id:guid}/view")] public async Task ViewBooking(Guid id) { var booking = await _bookingService.GetBookingView(new { bookingId = id }) as BookingDto; if (booking == null) return Content(BookingViewHtml.NotFound(), "text/html"); return Content(BookingViewHtml.Render(booking), "text/html"); } // ---------- Staff ---------- [CustomMiddleware] [TenantResolution] [RequiresPermission("bookings")] [HttpGet("api/client/bookings")] public async Task>> GetBookings( [FromQuery] Guid? facilityId, [FromQuery] Guid? facilitySessionId, [FromQuery] DateTime? fromDate, [FromQuery] DateTime? toDate, [FromQuery] string? status) => Response(await _bookingService.GetBookings(new { facilityId, facilitySessionId, fromDate = fromDate?.ToString("o"), toDate = toDate?.ToString("o"), status }, HttpContext)); // Kept as a thin backward-compat wrapper around the unified GetBookings above -- // prefer GET api/client/bookings?facilitySessionId= going forward. [CustomMiddleware] [TenantResolution] [RequiresPermission("bookings")] [HttpGet("api/client/bookings/session/{facilitySessionId:guid}")] public async Task>> GetSessionBookings(Guid facilitySessionId) => Response(await _bookingService.GetSessionBookings(new { facilitySessionId }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("bookings")] [HttpPost("api/client/bookings/check-in")] public async Task>> CheckInBooking([FromBody] CheckInBookingRequest request) => Response(await _bookingService.CheckInBooking(new { otpCode = request.OtpCode, rfidTagId = request.RfidTagId }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("bookings")] [HttpPost("api/client/bookings/{id:guid}/mark-paid")] public async Task>> MarkGuestBookingPaid(Guid id, [FromBody] MarkGuestBookingPaidRequest request) => Response(await _bookingService.MarkGuestBookingPaid(new { bookingId = id, amountCharged = request.AmountCharged }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("bookings")] [HttpPost("api/client/bookings/{id:guid}/cancel")] public async Task>> CancelBooking(Guid id) => Response(await _bookingService.CancelBooking(new { bookingId = id }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("bookings")] [HttpPut("api/client/bookings/{id:guid}/reassign")] public async Task>> ReassignBooking(Guid id, [FromBody] ReassignBookingRequest request) => Response(await _bookingService.ReassignBooking(new { bookingId = id, guestName = request.GuestName, guestPhone = request.GuestPhone }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("bookings")] [HttpPut("api/client/bookings/{id:guid}/reschedule")] public async Task>> RescheduleBooking(Guid id, [FromBody] RescheduleBookingRequest request) => Response(await _bookingService.RescheduleBooking(new { bookingId = id, startTime = request.StartTime?.ToString("o"), durationMinutes = request.DurationMinutes, facilitySessionId = request.FacilitySessionId }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("bookings")] [HttpPost("api/client/bookings/{id:guid}/no-show")] public async Task>> MarkNoShow(Guid id) => Response(await _bookingService.MarkNoShow(new { bookingId = id }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("bookings")] [HttpPost("api/client/bookings/{id:guid}/complete")] public async Task>> CompleteBooking(Guid id) => Response(await _bookingService.CompleteBooking(new { bookingId = id }, HttpContext)); } }