- POST /api/v1/reviews (one per chat_id, UNIQUE) - GET /api/v1/users/:id/reviews (public list) - GET /api/v1/users/:id/stats (rating_avg, count) - migration 0004: reviews (rating 1-5, anonymous flag), user_stats aggregate - only chat participants can review; reviewed_id is other party
127 lines
3.7 KiB
Go
127 lines
3.7 KiB
Go
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)
|
|
} |