modified: begushiybashkir/bbvue/src/views/Reviews.vue
modified: serv_nginx/api_bb/internal/handlers/handlers.go new file: serv_nginx/api_bb/internal/handlers/review_handler.go new file: serv_nginx/api_bb/internal/models/review.go new file: serv_nginx/api_bb/internal/repository/review_repository.go modified: serv_nginx/api_bb/internal/routes/routes.go new file: serv_nginx/api_bb/internal/service/review_service.go set reviews router, handler, service, repository
This commit is contained in:
@@ -16,6 +16,7 @@ type Handler struct {
|
||||
userHandler *UserHandler
|
||||
avatarHandler *AvatarHandler
|
||||
newsHandler *NewsHandler
|
||||
reviewHandler *ReviewHandler
|
||||
// Здесь будут добавлены другие обработчики
|
||||
// userHandler *UserHandler
|
||||
// eventHandler *EventHandler
|
||||
@@ -27,6 +28,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
||||
userRepo := repository.NewUserRepository(db)
|
||||
newsRepo := repository.NewNewsRepository(db)
|
||||
commentRepo := repository.NewCommentRepository(db)
|
||||
reviewRepo := repository.NewReviewRepository(db)
|
||||
|
||||
// Initialize logger
|
||||
baseLogger := logger.NewWrapper(logger.Get()) // Создаем базовый логгер
|
||||
@@ -37,6 +39,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
||||
userService := service.NewUserService(userRepo, jwtService, baseLogger)
|
||||
avatarService := service.NewAvatarService(userRepo, baseLogger)
|
||||
newsService := service.NewNewsService(newsRepo, commentRepo, baseLogger)
|
||||
reviewService := service.NewReviewService(reviewRepo, baseLogger)
|
||||
|
||||
// Инициализация обработчиков
|
||||
healthHandler := NewHealthHandler()
|
||||
@@ -44,6 +47,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
||||
userHandler := NewUserHandler(&userService)
|
||||
newsHandler := NewNewsHandler(newsService, baseLogger)
|
||||
avatarHandler := NewAvatarHandler(avatarService)
|
||||
reviewHandler := NewReviewHandler(reviewService, baseLogger)
|
||||
|
||||
return &Handler{
|
||||
healthHandler: healthHandler,
|
||||
@@ -51,6 +55,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
||||
userHandler: userHandler,
|
||||
newsHandler: newsHandler,
|
||||
avatarHandler: avatarHandler,
|
||||
reviewHandler: reviewHandler,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,3 +79,7 @@ func (h *Handler) AvatarHandler() *AvatarHandler {
|
||||
func (h *Handler) NewsHandler() *NewsHandler {
|
||||
return h.newsHandler
|
||||
}
|
||||
|
||||
func (h *Handler) ReviewHandler() *ReviewHandler {
|
||||
return h.reviewHandler
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
// handlers/review_handler.go
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"api_bb/internal/models"
|
||||
"api_bb/internal/service"
|
||||
"api_bb/pkg/logger"
|
||||
"api_bb/pkg/utils"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ReviewHandler struct {
|
||||
reviewService service.ReviewService
|
||||
logger logger.LoggerInterface
|
||||
}
|
||||
|
||||
func NewReviewHandler(reviewService service.ReviewService, logger logger.LoggerInterface) *ReviewHandler {
|
||||
return &ReviewHandler{
|
||||
reviewService: reviewService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ReviewHandler) Routes() chi.Router {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Get("/", h.GetReviews)
|
||||
r.Get("/stats", h.GetReviewsStats)
|
||||
r.Get("/my", h.GetMyReviews)
|
||||
r.Post("/", h.CreateReview)
|
||||
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
r.Get("/", h.GetReviewByID)
|
||||
r.Put("/", h.UpdateReview)
|
||||
r.Delete("/", h.DeleteReview)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// 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) {
|
||||
userID, ok := r.Context().Value("userID").(uint)
|
||||
if !ok {
|
||||
utils.RespondWithError(w, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
reviews, err := h.reviewService.GetUserReviews(userID)
|
||||
if err != nil {
|
||||
h.logger.With(zap.String("userID", string(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) {
|
||||
userID, ok := r.Context().Value("userID").(uint)
|
||||
if !ok {
|
||||
utils.RespondWithError(w, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.CreateReviewRequest
|
||||
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
|
||||
h.logger.Error("Failed to decode review request", zap.Error(err))
|
||||
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
review, err := h.reviewService.CreateReview(&req, userID)
|
||||
if err != nil {
|
||||
h.logger.With(zap.String("userID", string(userID))).Error("Failed to create review", zap.Error(err))
|
||||
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create review")
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespondWithJSON(w, http.StatusCreated, review)
|
||||
}
|
||||
|
||||
// GetReviewByID возвращает отзыв по ID
|
||||
func (h *ReviewHandler) GetReviewByID(w http.ResponseWriter, r *http.Request) {
|
||||
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.String("id", string(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) {
|
||||
userID, ok := r.Context().Value("userID").(uint)
|
||||
if !ok {
|
||||
utils.RespondWithError(w, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
isAdmin, _ := r.Context().Value("isAdmin").(bool)
|
||||
|
||||
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))
|
||||
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
|
||||
}
|
||||
|
||||
utils.RespondWithJSON(w, http.StatusOK, review)
|
||||
}
|
||||
|
||||
// DeleteReview удаляет отзыв
|
||||
func (h *ReviewHandler) DeleteReview(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := r.Context().Value("userID").(uint)
|
||||
if !ok {
|
||||
utils.RespondWithError(w, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
isAdmin, _ := r.Context().Value("isAdmin").(bool)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
utils.RespondWithJSON(w, http.StatusOK, map[string]string{"message": "Review deleted successfully"})
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// models/review.go
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Review struct {
|
||||
ID uint `json:"id" gorm:"primarykey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
||||
|
||||
Rating int `json:"rating" gorm:"not null;check:rating >= 1 AND rating <= 5"`
|
||||
Text string `json:"text" gorm:"type:text;not null"`
|
||||
Achievement string `json:"achievement" gorm:"size:255"`
|
||||
Distance string `json:"distance" gorm:"size:50"`
|
||||
Improvement string `json:"improvement" gorm:"size:100"`
|
||||
Trainings int `json:"trainings" gorm:"default:0"`
|
||||
Verified bool `json:"verified" gorm:"default:false"`
|
||||
|
||||
// Связи
|
||||
AuthorID uint `json:"author_id" gorm:"not null"`
|
||||
Author User `json:"author" gorm:"foreignKey:AuthorID"`
|
||||
}
|
||||
|
||||
// DTO для создания отзыва
|
||||
type CreateReviewRequest struct {
|
||||
Rating int `json:"rating" validate:"required,min=1,max=5"`
|
||||
Text string `json:"text" validate:"required,min=10,max=500"`
|
||||
Achievement string `json:"achievement" validate:"max=255"`
|
||||
Distance string `json:"distance" validate:"max=50"`
|
||||
Improvement string `json:"improvement" validate:"max=100"`
|
||||
Trainings int `json:"trainings" validate:"min=0"`
|
||||
}
|
||||
|
||||
// DTO для обновления отзыва
|
||||
type UpdateReviewRequest struct {
|
||||
Rating int `json:"rating" validate:"omitempty,min=1,max=5"`
|
||||
Text string `json:"text" validate:"omitempty,min=10,max=500"`
|
||||
Achievement string `json:"achievement" validate:"omitempty,max=255"`
|
||||
Distance string `json:"distance" validate:"omitempty,max=50"`
|
||||
Improvement string `json:"improvement" validate:"omitempty,max=100"`
|
||||
Trainings int `json:"trainings" validate:"omitempty,min=0"`
|
||||
}
|
||||
|
||||
// DTO для ответа с отзывом
|
||||
type ReviewResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Rating int `json:"rating"`
|
||||
Text string `json:"text"`
|
||||
Achievement string `json:"achievement,omitempty"`
|
||||
Distance string `json:"distance,omitempty"`
|
||||
Improvement string `json:"improvement,omitempty"`
|
||||
Trainings int `json:"trainings"`
|
||||
Verified bool `json:"verified"`
|
||||
Author AuthorInfo `json:"author"`
|
||||
}
|
||||
|
||||
// DTO для статистики отзывов
|
||||
type ReviewsStatsResponse struct {
|
||||
TotalReviews int `json:"total_reviews"`
|
||||
AverageRating float64 `json:"average_rating"`
|
||||
SuccessStories int `json:"success_stories"`
|
||||
RatingDistribution map[int]int `json:"rating_distribution"`
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// repository/review_repository.go
|
||||
package repository
|
||||
|
||||
import (
|
||||
"api_bb/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ReviewRepository interface {
|
||||
Create(review *models.Review) error
|
||||
GetByID(id uint) (*models.Review, error)
|
||||
GetAll(page, limit int, sortBy, filter string) ([]models.Review, int64, error)
|
||||
GetByAuthorID(authorID uint) ([]models.Review, error)
|
||||
Update(review *models.Review) error
|
||||
Delete(id uint) error
|
||||
GetStats() (*models.ReviewsStatsResponse, error)
|
||||
GetRatingDistribution() (map[int]int, error)
|
||||
}
|
||||
|
||||
type reviewRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewReviewRepository(db *gorm.DB) ReviewRepository {
|
||||
return &reviewRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *reviewRepository) Create(review *models.Review) error {
|
||||
return r.db.Create(review).Error
|
||||
}
|
||||
|
||||
func (r *reviewRepository) GetByID(id uint) (*models.Review, error) {
|
||||
var review models.Review
|
||||
err := r.db.Preload("Author").First(&review, id).Error
|
||||
return &review, err
|
||||
}
|
||||
|
||||
func (r *reviewRepository) GetAll(page, limit int, sortBy, filter string) ([]models.Review, int64, error) {
|
||||
var reviews []models.Review
|
||||
var total int64
|
||||
|
||||
query := r.db.Model(&models.Review{}).Preload("Author")
|
||||
|
||||
// Применяем фильтрацию по рейтингу
|
||||
if filter != "" && filter != "all" {
|
||||
query = query.Where("rating >= ?", filter)
|
||||
}
|
||||
|
||||
// Считаем общее количество
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Применяем сортировку
|
||||
switch sortBy {
|
||||
case "newest":
|
||||
query = query.Order("created_at DESC")
|
||||
case "oldest":
|
||||
query = query.Order("created_at ASC")
|
||||
case "highest":
|
||||
query = query.Order("rating DESC, created_at DESC")
|
||||
case "lowest":
|
||||
query = query.Order("rating ASC, created_at DESC")
|
||||
default:
|
||||
query = query.Order("created_at DESC")
|
||||
}
|
||||
|
||||
// Применяем пагинацию
|
||||
offset := (page - 1) * limit
|
||||
err := query.Offset(offset).Limit(limit).Find(&reviews).Error
|
||||
|
||||
return reviews, total, err
|
||||
}
|
||||
|
||||
func (r *reviewRepository) GetByAuthorID(authorID uint) ([]models.Review, error) {
|
||||
var reviews []models.Review
|
||||
err := r.db.Where("author_id = ?", authorID).Preload("Author").Find(&reviews).Error
|
||||
return reviews, err
|
||||
}
|
||||
|
||||
func (r *reviewRepository) Update(review *models.Review) error {
|
||||
return r.db.Save(review).Error
|
||||
}
|
||||
|
||||
func (r *reviewRepository) Delete(id uint) error {
|
||||
return r.db.Delete(&models.Review{}, id).Error
|
||||
}
|
||||
|
||||
func (r *reviewRepository) GetStats() (*models.ReviewsStatsResponse, error) {
|
||||
var totalReviews int64
|
||||
var averageRating float64
|
||||
var successStories int64
|
||||
|
||||
// Общее количество отзывов
|
||||
if err := r.db.Model(&models.Review{}).Count(&totalReviews).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Средний рейтинг
|
||||
if err := r.db.Model(&models.Review{}).Select("AVG(rating)").Row().Scan(&averageRating); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Количество успешных историй (отзывы с рейтингом >= 4 и достижениями)
|
||||
if err := r.db.Model(&models.Review{}).
|
||||
Where("rating >= ? AND achievement != ?", 4, "").
|
||||
Count(&successStories).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Распределение по рейтингам
|
||||
ratingDistribution, err := r.GetRatingDistribution()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.ReviewsStatsResponse{
|
||||
TotalReviews: int(totalReviews),
|
||||
AverageRating: averageRating,
|
||||
SuccessStories: int(successStories),
|
||||
RatingDistribution: ratingDistribution,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *reviewRepository) GetRatingDistribution() (map[int]int, error) {
|
||||
var results []struct {
|
||||
Rating int
|
||||
Count int
|
||||
}
|
||||
|
||||
err := r.db.Model(&models.Review{}).
|
||||
Select("rating, COUNT(*) as count").
|
||||
Group("rating").
|
||||
Order("rating DESC").
|
||||
Scan(&results).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
distribution := make(map[int]int)
|
||||
for _, result := range results {
|
||||
distribution[result.Rating] = result.Count
|
||||
}
|
||||
|
||||
// Заполняем отсутствующие рейтинги нулями
|
||||
for i := 1; i <= 5; i++ {
|
||||
if _, exists := distribution[i]; !exists {
|
||||
distribution[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
return distribution, nil
|
||||
}
|
||||
@@ -86,6 +86,24 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
||||
})
|
||||
})
|
||||
|
||||
// Маршруты для отзывов
|
||||
r.Route("/reviews", func(r chi.Router) {
|
||||
// Публичные маршруты
|
||||
r.Get("/", allHandler.ReviewHandler().GetReviews)
|
||||
r.Get("/stats", allHandler.ReviewHandler().GetReviewsStats)
|
||||
r.Get("/{id}", allHandler.ReviewHandler().GetReviewByID)
|
||||
|
||||
// Защищенные маршруты
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middleware.AuthMiddleware(jwtService, userRepo))
|
||||
r.Use(middleware.RequireAuth)
|
||||
|
||||
r.Post("/", allHandler.ReviewHandler().CreateReview)
|
||||
r.Get("/my", allHandler.ReviewHandler().GetMyReviews)
|
||||
r.Put("/{id}", allHandler.ReviewHandler().UpdateReview)
|
||||
r.Delete("/{id}", allHandler.ReviewHandler().DeleteReview)
|
||||
})
|
||||
})
|
||||
// Здесь будут добавлены другие маршруты:
|
||||
// r.Mount("/events", eventHandler.Routes())
|
||||
// r.Mount("/reviews", reviewHandler.Routes())
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
// service/review_service.go
|
||||
package service
|
||||
|
||||
import (
|
||||
"api_bb/internal/models"
|
||||
"api_bb/internal/repository"
|
||||
"api_bb/pkg/logger"
|
||||
"errors"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ReviewService interface {
|
||||
CreateReview(req *models.CreateReviewRequest, authorID uint) (*models.ReviewResponse, error)
|
||||
GetReviewByID(id uint) (*models.ReviewResponse, error)
|
||||
GetAllReviews(page, limit int, sortBy, filter string) ([]models.ReviewResponse, int, error)
|
||||
GetUserReviews(userID uint) ([]models.ReviewResponse, error)
|
||||
UpdateReview(id uint, req *models.UpdateReviewRequest, userID uint, isAdmin bool) (*models.ReviewResponse, error)
|
||||
DeleteReview(id uint, userID uint, isAdmin bool) error
|
||||
GetReviewsStats() (*models.ReviewsStatsResponse, error)
|
||||
}
|
||||
|
||||
type reviewService struct {
|
||||
reviewRepo repository.ReviewRepository
|
||||
logger logger.LoggerInterface
|
||||
}
|
||||
|
||||
func NewReviewService(reviewRepo repository.ReviewRepository, logger logger.LoggerInterface) ReviewService {
|
||||
return &reviewService{
|
||||
reviewRepo: reviewRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *reviewService) CreateReview(req *models.CreateReviewRequest, authorID uint) (*models.ReviewResponse, error) {
|
||||
review := &models.Review{
|
||||
Rating: req.Rating,
|
||||
Text: req.Text,
|
||||
Achievement: req.Achievement,
|
||||
Distance: req.Distance,
|
||||
Improvement: req.Improvement,
|
||||
Trainings: req.Trainings,
|
||||
AuthorID: authorID,
|
||||
Verified: false, // По умолчанию непроверенный
|
||||
}
|
||||
|
||||
if err := s.reviewRepo.Create(review); err != nil {
|
||||
s.logger.Error("Failed to create review", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Получаем созданный отзыв с информацией об авторе
|
||||
createdReview, err := s.reviewRepo.GetByID(review.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get created review", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.toReviewResponse(createdReview), nil
|
||||
}
|
||||
|
||||
func (s *reviewService) GetReviewByID(id uint) (*models.ReviewResponse, error) {
|
||||
review, err := s.reviewRepo.GetByID(id)
|
||||
if err != nil {
|
||||
s.logger.With(zap.Int("id", int(id))).Error("Failed to get review by ID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.toReviewResponse(review), nil
|
||||
}
|
||||
|
||||
func (s *reviewService) GetAllReviews(page, limit int, sortBy, filter string) ([]models.ReviewResponse, int, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if limit < 1 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
reviews, total, err := s.reviewRepo.GetAll(page, limit, sortBy, filter)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get all reviews", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
responses := make([]models.ReviewResponse, len(reviews))
|
||||
for i, review := range reviews {
|
||||
responses[i] = *s.toReviewResponse(&review)
|
||||
}
|
||||
|
||||
totalPages := (int(total) + limit - 1) / limit
|
||||
|
||||
return responses, totalPages, nil
|
||||
}
|
||||
|
||||
func (s *reviewService) GetUserReviews(userID uint) ([]models.ReviewResponse, error) {
|
||||
reviews, err := s.reviewRepo.GetByAuthorID(userID)
|
||||
if err != nil {
|
||||
s.logger.With(zap.Int("userID", int(userID))).Error("Failed to get user reviews", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responses := make([]models.ReviewResponse, len(reviews))
|
||||
for i, review := range reviews {
|
||||
responses[i] = *s.toReviewResponse(&review)
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
func (s *reviewService) UpdateReview(id uint, req *models.UpdateReviewRequest, userID uint, isAdmin bool) (*models.ReviewResponse, error) {
|
||||
review, err := s.reviewRepo.GetByID(id)
|
||||
if err != nil {
|
||||
s.logger.With(zap.Int("id", int(id))).Error("Failed to get review for update", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Проверяем права доступа
|
||||
if review.AuthorID != userID && !isAdmin {
|
||||
s.logger.With(zap.Int("userID", int(userID))).With(zap.Int("reviewAuthorID", int(review.AuthorID))).Error("Unauthorized attempt to update review", zap.Error(err))
|
||||
}
|
||||
|
||||
// Обновляем поля
|
||||
if req.Rating != 0 {
|
||||
review.Rating = req.Rating
|
||||
}
|
||||
if req.Text != "" {
|
||||
review.Text = req.Text
|
||||
}
|
||||
if req.Achievement != "" {
|
||||
review.Achievement = req.Achievement
|
||||
}
|
||||
if req.Distance != "" {
|
||||
review.Distance = req.Distance
|
||||
}
|
||||
if req.Improvement != "" {
|
||||
review.Improvement = req.Improvement
|
||||
}
|
||||
if req.Trainings != 0 {
|
||||
review.Trainings = req.Trainings
|
||||
}
|
||||
|
||||
if err := s.reviewRepo.Update(review); err != nil {
|
||||
s.logger.With(zap.Int("id", int(id))).Error("Failed to update review", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Получаем обновленный отзыв
|
||||
updatedReview, err := s.reviewRepo.GetByID(id)
|
||||
if err != nil {
|
||||
s.logger.With(zap.Int("id", int(id))).Error("Failed to get updated review", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.toReviewResponse(updatedReview), nil
|
||||
}
|
||||
|
||||
func (s *reviewService) DeleteReview(id uint, userID uint, isAdmin bool) error {
|
||||
review, err := s.reviewRepo.GetByID(id)
|
||||
if err != nil {
|
||||
s.logger.With(zap.Int("id", int(id))).Error("Failed to get review for deletion", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// Проверяем права доступа
|
||||
if review.AuthorID != userID && !isAdmin {
|
||||
s.logger.With(zap.Int("userID", int(userID))).With(zap.Int("reviewAuthorID", int(review.AuthorID))).Error("Unauthorized attempt to delete review", zap.Error(err))
|
||||
return errors.New("unauthorized")
|
||||
}
|
||||
|
||||
return s.reviewRepo.Delete(id)
|
||||
}
|
||||
|
||||
func (s *reviewService) GetReviewsStats() (*models.ReviewsStatsResponse, error) {
|
||||
return s.reviewRepo.GetStats()
|
||||
}
|
||||
|
||||
func (s *reviewService) toReviewResponse(review *models.Review) *models.ReviewResponse {
|
||||
return &models.ReviewResponse{
|
||||
ID: review.ID,
|
||||
CreatedAt: review.CreatedAt,
|
||||
Rating: review.Rating,
|
||||
Text: review.Text,
|
||||
Achievement: review.Achievement,
|
||||
Distance: review.Distance,
|
||||
Improvement: review.Improvement,
|
||||
Trainings: review.Trainings,
|
||||
Verified: review.Verified,
|
||||
Author: models.AuthorInfo{
|
||||
ID: review.Author.ID,
|
||||
FirstName: review.Author.FirstName,
|
||||
LastName: review.Author.LastName,
|
||||
Email: review.Author.Email,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user