bookings.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. // Package handlers
  2. package handlers
  3. import (
  4. "encoding/json"
  5. "errors"
  6. "net/http"
  7. "time"
  8. "github.com/go-chi/chi/v5"
  9. "github.com/jackc/pgx/v5/pgconn"
  10. "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/middleware"
  11. "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/models"
  12. "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/repository"
  13. "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/validator"
  14. )
  15. type BookingHandler struct {
  16. bookingRepo *repository.BookingRepo
  17. placeRepo *repository.PlaceRepo
  18. }
  19. func NewBookingHandler(bookingRepo *repository.BookingRepo, placeRepo *repository.PlaceRepo, appEnv string) *BookingHandler {
  20. return &BookingHandler{bookingRepo: bookingRepo, placeRepo: placeRepo}
  21. }
  22. type createBookingRequest struct {
  23. PlaceID string `json:"place_id" validate:"required,uuid"`
  24. StartTime string `json:"start_time" validate:"required,datetime=2006-01-02T15:04:05Z07:00"`
  25. EndTime string `json:"end_time" validate:"required,datetime=2006-01-02T15:04:05Z07:00"`
  26. Comment *string `json:"comment" validate:"omitempty,max=1000"`
  27. }
  28. func (h *BookingHandler) Create(w http.ResponseWriter, r *http.Request) {
  29. userID := middleware.GetUserID(r.Context())
  30. var req createBookingRequest
  31. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  32. writeError(w, http.StatusBadRequest, "invalid request body", err)
  33. return
  34. }
  35. if err := validator.Validate(req); err != nil {
  36. writeValidationError(w, err)
  37. return
  38. }
  39. start, err := time.Parse(time.RFC3339, req.StartTime)
  40. if err != nil {
  41. writeError(w, http.StatusBadRequest, "invalid start_time format, expected RFC3339", err)
  42. return
  43. }
  44. end, err := time.Parse(time.RFC3339, req.EndTime)
  45. if err != nil {
  46. writeError(w, http.StatusBadRequest, "invalid end_time format, expected RFC3339", err)
  47. return
  48. }
  49. if !end.After(start) {
  50. writeError(w, http.StatusBadRequest, "end_time must be after start_time", nil)
  51. return
  52. }
  53. // Получаем данные места (включая hourly_rate и currency) через placeRepo
  54. place, err := h.placeRepo.GetByID(r.Context(), req.PlaceID)
  55. if err != nil {
  56. writeError(w, http.StatusInternalServerError, "failed to get place", err)
  57. return
  58. }
  59. if place == nil {
  60. writeError(w, http.StatusNotFound, "place not found", nil)
  61. return
  62. }
  63. // Рассчитываем цену
  64. var totalPrice *int
  65. if place.HourlyRate != nil {
  66. hours := int(end.Sub(start).Hours())
  67. if hours < 1 {
  68. hours = 1
  69. }
  70. price := *place.HourlyRate * hours
  71. totalPrice = &price
  72. }
  73. booking := &models.Booking{
  74. PlaceID: req.PlaceID,
  75. UserID: userID,
  76. StartTime: start,
  77. EndTime: end,
  78. TotalPrice: totalPrice,
  79. Currency: place.Currency,
  80. Comment: req.Comment,
  81. }
  82. if err := h.bookingRepo.Create(r.Context(), booking); err != nil {
  83. var pgErr *pgconn.PgError
  84. if errors.As(err, &pgErr) && pgErr.Code == "23P01" { // exclusion_constraint_violation
  85. writeError(w, http.StatusConflict, "time slot is not available", err)
  86. return
  87. }
  88. writeError(w, http.StatusInternalServerError, "failed to create booking", err)
  89. return
  90. }
  91. writeJSON(w, http.StatusCreated, booking)
  92. }
  93. func (h *BookingHandler) ListMy(w http.ResponseWriter, r *http.Request) {
  94. userID := middleware.GetUserID(r.Context())
  95. bookings, err := h.bookingRepo.ListByUser(r.Context(), userID)
  96. if err != nil {
  97. writeError(w, http.StatusInternalServerError, "failed to list bookings", err)
  98. return
  99. }
  100. if bookings == nil {
  101. bookings = []*models.Booking{}
  102. }
  103. writeJSON(w, http.StatusOK, map[string]interface{}{"data": bookings})
  104. }
  105. func (h *BookingHandler) Cancel(w http.ResponseWriter, r *http.Request) {
  106. id := chi.URLParam(r, "id")
  107. userID := middleware.GetUserID(r.Context())
  108. booking, err := h.bookingRepo.GetByID(r.Context(), id)
  109. if err != nil {
  110. writeError(w, http.StatusInternalServerError, "failed to get booking", err)
  111. return
  112. }
  113. if booking == nil {
  114. writeError(w, http.StatusNotFound, "booking not found", nil)
  115. return
  116. }
  117. if booking.UserID != userID {
  118. writeError(w, http.StatusForbidden, "not your booking", nil)
  119. return
  120. }
  121. if err := h.bookingRepo.UpdateStatus(r.Context(), id, "cancelled"); err != nil {
  122. writeError(w, http.StatusInternalServerError, "failed to cancel booking", err)
  123. return
  124. }
  125. writeJSON(w, http.StatusOK, map[string]string{"status": "cancelled"})
  126. }
  127. func (h *BookingHandler) Confirm(w http.ResponseWriter, r *http.Request) {
  128. id := chi.URLParam(r, "id")
  129. if err := h.bookingRepo.UpdateStatus(r.Context(), id, "confirmed"); err != nil {
  130. writeError(w, http.StatusInternalServerError, "failed to confirm booking", err)
  131. return
  132. }
  133. writeJSON(w, http.StatusOK, map[string]string{"status": "confirmed"})
  134. }