// handlers/review_handler.go package handlers import ( "api_bb/internal/models" "api_bb/internal/service" "api_bb/pkg/logger" "api_bb/pkg/middleware" "api_bb/pkg/utils" "net/http" "strconv" "github.com/go-chi/chi/v5" "go.uber.org/zap" ) // ReviewHandler обрабатывает HTTP-запросы, связанные с отзывами type ReviewHandler struct { reviewService service.ReviewService // Сервис для работы с отзывами logger logger.LoggerInterface // Логгер для записи событий } // NewReviewHandler создает новый экземпляр ReviewHandler func NewReviewHandler(reviewService service.ReviewService, logger logger.LoggerInterface) *ReviewHandler { return &ReviewHandler{ reviewService: reviewService, logger: logger, } } // GetReviews возвращает список отзывов с пагинацией и фильтрацией func (h *ReviewHandler) GetReviews(w http.ResponseWriter, r *http.Request) { page, _ := strconv.Atoi(r.URL.Query().Get("page")) limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) sortBy := r.URL.Query().Get("sort") filter := r.URL.Query().Get("filter") if page < 1 { page = 1 } if limit < 1 { limit = 6 } reviews, totalPages, err := h.reviewService.GetAllReviews(page, limit, sortBy, filter) if err != nil { h.logger.Error("Failed to get reviews", zap.Error(err)) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get reviews") return } response := map[string]interface{}{ "reviews": reviews, "current_page": page, "total_pages": totalPages, "total_items": len(reviews), } utils.RespondWithJSON(w, http.StatusOK, response) } // GetReviewsStats возвращает статистику отзывов func (h *ReviewHandler) GetReviewsStats(w http.ResponseWriter, r *http.Request) { stats, err := h.reviewService.GetReviewsStats() if err != nil { h.logger.Error("Failed to get reviews stats", zap.Error(err)) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get reviews statistics") return } utils.RespondWithJSON(w, http.StatusOK, stats) } // GetMyReviews возвращает отзывы текущего аутентифицированного пользователя func (h *ReviewHandler) GetMyReviews(w http.ResponseWriter, r *http.Request) { // Получаем ID пользователя из контекста (добавляется middleware аутентификации) userID, ok := r.Context().Value("middleware.UserIDKey").(uint) if !ok { h.logger.Warn("Failed to get userID from context in GetMyReviews", zap.String("path", r.URL.Path), zap.String("method", r.Method), zap.String("remote_addr", r.RemoteAddr), ) utils.RespondWithError(w, http.StatusUnauthorized, "User not authenticated") return } // Получаем отзывы пользователя из сервиса reviews, err := h.reviewService.GetUserReviews(userID) if err != nil { h.logger.With(zap.Int("userID", int(userID))).Error("Failed to get user reviews", zap.Error(err)) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get your reviews") return } utils.RespondWithJSON(w, http.StatusOK, reviews) } // CreateReview создает новый отзыв от имени текущего пользователя func (h *ReviewHandler) CreateReview(w http.ResponseWriter, r *http.Request) { // Получаем ID пользователя из контекста userID, ok := r.Context().Value(middleware.UserIDKey).(uint) if !ok { h.logger.Warn("Failed to get userID from context in CreateReview", zap.String("path", r.URL.Path), zap.String("method", r.Method), zap.String("remote_addr", r.RemoteAddr), zap.Uint("userID", userID), ) utils.RespondWithError(w, http.StatusUnauthorized, "User not authenticated") return } h.logger.Debug("Successfully extracted userID from context", zap.Uint("userID", userID), zap.String("path", r.URL.Path), zap.String("method", r.Method), ) // Декодируем тело запроса var req models.CreateReviewRequest if err := utils.DecodeJSONBody(w, r, &req); err != nil { h.logger.Error("Failed to decode review request", zap.Error(err), zap.Uint("userID", userID), ) utils.RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } // Создаем отзыв через сервис review, err := h.reviewService.CreateReview(&req, userID) if err != nil { h.logger.With(zap.Int("userID", int(userID))).Error("Failed to create review", zap.Error(err)) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create review") return } h.logger.Info("Review created successfully", zap.Uint("userID", userID), zap.Any("review_id", review.ID), ) utils.RespondWithJSON(w, http.StatusCreated, review) } // GetReviewByID возвращает отзыв по его идентификатору func (h *ReviewHandler) GetReviewByID(w http.ResponseWriter, r *http.Request) { // Получаем ID отзыва из параметров URL idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.RespondWithError(w, http.StatusBadRequest, "Invalid review ID") return } // Получаем отзыв из сервиса review, err := h.reviewService.GetReviewByID(uint(id)) if err != nil { h.logger.With(zap.Int("id", int(id))).Error("Failed to get review", zap.Error(err)) utils.RespondWithError(w, http.StatusNotFound, "Review not found") return } utils.RespondWithJSON(w, http.StatusOK, review) } // UpdateReview обновляет существующий отзыв func (h *ReviewHandler) UpdateReview(w http.ResponseWriter, r *http.Request) { // Получаем ID пользователя из контекста userID, ok := r.Context().Value(middleware.UserIDKey).(uint) if !ok { h.logger.Warn("Failed to get userID from context in UpdateReview", zap.String("path", r.URL.Path), zap.String("method", r.Method), zap.String("remote_addr", r.RemoteAddr), zap.Uint("userID", userID), ) utils.RespondWithError(w, http.StatusUnauthorized, "User not authenticated") return } // Получаем флаг администратора из контекста isAdmin, _ := r.Context().Value("IsAdmin").(bool) // Получаем ID отзыва из параметров URL idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.RespondWithError(w, http.StatusBadRequest, "Invalid review ID") return } // Декодируем тело запроса var req models.UpdateReviewRequest if err := utils.DecodeJSONBody(w, r, &req); err != nil { h.logger.Error("Failed to decode update review request", zap.Error(err), zap.Uint("userID", userID), zap.Uint("review_id", uint(id)), ) utils.RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } // Обновляем отзыв через сервис review, err := h.reviewService.UpdateReview(uint(id), &req, userID, isAdmin) if err != nil { h.logger.With(zap.Int("id", int(id))).With(zap.Int("userID", int(userID))).Error("Failed to update review", zap.Error(err)) if err.Error() == "unauthorized" { utils.RespondWithError(w, http.StatusForbidden, "You can only update your own reviews") return } utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update review") return } h.logger.Info("Review updated successfully", zap.Uint("userID", userID), zap.Uint("review_id", uint(id)), ) utils.RespondWithJSON(w, http.StatusOK, review) } // DeleteReview удаляет отзыв func (h *ReviewHandler) DeleteReview(w http.ResponseWriter, r *http.Request) { // Получаем ID пользователя из контекста userID, ok := r.Context().Value(middleware.UserIDKey).(uint) if !ok { h.logger.Warn("Failed to get userID from context in DeleteReview", zap.String("path", r.URL.Path), zap.String("method", r.Method), zap.String("remote_addr", r.RemoteAddr), ) utils.RespondWithError(w, http.StatusUnauthorized, "User not authenticated") return } // Получаем флаг администратора из контекста isAdmin, _ := r.Context().Value("IsAdmin").(bool) // Получаем ID отзыва из параметров URL idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.RespondWithError(w, http.StatusBadRequest, "Invalid review ID") return } // Удаляем отзыв через сервис err = h.reviewService.DeleteReview(uint(id), userID, isAdmin) if err != nil { h.logger.With(zap.Int("id", int(id))).With(zap.Int("userID", int(userID))).Error("Failed to delete review", zap.Error(err)) if err.Error() == "unauthorized" { utils.RespondWithError(w, http.StatusForbidden, "You can only delete your own reviews") return } utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete review") return } h.logger.Info("Review deleted successfully", zap.Uint("userID", userID), zap.Uint("review_id", uint(id)), ) utils.RespondWithJSON(w, http.StatusOK, map[string]string{"message": "Review deleted successfully"}) }