package validation import ( "errors" "regexp" "strings" ) // ValidateUsername checks if a username meets requirements func ValidateUsername(username string) error { u := strings.TrimSpace(username) if u == "" { return errors.New("username cannot be empty") } if len(u) < 3 { return errors.New("username must be at least 3 characters") } if len(u) > 32 { return errors.New("username cannot exceed 32 characters") } // Only allow lowercase letters, numbers, hyphen, underscore, dot validUsername := regexp.MustCompile(`^[a-z0-9\-_.]+$`) if !validUsername.MatchString(u) { return errors.New("username can only contain lowercase letters, numbers, and characters: - _ .") } // Prevent consecutive special chars if strings.Contains(u, "--") || strings.Contains(u, "__") || strings.Contains(u, "..") { return errors.New("username cannot contain consecutive special characters") } // Cannot start or end with special chars if strings.HasPrefix(u, "-") || strings.HasPrefix(u, "_") || strings.HasPrefix(u, ".") || strings.HasSuffix(u, "-") || strings.HasSuffix(u, "_") || strings.HasSuffix(u, ".") { return errors.New("username cannot start or end with special characters") } // Prevent reserved words reserved := []string{"admin", "administrator", "mod", "moderator", "root", "system", "support", "official", "bot"} for _, r := range reserved { if strings.EqualFold(u, r) { return errors.New("username is reserved") } } return nil } // SanitizeRewardName cleans and validates reward names func SanitizeRewardName(name string) (string, error) { n := strings.TrimSpace(name) if n == "" { return "", errors.New("reward name cannot be empty") } if len(n) > 255 { return "", errors.New("reward name too long (max 255 characters)") } return n, nil } // ValidateRewardType ensures reward type is allowed func ValidateRewardType(rewardType string) error { allowed := map[string]bool{ "avatar_static": true, "avatar_animated": true, "avatar_upload_unlock": true, "merch_coupon": true, "merch_physical": true, "merch_digital": true, "custom": true, } if !allowed[rewardType] { return errors.New("invalid reward type") } return nil } // ValidatePoints checks if points value is reasonable func ValidatePoints(points int64) error { if points < 0 { return errors.New("points cannot be negative") } if points > 1000000 { return errors.New("points value too large (max 1,000,000)") } return nil } // ValidateXP checks if XP value is reasonable func ValidateXP(xp int64) error { if xp < 0 { return errors.New("XP cannot be negative") } if xp > 10000000 { return errors.New("XP value too large (max 10,000,000)") } return nil } // ValidateRewardStock checks stock value func ValidateRewardStock(stock int) error { if stock < -1 { return errors.New("stock cannot be less than -1 (use -1 for unlimited)") } if stock > 100000 { return errors.New("stock value too large (max 100,000)") } return nil } // ValidateAchievementCode checks achievement code format func ValidateAchievementCode(code string) error { c := strings.TrimSpace(code) if c == "" { return errors.New("achievement code cannot be empty") } if len(c) > 64 { return errors.New("achievement code too long (max 64 characters)") } // Only alphanumeric and underscore validCode := regexp.MustCompile(`^[a-z0-9_]+$`) if !validCode.MatchString(c) { return errors.New("achievement code can only contain lowercase letters, numbers, and underscores") } return nil } // ValidateRedemptionStatus checks if redemption status is valid func ValidateRedemptionStatus(status string) error { allowed := map[string]bool{ "pending": true, "approved": true, "rejected": true, "fulfilled": true, } if !allowed[status] { return errors.New("invalid redemption status") } return nil }