bookings.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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. }
  18. func NewBookingHandler(bookingRepo *repository.BookingRepo, appEnv string) *BookingHandler {
  19. return &BookingHandler{bookingRepo: bookingRepo}
  20. }
  21. type createBookingRequest struct {
  22. PlaceID string `json:"place_id" validate:"required,uuid"`
  23. StartTime string `json:"start_time" validate:"required,datetime=2006-01-02T15:04:05Z07:00"`
  24. EndTime string `json:"end_time" validate:"required,datetime=2006-01-02T15:04:05Z07:00"`
  25. Comment *string `json:"comment" validate:"omitempty,max=1000"`
  26. }
  27. func (h *BookingHandler) Create(w http.ResponseWriter, r *http.Request) {
  28. userID := middleware.GetUserID(r.Context())
  29. var req createBookingRequest
  30. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  31. writeError(w, http.StatusBadRequest, "invalid request body", err)
  32. return
  33. }
  34. if err := validator.Validate(req); err != nil {
  35. writeValidationError(w, err)
  36. return
  37. }
  38. start, err := time.Parse(time.RFC3339, req.StartTime)
  39. if err != nil {
  40. writeError(w, http.StatusBadRequest, "invalid start_time format, expected RFC3339", err)
  41. return
  42. }
  43. end, err := time.Parse(time.RFC3339, req.EndTime)
  44. if err != nil {
  45. writeError(w, http.StatusBadRequest, "invalid end_time format, expected RFC3339", err)
  46. return
  47. }
  48. if !end.After(start) {
  49. writeError(w, http.StatusBadRequest, "end_time must be after start_time", nil)
  50. return
  51. }
  52. booking := &models.Booking{
  53. PlaceID: req.PlaceID,
  54. UserID: userID,
  55. StartTime: start,
  56. EndTime: end,
  57. Comment: req.Comment,
  58. }
  59. if err := h.bookingRepo.Create(r.Context(), booking); err != nil {
  60. if errors.Is(err, repository.ErrPlaceNotFound) {
  61. writeError(w, http.StatusNotFound, "place not found", nil)
  62. return
  63. }
  64. var pgErr *pgconn.PgError
  65. if errors.As(err, &pgErr) && pgErr.Code == "23P01" { // exclusion_constraint_violation
  66. writeError(w, http.StatusConflict, "time slot is not available", err)
  67. return
  68. }
  69. writeError(w, http.StatusInternalServerError, "failed to create booking", err)
  70. return
  71. }
  72. writeJSON(w, http.StatusCreated, booking)
  73. }
  74. func (h *BookingHandler) ListMy(w http.ResponseWriter, r *http.Request) {
  75. userID := middleware.GetUserID(r.Context())
  76. bookings, err := h.bookingRepo.ListByUser(r.Context(), userID)
  77. if err != nil {
  78. writeError(w, http.StatusInternalServerError, "failed to list bookings", err)
  79. return
  80. }
  81. if bookings == nil {
  82. bookings = []*models.Booking{}
  83. }
  84. writeJSON(w, http.StatusOK, map[string]interface{}{"data": bookings})
  85. }
  86. func (h *BookingHandler) Cancel(w http.ResponseWriter, r *http.Request) {
  87. id := chi.URLParam(r, "id")
  88. userID := middleware.GetUserID(r.Context())
  89. booking, err := h.bookingRepo.GetByID(r.Context(), id)
  90. if err != nil {
  91. writeError(w, http.StatusInternalServerError, "failed to get booking", err)
  92. return
  93. }
  94. if booking == nil {
  95. writeError(w, http.StatusNotFound, "booking not found", nil)
  96. return
  97. }
  98. if booking.UserID != userID {
  99. writeError(w, http.StatusForbidden, "not your booking", nil)
  100. return
  101. }
  102. if err := h.bookingRepo.UpdateStatus(r.Context(), id, "cancelled"); err != nil {
  103. writeError(w, http.StatusInternalServerError, "failed to cancel booking", err)
  104. return
  105. }
  106. writeJSON(w, http.StatusOK, map[string]string{"status": "cancelled"})
  107. }
  108. func (h *BookingHandler) Confirm(w http.ResponseWriter, r *http.Request) {
  109. id := chi.URLParam(r, "id")
  110. if err := h.bookingRepo.UpdateStatus(r.Context(), id, "confirmed"); err != nil {
  111. writeError(w, http.StatusInternalServerError, "failed to confirm booking", err)
  112. return
  113. }
  114. writeJSON(w, http.StatusOK, map[string]string{"status": "confirmed"})
  115. }