package handlers import ( "strings" "github.com/gofiber/fiber/v2" "github.com/google/uuid" "github.com/buhapp/backend/internal/audit" "github.com/buhapp/backend/internal/chat" "github.com/buhapp/backend/internal/reviews" ) type ReviewHandlers struct { Reviews *reviews.Repo Chat *chat.Repo Audit *audit.Repo } func NewReviewHandlers(r *reviews.Repo, ch *chat.Repo, a *audit.Repo) *ReviewHandlers { return &ReviewHandlers{Reviews: r, Chat: ch, Audit: a} } type createReviewRequest struct { ChatID string `json:"chat_id"` Rating int `json:"rating"` Body string `json:"body"` Anonymous bool `json:"anonymous"` } func (h *ReviewHandlers) Create(c *fiber.Ctx) error { me, err := userID(c) if err != nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "no user"}) } var req createReviewRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid JSON"}) } chatID, err := uuid.Parse(req.ChatID) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "bad chat_id"}) } if req.Rating < 1 || req.Rating > 5 { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "rating must be 1..5"}) } body := strings.TrimSpace(req.Body) if len(body) > 2000 { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body too long (max 2000)"}) } // Получаем чат и определяем reviewed_id ch, err := h.Chat.GetByID(c.UserContext(), chatID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "db error"}) } if ch == nil { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "chat not found"}) } reviewed := ch.UserA if reviewed == me { reviewed = ch.UserB } if ch.UserA != me && ch.UserB != me { return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "not a chat member"}) } // Проверим, не оставлял ли уже dup, _ := h.Reviews.HasReviewed(c.UserContext(), chatID, me) if dup { return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "review already exists for this chat"}) } rev := &reviews.Review{ ReviewerID: me, ReviewedID: reviewed, ChatID: chatID, Rating: req.Rating, Body: body, Anonymous: req.Anonymous, } if err := h.Reviews.Create(c.UserContext(), rev); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } _ = h.Audit.Log(c.UserContext(), &audit.Event{ UserID: &me, Action: "review.create", TargetType: "user", TargetID: reviewed.String(), IP: c.IP(), UserAgent: c.Get("User-Agent"), }) return c.Status(fiber.StatusCreated).JSON(fiber.Map{ "id": rev.ID, "rating": rev.Rating, "anonymous": rev.Anonymous, "created_at": rev.CreatedAt, }) } func (h *ReviewHandlers) ListForUser(c *fiber.Ctx) error { id, err := uuid.Parse(c.Params("id")) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "bad id"}) } limit := c.QueryInt("limit", 20) offset := c.QueryInt("offset", 0) list, err := h.Reviews.ListForUser(c.UserContext(), id, limit, offset) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "db error"}) } return c.JSON(fiber.Map{ "reviews": list, "count": len(list), }) } func (h *ReviewHandlers) Stats(c *fiber.Ctx) error { id, err := uuid.Parse(c.Params("id")) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "bad id"}) } st, err := h.Reviews.GetStats(c.UserContext(), id) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "db error"}) } return c.JSON(st) }