|
|
@@ -3,10 +3,12 @@ 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"
|
|
|
@@ -18,7 +20,7 @@ type BookingHandler struct {
|
|
|
placeRepo *repository.PlaceRepo
|
|
|
}
|
|
|
|
|
|
-func NewBookingHandler(bookingRepo *repository.BookingRepo, placeRepo *repository.PlaceRepo) *BookingHandler {
|
|
|
+func NewBookingHandler(bookingRepo *repository.BookingRepo, placeRepo *repository.PlaceRepo, appEnv string) *BookingHandler {
|
|
|
return &BookingHandler{bookingRepo: bookingRepo, placeRepo: placeRepo}
|
|
|
}
|
|
|
|
|
|
@@ -34,7 +36,7 @@ func (h *BookingHandler) Create(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
var req createBookingRequest
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
|
- writeError(w, http.StatusBadRequest, "invalid request body")
|
|
|
+ writeError(w, http.StatusBadRequest, "invalid request body", err)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -45,42 +47,32 @@ func (h *BookingHandler) Create(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
start, err := time.Parse(time.RFC3339, req.StartTime)
|
|
|
if err != nil {
|
|
|
- writeError(w, http.StatusBadRequest, "invalid start_time format, expected RFC3339")
|
|
|
+ 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")
|
|
|
+ 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")
|
|
|
- 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")
|
|
|
+ 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, err.Error())
|
|
|
+ writeError(w, http.StatusInternalServerError, "failed to get place", err)
|
|
|
return
|
|
|
}
|
|
|
if place == nil {
|
|
|
- writeError(w, http.StatusNotFound, "place not found")
|
|
|
+ writeError(w, http.StatusNotFound, "place not found", nil)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // Рассчитываем цену самостоятельно, без вызова CalculateTotalPrice (работает для всех типов мест)
|
|
|
+ // Рассчитываем цену
|
|
|
var totalPrice *int
|
|
|
if place.HourlyRate != nil {
|
|
|
hours := int(end.Sub(start).Hours())
|
|
|
@@ -102,7 +94,12 @@ func (h *BookingHandler) Create(w http.ResponseWriter, r *http.Request) {
|
|
|
}
|
|
|
|
|
|
if err := h.bookingRepo.Create(r.Context(), booking); err != nil {
|
|
|
- writeError(w, http.StatusInternalServerError, err.Error())
|
|
|
+ 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
|
|
|
}
|
|
|
|
|
|
@@ -114,7 +111,7 @@ func (h *BookingHandler) ListMy(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
bookings, err := h.bookingRepo.ListByUser(r.Context(), userID)
|
|
|
if err != nil {
|
|
|
- writeError(w, http.StatusInternalServerError, err.Error())
|
|
|
+ writeError(w, http.StatusInternalServerError, "failed to list bookings", err)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -131,20 +128,20 @@ func (h *BookingHandler) Cancel(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
booking, err := h.bookingRepo.GetByID(r.Context(), id)
|
|
|
if err != nil {
|
|
|
- writeError(w, http.StatusInternalServerError, err.Error())
|
|
|
+ writeError(w, http.StatusInternalServerError, "failed to get booking", err)
|
|
|
return
|
|
|
}
|
|
|
if booking == nil {
|
|
|
- writeError(w, http.StatusNotFound, "booking not found")
|
|
|
+ writeError(w, http.StatusNotFound, "booking not found", nil)
|
|
|
return
|
|
|
}
|
|
|
if booking.UserID != userID {
|
|
|
- writeError(w, http.StatusForbidden, "not your booking")
|
|
|
+ writeError(w, http.StatusForbidden, "not your booking", nil)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
if err := h.bookingRepo.UpdateStatus(r.Context(), id, "cancelled"); err != nil {
|
|
|
- writeError(w, http.StatusInternalServerError, err.Error())
|
|
|
+ writeError(w, http.StatusInternalServerError, "failed to cancel booking", err)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -155,7 +152,7 @@ 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())
|
|
|
+ writeError(w, http.StatusInternalServerError, "failed to confirm booking", err)
|
|
|
return
|
|
|
}
|
|
|
|