package rating import ( "encoding/json" "net/http" "strconv" "api_yal/internal/logger" "api_yal/internal/middleware" "api_yal/internal/models" "github.com/go-chi/chi/v5" "go.uber.org/zap" ) type RatingHandler struct { service RatingService } // NewRatingHandler создает новый обработчик рейтингов func NewRatingHandler(service RatingService) *RatingHandler { return &RatingHandler{ service: service, } } // CreateRating создает новый рейтинг // @Summary Create a new rating // @Tags ratings // @Accept json // @Produce json // @Param request body CreateRatingRequest true "Rating data" // @Success 201 {object} RatingResponse // @Router /ratings [post] func (h *RatingHandler) CreateRating(w http.ResponseWriter, r *http.Request) { l := logger.Get() userID, ok := middleware.GetUserID(r.Context()) if !ok { l.Debug("Unauthorized: user ID not found in context") http.Error(w, "Unauthorized", http.StatusUnauthorized) return } var req CreateRatingRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { l.Debug("Invalid request body", zap.Error(err)) http.Error(w, "Invalid request body", http.StatusBadRequest) return } rating, err := h.service.CreateRating(r.Context(), userID, &req) if err != nil { l.Error("Failed to create rating", zap.Error(err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(rating) } // GetRatingByID возвращает рейтинг по ID // @Summary Get rating by ID // @Tags ratings // @Produce json // @Param id path int true "Rating ID" // @Success 200 {object} RatingResponse // @Router /ratings/{id} [get] func (h *RatingHandler) GetRatingByID(w http.ResponseWriter, r *http.Request) { l := logger.Get() idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { http.Error(w, "Invalid rating ID", http.StatusBadRequest) return } rating, err := h.service.GetRatingByID(r.Context(), uint(id)) if err != nil { l.Error("Failed to get rating", zap.Error(err)) http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(rating) } // GetRatingByObjectAndPlatform возвращает рейтинг объекта по платформе // @Summary Get rating by object ID and platform // @Tags ratings // @Produce json // @Param objectID path int true "Object ID" // @Param platform path string true "Platform (entrepreneur/tourist)" // @Success 200 {object} RatingResponse // @Router /ratings/object/{objectID}/platform/{platform} [get] func (h *RatingHandler) GetRatingByObjectAndPlatform(w http.ResponseWriter, r *http.Request) { l := logger.Get() objectIDStr := chi.URLParam(r, "objectID") objectID, err := strconv.ParseUint(objectIDStr, 10, 32) if err != nil { http.Error(w, "Invalid object ID", http.StatusBadRequest) return } platform := models.PlatformType(chi.URLParam(r, "platform")) if platform != models.PlatformEntrepreneur && platform != models.PlatformTourist { http.Error(w, "Invalid platform", http.StatusBadRequest) return } rating, err := h.service.GetRatingByObjectAndPlatform(r.Context(), uint(objectID), platform) if err != nil { l.Error("Failed to get rating", zap.Error(err)) http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(rating) } // UpdateRating обновляет рейтинг // @Summary Update rating // @Tags ratings // @Accept json // @Produce json // @Param id path int true "Rating ID" // @Param request body UpdateRatingRequest true "Update data" // @Success 200 {object} RatingResponse // @Router /ratings/{id} [put] func (h *RatingHandler) UpdateRating(w http.ResponseWriter, r *http.Request) { l := logger.Get() idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { http.Error(w, "Invalid rating ID", http.StatusBadRequest) return } var req UpdateRatingRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { l.Debug("Invalid request body", zap.Error(err)) http.Error(w, "Invalid request body", http.StatusBadRequest) return } rating, err := h.service.UpdateRating(r.Context(), uint(id), &req) if err != nil { l.Error("Failed to update rating", zap.Error(err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(rating) } // DeleteRating удаляет рейтинг // @Summary Delete rating // @Tags ratings // @Param id path int true "Rating ID" // @Success 204 "No Content" // @Router /ratings/{id} [delete] func (h *RatingHandler) DeleteRating(w http.ResponseWriter, r *http.Request) { l := logger.Get() idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { http.Error(w, "Invalid rating ID", http.StatusBadRequest) return } if err := h.service.DeleteRating(r.Context(), uint(id)); err != nil { l.Error("Failed to delete rating", zap.Error(err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } // ListRatings возвращает список рейтингов // @Summary List ratings // @Tags ratings // @Produce json // @Param page query int false "Page number" default(1) // @Param page_size query int false "Page size" default(20) // @Param platform query string false "Filter by platform" // @Param owner_id query int false "Filter by owner ID" // @Param object_id query int false "Filter by object ID" // @Success 200 {object} ListRatingsResponse // @Router /ratings [get] func (h *RatingHandler) ListRatings(w http.ResponseWriter, r *http.Request) { l := logger.Get() req := &ListRatingsRequest{} // Парсинг query параметров if pageStr := r.URL.Query().Get("page"); pageStr != "" { if page, err := strconv.Atoi(pageStr); err == nil && page > 0 { req.Page = page } } if pageSizeStr := r.URL.Query().Get("page_size"); pageSizeStr != "" { if pageSize, err := strconv.Atoi(pageSizeStr); err == nil && pageSize > 0 { req.PageSize = pageSize } } req.Platform = r.URL.Query().Get("platform") if ownerIDStr := r.URL.Query().Get("owner_id"); ownerIDStr != "" { if ownerID, err := strconv.ParseUint(ownerIDStr, 10, 32); err == nil { req.OwnerID = uint(ownerID) } } if objectIDStr := r.URL.Query().Get("object_id"); objectIDStr != "" { if objectID, err := strconv.ParseUint(objectIDStr, 10, 32); err == nil { req.ObjectID = uint(objectID) } } var response *ListRatingsResponse var err error if req.ObjectID > 0 { // Получаем все рейтинги объекта ratings, err := h.service.GetRatingsByObject(r.Context(), req.ObjectID) if err != nil { l.Error("Failed to get ratings by object", zap.Error(err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } response = &ListRatingsResponse{ Ratings: ratings, Total: int64(len(ratings)), Page: 1, PageSize: len(ratings), TotalPages: 1, } } else if req.OwnerID > 0 { response, err = h.service.GetRatingsByOwner(r.Context(), req.OwnerID, req) } else { response, err = h.service.ListRatings(r.Context(), req) } if err != nil { l.Error("Failed to list ratings", zap.Error(err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // Vote голосование за объект // @Summary Vote for an object // @Tags ratings // @Accept json // @Produce json // @Param request body VoteRequest true "Vote data" // @Param targetID path int true "Target object ID" // @Param platform path string true "Platform" // @Success 200 {object} RatingResponse // @Router /ratings/{targetID}/vote/{platform} [post] func (h *RatingHandler) Vote(w http.ResponseWriter, r *http.Request) { l := logger.Get() userID, ok := middleware.GetUserID(r.Context()) if !ok { l.Debug("Unauthorized: user ID not found in context") http.Error(w, "Unauthorized", http.StatusUnauthorized) return } targetIDStr := chi.URLParam(r, "targetID") targetID, err := strconv.ParseUint(targetIDStr, 10, 32) if err != nil { http.Error(w, "Invalid target ID", http.StatusBadRequest) return } platform := models.PlatformType(chi.URLParam(r, "platform")) if platform != models.PlatformEntrepreneur && platform != models.PlatformTourist { http.Error(w, "Invalid platform", http.StatusBadRequest) return } var req VoteRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { l.Debug("Invalid request body", zap.Error(err)) http.Error(w, "Invalid request body", http.StatusBadRequest) return } rating, err := h.service.Vote(r.Context(), userID, uint(targetID), platform, req.Score) if err != nil { l.Error("Failed to vote", zap.Error(err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(rating) } // GetMyVote возвращает голос текущего пользователя // @Summary Get current user's vote // @Tags ratings // @Produce json // @Param targetID path int true "Target object ID" // @Param platform path string true "Platform" // @Success 200 {object} UserRatingInfoResponse // @Router /ratings/{targetID}/my-vote/{platform} [get] func (h *RatingHandler) GetMyVote(w http.ResponseWriter, r *http.Request) { l := logger.Get() userID, ok := middleware.GetUserID(r.Context()) if !ok { l.Debug("Unauthorized: user ID not found in context") http.Error(w, "Unauthorized", http.StatusUnauthorized) return } targetIDStr := chi.URLParam(r, "targetID") targetID, err := strconv.ParseUint(targetIDStr, 10, 32) if err != nil { http.Error(w, "Invalid target ID", http.StatusBadRequest) return } platform := models.PlatformType(chi.URLParam(r, "platform")) if platform != models.PlatformEntrepreneur && platform != models.PlatformTourist { http.Error(w, "Invalid platform", http.StatusBadRequest) return } voteInfo, err := h.service.GetUserVote(r.Context(), userID, uint(targetID), platform) if err != nil { l.Error("Failed to get user vote", zap.Error(err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(voteInfo) } // UpdateMyVote обновляет голос текущего пользователя // @Summary Update current user's vote // @Tags ratings // @Accept json // @Produce json // @Param targetID path int true "Target object ID" // @Param platform path string true "Platform" // @Param request body VoteRequest true "Vote data" // @Success 200 {object} RatingResponse // @Router /ratings/{targetID}/my-vote/{platform} [put] func (h *RatingHandler) UpdateMyVote(w http.ResponseWriter, r *http.Request) { l := logger.Get() userID, ok := middleware.GetUserID(r.Context()) if !ok { l.Debug("Unauthorized: user ID not found in context") http.Error(w, "Unauthorized", http.StatusUnauthorized) return } targetIDStr := chi.URLParam(r, "targetID") targetID, err := strconv.ParseUint(targetIDStr, 10, 32) if err != nil { http.Error(w, "Invalid target ID", http.StatusBadRequest) return } platform := models.PlatformType(chi.URLParam(r, "platform")) if platform != models.PlatformEntrepreneur && platform != models.PlatformTourist { http.Error(w, "Invalid platform", http.StatusBadRequest) return } var req VoteRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { l.Debug("Invalid request body", zap.Error(err)) http.Error(w, "Invalid request body", http.StatusBadRequest) return } rating, err := h.service.UpdateUserVote(r.Context(), userID, uint(targetID), platform, req.Score) if err != nil { l.Error("Failed to update vote", zap.Error(err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(rating) } // DeleteMyVote удаляет голос текущего пользователя // @Summary Delete current user's vote // @Tags ratings // @Param targetID path int true "Target object ID" // @Param platform path string true "Platform" // @Success 204 "No Content" // @Router /ratings/{targetID}/my-vote/{platform} [delete] func (h *RatingHandler) DeleteMyVote(w http.ResponseWriter, r *http.Request) { l := logger.Get() userID, ok := middleware.GetUserID(r.Context()) if !ok { l.Debug("Unauthorized: user ID not found in context") http.Error(w, "Unauthorized", http.StatusUnauthorized) return } targetIDStr := chi.URLParam(r, "targetID") targetID, err := strconv.ParseUint(targetIDStr, 10, 32) if err != nil { http.Error(w, "Invalid target ID", http.StatusBadRequest) return } platform := models.PlatformType(chi.URLParam(r, "platform")) if platform != models.PlatformEntrepreneur && platform != models.PlatformTourist { http.Error(w, "Invalid platform", http.StatusBadRequest) return } if err := h.service.DeleteUserVote(r.Context(), userID, uint(targetID), platform); err != nil { l.Error("Failed to delete vote", zap.Error(err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } // GetRatingStats возвращает статистику по рейтингам // @Summary Get rating statistics // @Tags ratings // @Produce json // @Success 200 {object} map[string]interface{} // @Router /ratings/stats [get] func (h *RatingHandler) GetRatingStats(w http.ResponseWriter, r *http.Request) { l := logger.Get() stats, err := h.service.GetRatingStats(r.Context()) if err != nil { l.Error("Failed to get rating stats", zap.Error(err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(stats) }