// Package handlers package handlers import ( "encoding/json" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/photoplaces/backend/internal/middleware" "github.com/photoplaces/backend/internal/models" "github.com/photoplaces/backend/internal/repository" "github.com/photoplaces/backend/internal/validator" ) type BookingHandler struct { bookingRepo *repository.BookingRepo placeRepo *repository.PlaceRepo } func NewBookingHandler(bookingRepo *repository.BookingRepo, placeRepo *repository.PlaceRepo) *BookingHandler { return &BookingHandler{bookingRepo: bookingRepo, placeRepo: placeRepo} } type createBookingRequest struct { PlaceID string `json:"place_id" validate:"required,uuid"` StartTime string `json:"start_time" validate:"required,datetime=2006-01-02T15:04:05Z07:00"` EndTime string `json:"end_time" validate:"required,datetime=2006-01-02T15:04:05Z07:00"` Comment *string `json:"comment" validate:"omitempty,max=1000"` } func (h *BookingHandler) Create(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) var req createBookingRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if err := validator.Validate(req); err != nil { writeValidationError(w, err) return } start, _ := time.Parse(time.RFC3339, req.StartTime) end, _ := time.Parse(time.RFC3339, req.EndTime) if !end.After(start) { writeError(w, http.StatusBadRequest, "end_time must be after start_time") return } available, err := h.bookingRepo.IsTimeSlotAvailable(r.Context(), req.PlaceID, start, end) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if !available { writeError(w, http.StatusConflict, "time slot is not available") return } totalPrice, err := h.bookingRepo.CalculateTotalPrice(r.Context(), req.PlaceID, start, end) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } booking := &models.Booking{ PlaceID: req.PlaceID, UserID: userID, StartTime: start, EndTime: end, TotalPrice: totalPrice, Currency: "RUB", Comment: strPtr(req.Comment), } if err := h.bookingRepo.Create(r.Context(), booking); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusCreated, booking) } func (h *BookingHandler) ListMy(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) bookings, err := h.bookingRepo.ListByUser(r.Context(), userID) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if bookings == nil { bookings = []*models.Booking{} } writeJSON(w, http.StatusOK, map[string]interface{}{"data": bookings}) } func (h *BookingHandler) Cancel(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") userID := middleware.GetUserID(r.Context()) booking, err := h.bookingRepo.GetByID(r.Context(), id) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if booking == nil { writeError(w, http.StatusNotFound, "booking not found") return } if booking.UserID != userID { writeError(w, http.StatusForbidden, "not your booking") return } if err := h.bookingRepo.UpdateStatus(r.Context(), id, "cancelled"); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]string{"status": "cancelled"}) } func (h *BookingHandler) Confirm(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") if err := h.bookingRepo.UpdateStatus(r.Context(), id, "confirmed"); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]string{"status": "confirmed"}) }