package socialrating import ( "errors" "net/http" "strconv" "github.com/gin-gonic/gin" "gorm.io/gorm" "social-raiting.nekiiinkognito.ru/internal/models" ) type Handler struct { Service Service } type ChangeRequest struct { TargetUserID uint `json:"targetUserId" binding:"required"` Amount int `json:"amount"` Reason string `json:"reason"` Source string `json:"source"` } type ChangeResponse struct { Operation models.SocialRatingOperation `json:"operation"` CurrentRating models.UserSocialRating `json:"currentRating"` } type UsersResponse struct { Users []UserWithRating `json:"users"` } type UserRatingResponse struct { User UserWithRating `json:"user"` } type HistoryResponse struct { Operations []models.SocialRatingOperation `json:"operations"` } func NewHandler(service Service) Handler { return Handler{Service: service} } func (h Handler) ListUsers(ctx *gin.Context) { limit := parsePositiveLimit(ctx.Query("limit"), 50) users, err := h.Service.ListUsersWithRatings(limit) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load users"}) return } ctx.JSON(http.StatusOK, UsersResponse{Users: users}) } func (h Handler) GetUser(ctx *gin.Context) { userID, err := parseUintParam(ctx.Param("userId")) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } user, err := h.Service.GetUserRating(userID) if err != nil { handleApplyChangeError(ctx, err) return } ctx.JSON(http.StatusOK, UserRatingResponse{User: user}) } func (h Handler) GetUserHistory(ctx *gin.Context) { userID, err := parseUintParam(ctx.Param("userId")) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } limit := parsePositiveLimit(ctx.Query("limit"), 50) operations, err := h.Service.GetUserHistory(userID, limit) if err != nil { handleApplyChangeError(ctx, err) return } ctx.JSON(http.StatusOK, HistoryResponse{Operations: operations}) } func (h Handler) GetRecentOperations(ctx *gin.Context) { limit := parsePositiveLimit(ctx.Query("limit"), 50) operations, err := h.Service.GetRecentOperations(limit) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load operations"}) return } ctx.JSON(http.StatusOK, HistoryResponse{Operations: operations}) } func (h Handler) Increase(ctx *gin.Context) { h.applySignedChange(ctx, "increase", 1) } func (h Handler) Decrease(ctx *gin.Context) { h.applySignedChange(ctx, "decrease", -1) } func (h Handler) applySignedChange(ctx *gin.Context, operationType string, direction int) { var req ChangeRequest if err := ctx.ShouldBindJSON(&req); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } actorUserID, ok := getActorUserID(ctx) if !ok { ctx.JSON(http.StatusUnauthorized, gin.H{"error": "missing user context"}) return } amount := req.Amount if amount == 0 { amount = 1 } if amount < 0 { ctx.JSON(http.StatusBadRequest, gin.H{"error": "amount must be greater than zero"}) return } operation, currentRating, err := h.Service.ApplyChange(ChangeInput{ TargetUserID: req.TargetUserID, ActorUserID: &actorUserID, Delta: amount * direction, OperationType: operationType, Reason: req.Reason, Source: req.Source, }) if err != nil { handleApplyChangeError(ctx, err) return } ctx.JSON(http.StatusOK, ChangeResponse{ Operation: operation, CurrentRating: currentRating, }) } func getActorUserID(ctx *gin.Context) (uint, bool) { value, exists := ctx.Get("userID") if !exists { return 0, false } userID, ok := value.(uint) return userID, ok } func handleApplyChangeError(ctx *gin.Context, err error) { switch { case errors.Is(err, gorm.ErrRecordNotFound): ctx.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) default: ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } } func parseUintParam(raw string) (uint, error) { value, err := strconv.ParseUint(raw, 10, 64) if err != nil || value == 0 { return 0, errors.New("invalid user id") } return uint(value), nil }