Files
tp/main_dc/yalarba/api_yal/internal/domain/feetback/handler.go
T
valitovgaziz 63d486f48d On branch main
modified:   main_dc/yalarba/api_yal/internal/domain/appeal/router.go
	modified:   main_dc/yalarba/api_yal/internal/domain/feetback/dto.go
	modified:   main_dc/yalarba/api_yal/internal/domain/feetback/handler.go
	modified:   main_dc/yalarba/api_yal/internal/domain/feetback/router.go
	modified:   main_dc/yalarba/api_yal/internal/domain/feetback/service.go
	modified:   main_dc/yalarba/api_yal/internal/models/feedback.go
	modified:   main_dc/yalarba/api_yal/internal/repository/comment_repository.go
	modified:   main_dc/yalarba/api_yal/internal/repository/feedback_repository.go
	modified:   main_dc/yalarba/api_yal/internal/repository/feedback_repository_impl.go
	modified:   main_dc/yalarba/api_yal/internal/router/router.go
craete routerRegister, service, hander, dto for feedback
2026-05-19 15:01:57 +05:00

547 lines
16 KiB
Go

package feetback
import (
"api_yal/internal/models"
"api_yal/internal/logger"
"encoding/json"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
"go.uber.org/zap"
)
// FeedbackHandler обработчик для отзывов
type FeedbackHandler struct {
service FeedbackService
log *zap.Logger
}
// NewFeedbackHandler создает новый обработчик
func NewFeedbackHandler(service FeedbackService) *FeedbackHandler {
return &FeedbackHandler{
service: service,
log: logger.Get(),
}
}
// getUserIDFromContext получает ID пользователя из контекста
func (h *FeedbackHandler) getUserIDFromContext(r *http.Request) (uint, bool) {
userID, ok := r.Context().Value("user_id").(uint)
return userID, ok
}
// isAdminFromContext проверяет, является ли пользователь админом
func (h *FeedbackHandler) isAdminFromContext(r *http.Request) bool {
isAdmin, ok := r.Context().Value("is_admin").(bool)
return ok && isAdmin
}
// parsePagination парсит параметры пагинации
func (h *FeedbackHandler) parsePagination(r *http.Request) (offset, limit int) {
offset, _ = strconv.Atoi(r.URL.Query().Get("offset"))
limit, _ = strconv.Atoi(r.URL.Query().Get("limit"))
if limit <= 0 || limit > 100 {
limit = 20
}
if offset < 0 {
offset = 0
}
return
}
// sendJSON отправляет JSON ответ
func (h *FeedbackHandler) sendJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(data); err != nil {
h.log.Error("Failed to encode JSON response", zap.Error(err))
}
}
// sendError отправляет ошибку
func (h *FeedbackHandler) sendError(w http.ResponseWriter, status int, message string) {
h.sendJSON(w, status, ErrorResponse{
Error: http.StatusText(status),
Message: message,
Code: status,
})
}
// CreateFeedback создает новый отзыв
// POST /feedbacks
func (h *FeedbackHandler) CreateFeedback(w http.ResponseWriter, r *http.Request) {
userID, ok := h.getUserIDFromContext(r)
if !ok {
h.sendError(w, http.StatusUnauthorized, "unauthorized")
return
}
var req CreateFeedbackRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.sendError(w, http.StatusBadRequest, "invalid request body")
return
}
feedback := &models.Feedback{
ObjectID: req.ObjectID,
Score: req.Rating,
Text: req.Text,
Platform: req.Platform,
}
if err := h.service.Create(feedback, userID); err != nil {
h.log.Error("Failed to create feedback", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to create feedback")
return
}
// Загружаем созданный отзыв с данными владельца и объекта
created, _ := h.service.GetByID(feedback.ID)
response := h.mapFeedbackToResponse(created)
h.sendJSON(w, http.StatusCreated, response)
}
// GetFeedbackByID получает отзыв по ID
// GET /feedbacks/{id}
func (h *FeedbackHandler) GetFeedbackByID(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.sendError(w, http.StatusBadRequest, "invalid feedback id")
return
}
feedback, err := h.service.GetByID(uint(id))
if err != nil {
h.sendError(w, http.StatusNotFound, "feedback not found")
return
}
response := h.mapFeedbackToResponse(feedback)
h.sendJSON(w, http.StatusOK, response)
}
// UpdateFeedback обновляет отзыв
// PUT /feedbacks/{id}
func (h *FeedbackHandler) UpdateFeedback(w http.ResponseWriter, r *http.Request) {
userID, ok := h.getUserIDFromContext(r)
if !ok {
h.sendError(w, http.StatusUnauthorized, "unauthorized")
return
}
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.sendError(w, http.StatusBadRequest, "invalid feedback id")
return
}
var req UpdateFeedbackRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.sendError(w, http.StatusBadRequest, "invalid request body")
return
}
updates := make(map[string]interface{})
if req.Rating != nil {
updates["rating"] = *req.Rating
}
if req.Text != nil {
updates["text"] = *req.Text
}
if req.Platform != nil {
updates["platform"] = *req.Platform
}
if req.MediaURLs != nil {
updates["media_urls"] = req.MediaURLs
}
if err := h.service.Update(uint(id), userID, updates); err != nil {
if err.Error() == "you can only update your own feedback" {
h.sendError(w, http.StatusForbidden, err.Error())
return
}
h.log.Error("Failed to update feedback", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to update feedback")
return
}
feedback, _ := h.service.GetByID(uint(id))
response := h.mapFeedbackToResponse(feedback)
h.sendJSON(w, http.StatusOK, response)
}
// DeleteFeedback удаляет отзыв
// DELETE /feedbacks/{id}
func (h *FeedbackHandler) DeleteFeedback(w http.ResponseWriter, r *http.Request) {
userID, ok := h.getUserIDFromContext(r)
if !ok {
h.sendError(w, http.StatusUnauthorized, "unauthorized")
return
}
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.sendError(w, http.StatusBadRequest, "invalid feedback id")
return
}
isAdmin := h.isAdminFromContext(r)
if err := h.service.Delete(uint(id), userID, isAdmin); err != nil {
if err.Error() == "you can only delete your own feedback" {
h.sendError(w, http.StatusForbidden, err.Error())
return
}
h.log.Error("Failed to delete feedback", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to delete feedback")
return
}
h.sendJSON(w, http.StatusNoContent, nil)
}
// ListFeedbacks возвращает список отзывов с пагинацией
// GET /feedbacks
func (h *FeedbackHandler) ListFeedbacks(w http.ResponseWriter, r *http.Request) {
offset, limit := h.parsePagination(r)
feedbacks, total, err := h.service.List(offset, limit)
if err != nil {
h.log.Error("Failed to list feedbacks", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to list feedbacks")
return
}
response := h.mapFeedbackListToResponse(feedbacks, total, offset, limit)
h.sendJSON(w, http.StatusOK, response)
}
// GetMyFeedbacks возвращает отзывы текущего пользователя
// GET /feedbacks/my
func (h *FeedbackHandler) GetMyFeedbacks(w http.ResponseWriter, r *http.Request) {
userID, ok := h.getUserIDFromContext(r)
if !ok {
h.sendError(w, http.StatusUnauthorized, "unauthorized")
return
}
offset, limit := h.parsePagination(r)
feedbacks, total, err := h.service.ListByOwner(userID, offset, limit)
if err != nil {
h.log.Error("Failed to list user feedbacks", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to list feedbacks")
return
}
response := h.mapFeedbackListToResponse(feedbacks, total, offset, limit)
h.sendJSON(w, http.StatusOK, response)
}
// GetFeedbacksByObject возвращает отзывы по объекту
// GET /feedbacks/object/{objectID}
func (h *FeedbackHandler) GetFeedbacksByObject(w http.ResponseWriter, r *http.Request) {
objectIDStr := chi.URLParam(r, "objectID")
objectID, err := strconv.ParseUint(objectIDStr, 10, 32)
if err != nil {
h.sendError(w, http.StatusBadRequest, "invalid object id")
return
}
offset, limit := h.parsePagination(r)
feedbacks, total, err := h.service.ListByObject(uint(objectID), offset, limit)
if err != nil {
h.log.Error("Failed to list feedbacks by object", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to list feedbacks")
return
}
response := h.mapFeedbackListToResponse(feedbacks, total, offset, limit)
h.sendJSON(w, http.StatusOK, response)
}
// GetFeedbacksByPlatform возвращает отзывы по платформе
// GET /feedbacks/platform/{platform}
func (h *FeedbackHandler) GetFeedbacksByPlatform(w http.ResponseWriter, r *http.Request) {
platformStr := chi.URLParam(r, "platform")
platform := models.PlatformType(platformStr)
offset, limit := h.parsePagination(r)
feedbacks, total, err := h.service.ListByPlatform(platform, offset, limit)
if err != nil {
h.log.Error("Failed to list feedbacks by platform", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to list feedbacks")
return
}
response := h.mapFeedbackListToResponse(feedbacks, total, offset, limit)
h.sendJSON(w, http.StatusOK, response)
}
// SearchFeedbacks ищет отзывы по тексту
// GET /feedbacks/search
func (h *FeedbackHandler) SearchFeedbacks(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
if query == "" {
h.sendError(w, http.StatusBadRequest, "search query is required")
return
}
offset, limit := h.parsePagination(r)
feedbacks, total, err := h.service.Search(query, offset, limit)
if err != nil {
h.log.Error("Failed to search feedbacks", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to search feedbacks")
return
}
response := h.mapFeedbackListToResponse(feedbacks, total, offset, limit)
h.sendJSON(w, http.StatusOK, response)
}
// GetFeedbackComments возвращает комментарии к отзыву
// GET /feedbacks/{id}/comments
func (h *FeedbackHandler) GetFeedbackComments(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.sendError(w, http.StatusBadRequest, "invalid feedback id")
return
}
offset, limit := h.parsePagination(r)
comments, total, err := h.service.GetComments(uint(id), offset, limit)
if err != nil {
h.log.Error("Failed to get comments", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to get comments")
return
}
response := h.mapCommentListToResponse(comments, total, offset, limit)
h.sendJSON(w, http.StatusOK, response)
}
// AddComment добавляет комментарий к отзыву
// POST /feedbacks/{id}/comments
func (h *FeedbackHandler) AddComment(w http.ResponseWriter, r *http.Request) {
userID, ok := h.getUserIDFromContext(r)
if !ok {
h.sendError(w, http.StatusUnauthorized, "unauthorized")
return
}
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.sendError(w, http.StatusBadRequest, "invalid feedback id")
return
}
var req CreateCommentRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.sendError(w, http.StatusBadRequest, "invalid request body")
return
}
comment, err := h.service.AddComment(uint(id), userID, req.Text)
if err != nil {
if err.Error() == "feedback not found" {
h.sendError(w, http.StatusNotFound, err.Error())
return
}
h.log.Error("Failed to add comment", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to add comment")
return
}
response := h.mapCommentToResponse(comment)
h.sendJSON(w, http.StatusCreated, response)
}
// UpdateComment обновляет комментарий
// PUT /feedbacks/{id}/comments/{commentID}
func (h *FeedbackHandler) UpdateComment(w http.ResponseWriter, r *http.Request) {
userID, ok := h.getUserIDFromContext(r)
if !ok {
h.sendError(w, http.StatusUnauthorized, "unauthorized")
return
}
commentIDStr := chi.URLParam(r, "commentID")
commentID, err := strconv.ParseUint(commentIDStr, 10, 32)
if err != nil {
h.sendError(w, http.StatusBadRequest, "invalid comment id")
return
}
var req UpdateCommentRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.sendError(w, http.StatusBadRequest, "invalid request body")
return
}
isAdmin := h.isAdminFromContext(r)
if err := h.service.UpdateComment(uint(commentID), userID, req.Text, isAdmin); err != nil {
if err.Error() == "you can only update your own comments" {
h.sendError(w, http.StatusForbidden, err.Error())
return
}
h.log.Error("Failed to update comment", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to update comment")
return
}
h.sendJSON(w, http.StatusOK, map[string]string{"message": "comment updated successfully"})
}
// DeleteComment удаляет комментарий
// DELETE /feedbacks/{id}/comments/{commentID}
func (h *FeedbackHandler) DeleteComment(w http.ResponseWriter, r *http.Request) {
userID, ok := h.getUserIDFromContext(r)
if !ok {
h.sendError(w, http.StatusUnauthorized, "unauthorized")
return
}
commentIDStr := chi.URLParam(r, "commentID")
commentID, err := strconv.ParseUint(commentIDStr, 10, 32)
if err != nil {
h.sendError(w, http.StatusBadRequest, "invalid comment id")
return
}
isAdmin := h.isAdminFromContext(r)
if err := h.service.DeleteComment(uint(commentID), userID, isAdmin); err != nil {
if err.Error() == "you can only delete your own comments" {
h.sendError(w, http.StatusForbidden, err.Error())
return
}
h.log.Error("Failed to delete comment", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to delete comment")
return
}
h.sendJSON(w, http.StatusNoContent, nil)
}
// GetFeedbackStats возвращает статистику по отзывам
// GET /feedbacks/stats
func (h *FeedbackHandler) GetFeedbackStats(w http.ResponseWriter, r *http.Request) {
stats, err := h.service.GetStats()
if err != nil {
h.log.Error("Failed to get stats", zap.Error(err))
h.sendError(w, http.StatusInternalServerError, "failed to get stats")
return
}
h.sendJSON(w, http.StatusOK, stats)
}
// mapFeedbackToResponse маппинг модели в DTO ответа
func (h *FeedbackHandler) mapFeedbackToResponse(f *models.Feedback) FeedbackResponse {
resp := FeedbackResponse{
ID: f.ID,
OwnerID: f.OwnerID,
ObjectID: f.ObjectID,
Rating: f.Score,
Text: f.Text,
Platform: f.Platform,
CommentCount: f.CommentCount,
CreatedAt: f.CreatedAt,
UpdatedAt: f.UpdatedAt,
}
if f.OwnerID != 0 {
resp.Owner = &AccountBriefResponse{
ID: f.Owner.ID,
Username: f.Owner.FullName,
}
}
if f.ObjectID != 0 {
resp.Object = &ObjectBriefResponse{
ID: f.Object.ID,
Name: f.Object.Owner.FullName,
Description: f.Object.ShortDescription,
}
}
return resp
}
// mapFeedbackListToResponse маппинг списка отзывов в DTO ответа
func (h *FeedbackHandler) mapFeedbackListToResponse(feedbacks []models.Feedback, total int64, offset, limit int) FeedbackListResponse {
data := make([]FeedbackResponse, len(feedbacks))
for i, f := range feedbacks {
data[i] = h.mapFeedbackToResponse(&f)
}
resp := FeedbackListResponse{
Data: data,
Total: total,
Offset: offset,
Limit: limit,
}
nextOffset := offset + limit
if int64(nextOffset) < total {
resp.NextOffset = &nextOffset
}
return resp
}
// mapCommentToResponse маппинг комментария в DTO ответа
func (h *FeedbackHandler) mapCommentToResponse(c *models.Comment) CommentResponse {
resp := CommentResponse{
ID: c.ID,
FeedbackID: c.FeedbackID,
AccountID: c.AuthorID,
Text: c.Text,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
if c.AuthorID != 0 {
resp.Account = &AccountBriefResponse{
ID: c.Author.ID,
Username: c.Author.FullName,
}
}
return resp
}
// mapCommentListToResponse маппинг списка комментариев в DTO ответа
func (h *FeedbackHandler) mapCommentListToResponse(comments []models.Comment, total, offset, limit int) CommentListResponse {
data := make([]CommentResponse, len(comments))
for i, c := range comments {
data[i] = h.mapCommentToResponse(&c)
}
resp := CommentListResponse{
Data: data,
Total: total,
Offset: offset,
Limit: limit,
}
nextOffset := offset + limit
if nextOffset < total {
resp.NextOffset = &nextOffset
}
return resp
}