Add full monorepo: virtual-banker, backend, frontend, docs, scripts, deployment
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
68
backend/safety/filter.go
Normal file
68
backend/safety/filter.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package safety
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Filter filters content for safety
|
||||
type Filter interface {
|
||||
Filter(ctx context.Context, text string) (*FilterResult, error)
|
||||
}
|
||||
|
||||
// FilterResult contains filtering results
|
||||
type FilterResult struct {
|
||||
Allowed bool
|
||||
Blocked bool
|
||||
Redacted string
|
||||
Categories []string // e.g., "profanity", "pii", "abuse"
|
||||
}
|
||||
|
||||
// ContentFilter implements content filtering
|
||||
type ContentFilter struct {
|
||||
blockedWords []string
|
||||
}
|
||||
|
||||
// NewContentFilter creates a new content filter
|
||||
func NewContentFilter() *ContentFilter {
|
||||
return &ContentFilter{
|
||||
blockedWords: []string{
|
||||
// Add blocked words/phrases
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Filter filters content
|
||||
func (f *ContentFilter) Filter(ctx context.Context, text string) (*FilterResult, error) {
|
||||
lowerText := strings.ToLower(text)
|
||||
var categories []string
|
||||
|
||||
// Check for blocked words
|
||||
for _, word := range f.blockedWords {
|
||||
if strings.Contains(lowerText, strings.ToLower(word)) {
|
||||
categories = append(categories, "profanity")
|
||||
return &FilterResult{
|
||||
Allowed: false,
|
||||
Blocked: true,
|
||||
Redacted: f.redactPII(text),
|
||||
Categories: categories,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add more sophisticated filtering (ML models, etc.)
|
||||
|
||||
return &FilterResult{
|
||||
Allowed: true,
|
||||
Blocked: false,
|
||||
Redacted: f.redactPII(text),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// redactPII redacts personally identifiable information
|
||||
func (f *ContentFilter) redactPII(text string) string {
|
||||
// TODO: Implement PII detection and redaction
|
||||
// For now, return as-is
|
||||
return text
|
||||
}
|
||||
|
||||
59
backend/safety/rate_limit.go
Normal file
59
backend/safety/rate_limit.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package safety
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// RateLimiter implements rate limiting
|
||||
type RateLimiter struct {
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewRateLimiter creates a new rate limiter
|
||||
func NewRateLimiter(redisClient *redis.Client) *RateLimiter {
|
||||
return &RateLimiter{
|
||||
redis: redisClient,
|
||||
}
|
||||
}
|
||||
|
||||
// Check checks if a request is within rate limits
|
||||
func (r *RateLimiter) Check(ctx context.Context, key string, limit int, window time.Duration) (bool, error) {
|
||||
// Use sliding window log algorithm
|
||||
now := time.Now()
|
||||
windowStart := now.Add(-window)
|
||||
|
||||
// Count requests in window
|
||||
count, err := r.redis.ZCount(ctx, key, fmt.Sprintf("%d", windowStart.Unix()), fmt.Sprintf("%d", now.Unix())).Result()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if count >= int64(limit) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Add current request
|
||||
_, err = r.redis.ZAdd(ctx, key, redis.Z{
|
||||
Score: float64(now.Unix()),
|
||||
Member: fmt.Sprintf("%d", now.UnixNano()),
|
||||
}).Result()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Expire old entries
|
||||
r.redis.Expire(ctx, key, window)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CheckUser checks rate limit for a user
|
||||
func (r *RateLimiter) CheckUser(ctx context.Context, tenantID, userID string, limit int, window time.Duration) (bool, error) {
|
||||
key := fmt.Sprintf("ratelimit:user:%s:%s", tenantID, userID)
|
||||
return r.Check(ctx, key, limit, window)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user