// Package handlers package handlers import ( "encoding/json" "errors" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/jackc/pgx/v5/pgconn" "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/middleware" "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/models" "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/repository" "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/validator" ) type BookingHandler struct { bookingRepo *repository.BookingRepo placeRepo *repository.PlaceRepo } func NewBookingHandler(bookingRepo *repository.BookingRepo, placeRepo *repository.PlaceRepo, appEnv string) *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", err) return } if err := validator.Validate(req); err != nil { writeValidationError(w, err) return } start, err := time.Parse(time.RFC3339, req.StartTime) if err != nil { writeError(w, http.StatusBadRequest, "invalid start_time format, expected RFC3339", err) return } end, err := time.Parse(time.RFC3339, req.EndTime) if err != nil { writeError(w, http.StatusBadRequest, "invalid end_time format, expected RFC3339", err) return } if !end.After(start) { writeError(w, http.StatusBadRequest, "end_time must be after start_time", nil) return } // Получаем данные места (включая hourly_rate и currency) через placeRepo place, err := h.placeRepo.GetByID(r.Context(), req.PlaceID) if err != nil { writeError(w, http.StatusInternalServerError, "failed to get place", err) return } if place == nil { writeError(w, http.StatusNotFound, "place not found", nil) return } // Рассчитываем цену var totalPrice *int if place.HourlyRate != nil { hours := int(end.Sub(start).Hours()) if hours < 1 { hours = 1 } price := *place.HourlyRate * hours totalPrice = &price } booking := &models.Booking{ PlaceID: req.PlaceID, UserID: userID, StartTime: start, EndTime: end, TotalPrice: totalPrice, Currency: place.Currency, Comment: req.Comment, } if err := h.bookingRepo.Create(r.Context(), booking); err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == "23P01" { // exclusion_constraint_violation writeError(w, http.StatusConflict, "time slot is not available", err) return } writeError(w, http.StatusInternalServerError, "failed to create booking", err) 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, "failed to list bookings", err) 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, "failed to get booking", err) return } if booking == nil { writeError(w, http.StatusNotFound, "booking not found", nil) return } if booking.UserID != userID { writeError(w, http.StatusForbidden, "not your booking", nil) return } if err := h.bookingRepo.UpdateStatus(r.Context(), id, "cancelled"); err != nil { writeError(w, http.StatusInternalServerError, "failed to cancel booking", err) 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, "failed to confirm booking", err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "confirmed"}) }