package socialrating import ( "errors" "strings" "gorm.io/gorm" "social-raiting.nekiiinkognito.ru/internal/models" ) type Service struct { DB *gorm.DB } type ChangeInput struct { TargetUserID uint ActorUserID *uint Delta int OperationType string Reason string Source string } func NewService(db *gorm.DB) Service { return Service{DB: db} } func (s Service) ApplyChange(input ChangeInput) (models.SocialRatingOperation, models.UserSocialRating, error) { var operation models.SocialRatingOperation var currentRating models.UserSocialRating if err := validateChangeInput(input); err != nil { return operation, currentRating, err } err := s.DB.Transaction(func(tx *gorm.DB) error { if err := ensureUserExists(tx, input.TargetUserID); err != nil { return err } if input.ActorUserID != nil { if err := ensureUserExists(tx, *input.ActorUserID); err != nil { return err } } currentRating = models.UserSocialRating{UserID: input.TargetUserID} if err := tx.FirstOrCreate(¤tRating, models.UserSocialRating{ UserID: input.TargetUserID, }).Error; err != nil { return err } // Social rating is allowed to go below zero, so no lower bound is applied here. currentRating.Score += input.Delta operation = models.SocialRatingOperation{ TargetUserID: input.TargetUserID, ActorUserID: input.ActorUserID, Delta: input.Delta, OperationType: strings.TrimSpace(input.OperationType), Reason: strings.TrimSpace(input.Reason), Source: strings.TrimSpace(input.Source), BalanceAfter: currentRating.Score, } if err := tx.Create(&operation).Error; err != nil { return err } currentRating.LastOperationID = &operation.ID return tx.Save(¤tRating).Error }) return operation, currentRating, err } func validateChangeInput(input ChangeInput) error { if input.TargetUserID == 0 { return errors.New("target user id is required") } if strings.TrimSpace(input.OperationType) == "" { return errors.New("operation type is required") } if input.Delta == 0 { return errors.New("delta must not be zero") } return nil } func ensureUserExists(tx *gorm.DB, userID uint) error { var user models.User return tx.First(&user, "id = ?", userID).Error }