package comment import ( "api_yal/internal/logger" "api_yal/internal/util" "encoding/json" "net/http" "strconv" "github.com/go-chi/chi/v5" "go.uber.org/zap" ) type CommentHandler struct { service CommentService logger *zap.Logger } func NewCommentHandler(service CommentService) *CommentHandler { return &CommentHandler{ service: service, logger: logger.Get(), } } // CreateComment создает новый комментарий func (h *CommentHandler) CreateComment(w http.ResponseWriter, r *http.Request) { var req CreateCommentRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { h.logger.Error("Failed to decode request", zap.Error(err), ) util.ResponseWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid request body"}) return } // Получаем userID из контекста (устанавливается AuthMiddleware) userID, ok := r.Context().Value("user_id").(uint) if !ok { h.logger.Error("User ID not found in context") util.ResponseWithJSON(w, http.StatusUnauthorized, map[string]string{"error": "User not authenticated"}) return } comment, err := h.service.CreateComment(userID, &req) if err != nil { h.logger.Error("Failed to create comment", zap.Error(err), zap.Uint("user_id", userID), ) util.ResponseWithJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } util.ResponseWithJSON(w, http.StatusCreated, comment) } // GetCommentByID возвращает комментарий по ID func (h *CommentHandler) GetCommentByID(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { h.logger.Error("Invalid comment ID", zap.Error(err), zap.String("id", idStr), ) util.ResponseWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid comment ID"}) return } comment, err := h.service.GetCommentByID(uint(id)) if err != nil { h.logger.Error("Failed to get comment", zap.Error(err), zap.Uint64("comment_id", id), ) util.ResponseWithJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()}) return } util.ResponseWithJSON(w, http.StatusOK, comment) } // UpdateComment обновляет комментарий func (h *CommentHandler) UpdateComment(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { h.logger.Error("Invalid comment ID", zap.Error(err), zap.String("id", idStr), ) util.ResponseWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid comment ID"}) return } var req UpdateCommentRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { h.logger.Error("Failed to decode request", zap.Error(err), ) util.ResponseWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid request body"}) return } userID, ok := r.Context().Value("user_id").(uint) if !ok { h.logger.Error("User ID not found in context") util.ResponseWithJSON(w, http.StatusUnauthorized, map[string]string{"error": "User not authenticated"}) return } comment, err := h.service.UpdateComment(uint(id), userID, &req) if err != nil { h.logger.Error("Failed to update comment", zap.Error(err), zap.Uint64("comment_id", id), zap.Uint("user_id", userID), ) status := http.StatusInternalServerError if err.Error() == "comment not found" { status = http.StatusNotFound } else if err.Error() == "you can only edit your own comments" { status = http.StatusForbidden } util.ResponseWithJSON(w, status, map[string]string{"error": err.Error()}) return } util.ResponseWithJSON(w, http.StatusOK, comment) } // DeleteComment удаляет комментарий func (h *CommentHandler) DeleteComment(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { h.logger.Error("Invalid comment ID", zap.Error(err), zap.String("id", idStr), ) util.ResponseWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid comment ID"}) return } userID, ok := r.Context().Value("user_id").(uint) if !ok { h.logger.Error("User ID not found in context") util.ResponseWithJSON(w, http.StatusUnauthorized, map[string]string{"error": "User not authenticated"}) return } // Проверяем, является ли пользователь администратором isAdmin, _ := r.Context().Value("is_admin").(bool) err = h.service.DeleteComment(uint(id), userID, isAdmin) if err != nil { h.logger.Error("Failed to delete comment", zap.Error(err), zap.Uint64("comment_id", id), zap.Uint("user_id", userID), zap.Bool("is_admin", isAdmin), ) status := http.StatusInternalServerError if err.Error() == "comment not found" { status = http.StatusNotFound } else if err.Error() == "you can only delete your own comments" { status = http.StatusForbidden } util.ResponseWithJSON(w, status, map[string]string{"error": err.Error()}) return } util.ResponseWithJSON(w, http.StatusOK, map[string]string{"message": "Comment deleted successfully"}) } // ListComments возвращает список комментариев с фильтрацией func (h *CommentHandler) ListComments(w http.ResponseWriter, r *http.Request) { var req ListCommentsRequest if err := r.ParseForm(); err != nil { h.logger.Error("Failed to parse form", zap.Error(err), ) util.ResponseWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid query parameters"}) return } // Парсим query параметры if pageStr := r.URL.Query().Get("page"); pageStr != "" { if page, err := strconv.Atoi(pageStr); err == nil && page > 0 { req.Page = page } } if sizeStr := r.URL.Query().Get("page_size"); sizeStr != "" { if size, err := strconv.Atoi(sizeStr); err == nil && size > 0 && size <= 100 { req.PageSize = size } } if feedbackIDStr := r.URL.Query().Get("feedback_id"); feedbackIDStr != "" { if id, err := strconv.ParseUint(feedbackIDStr, 10, 32); err == nil { idUint := uint(id) req.FeedbackID = &idUint } } if authorIDStr := r.URL.Query().Get("author_id"); authorIDStr != "" { if id, err := strconv.ParseUint(authorIDStr, 10, 32); err == nil { idUint := uint(id) req.AuthorID = &idUint } } if parentIDStr := r.URL.Query().Get("parent_id"); parentIDStr != "" { if id, err := strconv.ParseUint(parentIDStr, 10, 32); err == nil { idUint := uint(id) req.ParentID = &idUint } } if verifiedStr := r.URL.Query().Get("verified"); verifiedStr != "" { if verified, err := strconv.ParseBool(verifiedStr); err == nil { req.Verified = &verified } } if sortBy := r.URL.Query().Get("sort_by"); sortBy != "" { req.SortBy = sortBy } if sortOrder := r.URL.Query().Get("sort_order"); sortOrder != "" { req.SortOrder = sortOrder } comments, total, err := h.service.ListComments(&req) if err != nil { h.logger.Error("Failed to list comments", zap.Error(err), zap.Any("request", req), ) util.ResponseWithJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } response := map[string]interface{}{ "data": comments, "total": total, "page": req.Page, "page_size": req.PageSize, "total_pages": (total + int64(req.PageSize) - 1) / int64(req.PageSize), } util.ResponseWithJSON(w, http.StatusOK, response) } // GetCommentsByFeedback возвращает комментарии по отзыву func (h *CommentHandler) GetCommentsByFeedback(w http.ResponseWriter, r *http.Request) { feedbackIDStr := chi.URLParam(r, "feedbackID") feedbackID, err := strconv.ParseUint(feedbackIDStr, 10, 32) if err != nil { h.logger.Error("Invalid feedback ID", zap.Error(err), zap.String("feedback_id", feedbackIDStr), ) util.ResponseWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid feedback ID"}) return } var req ListCommentsRequest if pageStr := r.URL.Query().Get("page"); pageStr != "" { if page, err := strconv.Atoi(pageStr); err == nil && page > 0 { req.Page = page } } if sizeStr := r.URL.Query().Get("page_size"); sizeStr != "" { if size, err := strconv.Atoi(sizeStr); err == nil && size > 0 && size <= 100 { req.PageSize = size } } comments, total, err := h.service.GetCommentsByFeedback(uint(feedbackID), &req) if err != nil { h.logger.Error("Failed to get comments by feedback", zap.Error(err), zap.Uint64("feedback_id", feedbackID), ) util.ResponseWithJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } response := map[string]interface{}{ "data": comments, "total": total, "page": req.Page, "page_size": req.PageSize, "total_pages": (total + int64(req.PageSize) - 1) / int64(req.PageSize), } util.ResponseWithJSON(w, http.StatusOK, response) } // GetMyComments возвращает комментарии текущего пользователя func (h *CommentHandler) GetMyComments(w http.ResponseWriter, r *http.Request) { userID, ok := r.Context().Value("user_id").(uint) if !ok { h.logger.Error("User ID not found in context") util.ResponseWithJSON(w, http.StatusUnauthorized, map[string]string{"error": "User not authenticated"}) return } var req ListCommentsRequest if pageStr := r.URL.Query().Get("page"); pageStr != "" { if page, err := strconv.Atoi(pageStr); err == nil && page > 0 { req.Page = page } } if sizeStr := r.URL.Query().Get("page_size"); sizeStr != "" { if size, err := strconv.Atoi(sizeStr); err == nil && size > 0 && size <= 100 { req.PageSize = size } } comments, total, err := h.service.GetCommentsByAuthor(userID, &req) if err != nil { h.logger.Error("Failed to get my comments", zap.Error(err), zap.Uint("user_id", userID), ) util.ResponseWithJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } response := map[string]interface{}{ "data": comments, "total": total, "page": req.Page, "page_size": req.PageSize, "total_pages": (total + int64(req.PageSize) - 1) / int64(req.PageSize), } util.ResponseWithJSON(w, http.StatusOK, response) } // GetReplies возвращает ответы на комментарий func (h *CommentHandler) GetReplies(w http.ResponseWriter, r *http.Request) { parentIDStr := chi.URLParam(r, "parentID") parentID, err := strconv.ParseUint(parentIDStr, 10, 32) if err != nil { h.logger.Error("Invalid parent comment ID", zap.Error(err), zap.String("parent_id", parentIDStr), ) util.ResponseWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid parent comment ID"}) return } var req ListCommentsRequest if pageStr := r.URL.Query().Get("page"); pageStr != "" { if page, err := strconv.Atoi(pageStr); err == nil && page > 0 { req.Page = page } } if sizeStr := r.URL.Query().Get("page_size"); sizeStr != "" { if size, err := strconv.Atoi(sizeStr); err == nil && size > 0 && size <= 100 { req.PageSize = size } } replies, total, err := h.service.GetReplies(uint(parentID), &req) if err != nil { h.logger.Error("Failed to get replies", zap.Error(err), zap.Uint64("parent_id", parentID), ) util.ResponseWithJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } response := map[string]interface{}{ "data": replies, "total": total, "page": req.Page, "page_size": req.PageSize, "total_pages": (total + int64(req.PageSize) - 1) / int64(req.PageSize), } util.ResponseWithJSON(w, http.StatusOK, response) } // VerifyComment верифицирует комментарий (только для админов) func (h *CommentHandler) VerifyComment(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { h.logger.Error("Invalid comment ID", zap.Error(err), zap.String("id", idStr), ) util.ResponseWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid comment ID"}) return } var req struct { Verified bool `json:"verified"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { h.logger.Error("Failed to decode request", zap.Error(err), ) util.ResponseWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid request body"}) return } err = h.service.VerifyComment(uint(id), req.Verified) if err != nil { h.logger.Error("Failed to verify comment", zap.Error(err), zap.Uint64("comment_id", id), zap.Bool("verified", req.Verified), ) util.ResponseWithJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } status := "unverified" if req.Verified { status = "verified" } util.ResponseWithJSON(w, http.StatusOK, map[string]string{"message": "Comment " + status + " successfully"}) } // GetCommentStats возвращает статистику по комментариям func (h *CommentHandler) GetCommentStats(w http.ResponseWriter, r *http.Request) { stats, err := h.service.GetCommentStats() if err != nil { h.logger.Error("Failed to get comment stats", zap.Error(err), ) util.ResponseWithJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } util.ResponseWithJSON(w, http.StatusOK, stats) }