feat: Go backend, enhanced search, new widgets, Docker deploy
Major changes: - Add Go backend (backend/) with microservices architecture - Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler, proxy-manager, media-search, fastClassifier, language detection - New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard, UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions - Improved discover-svc with discover-db integration - Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md) - Library-svc: project_id schema migration - Remove deprecated finance-svc and travel-svc - Localization improvements across services Made-with: Cursor
This commit is contained in:
183
backend/pkg/cache/redis.go
vendored
Normal file
183
backend/pkg/cache/redis.go
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type RedisCache struct {
|
||||
client *redis.Client
|
||||
prefix string
|
||||
}
|
||||
|
||||
func NewRedisCache(redisURL, prefix string) (*RedisCache, error) {
|
||||
opts, err := redis.ParseURL(redisURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := redis.NewClient(opts)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &RedisCache{
|
||||
client: client,
|
||||
prefix: prefix,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *RedisCache) Close() error {
|
||||
return c.client.Close()
|
||||
}
|
||||
|
||||
func (c *RedisCache) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.client.Set(ctx, c.prefix+":"+key, data, ttl).Err()
|
||||
}
|
||||
|
||||
func (c *RedisCache) Get(ctx context.Context, key string, dest interface{}) error {
|
||||
data, err := c.client.Get(ctx, c.prefix+":"+key).Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, dest)
|
||||
}
|
||||
|
||||
func (c *RedisCache) Exists(ctx context.Context, key string) (bool, error) {
|
||||
n, err := c.client.Exists(ctx, c.prefix+":"+key).Result()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
func (c *RedisCache) Delete(ctx context.Context, key string) error {
|
||||
return c.client.Del(ctx, c.prefix+":"+key).Err()
|
||||
}
|
||||
|
||||
func (c *RedisCache) SetJSON(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
|
||||
return c.Set(ctx, key, value, ttl)
|
||||
}
|
||||
|
||||
func (c *RedisCache) GetJSON(ctx context.Context, key string, dest interface{}) error {
|
||||
return c.Get(ctx, key, dest)
|
||||
}
|
||||
|
||||
type CacheKey string
|
||||
|
||||
const (
|
||||
KeySearchResults CacheKey = "search"
|
||||
KeyArticleSummary CacheKey = "summary"
|
||||
KeyDigest CacheKey = "digest"
|
||||
KeyChatResponse CacheKey = "chat"
|
||||
)
|
||||
|
||||
func HashKey(parts ...string) string {
|
||||
combined := ""
|
||||
for _, p := range parts {
|
||||
combined += p + ":"
|
||||
}
|
||||
hash := sha256.Sum256([]byte(combined))
|
||||
return hex.EncodeToString(hash[:16])
|
||||
}
|
||||
|
||||
func (c *RedisCache) CacheSearch(ctx context.Context, query string, results interface{}, ttl time.Duration) error {
|
||||
key := string(KeySearchResults) + ":" + HashKey(query)
|
||||
return c.Set(ctx, key, results, ttl)
|
||||
}
|
||||
|
||||
func (c *RedisCache) GetCachedSearch(ctx context.Context, query string, dest interface{}) error {
|
||||
key := string(KeySearchResults) + ":" + HashKey(query)
|
||||
return c.Get(ctx, key, dest)
|
||||
}
|
||||
|
||||
func (c *RedisCache) CacheArticleSummary(ctx context.Context, url string, events []string, ttl time.Duration) error {
|
||||
key := string(KeyArticleSummary) + ":" + HashKey(url)
|
||||
return c.Set(ctx, key, events, ttl)
|
||||
}
|
||||
|
||||
func (c *RedisCache) GetCachedArticleSummary(ctx context.Context, url string) ([]string, error) {
|
||||
key := string(KeyArticleSummary) + ":" + HashKey(url)
|
||||
var events []string
|
||||
if err := c.Get(ctx, key, &events); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (c *RedisCache) CacheDigest(ctx context.Context, topic, region, title string, digest interface{}, ttl time.Duration) error {
|
||||
key := string(KeyDigest) + ":" + HashKey(topic, region, title)
|
||||
return c.Set(ctx, key, digest, ttl)
|
||||
}
|
||||
|
||||
func (c *RedisCache) GetCachedDigest(ctx context.Context, topic, region, title string, dest interface{}) error {
|
||||
key := string(KeyDigest) + ":" + HashKey(topic, region, title)
|
||||
return c.Get(ctx, key, dest)
|
||||
}
|
||||
|
||||
type MemoryCache struct {
|
||||
data map[string]cacheEntry
|
||||
}
|
||||
|
||||
type cacheEntry struct {
|
||||
value interface{}
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
func NewMemoryCache() *MemoryCache {
|
||||
return &MemoryCache{
|
||||
data: make(map[string]cacheEntry),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MemoryCache) Set(key string, value interface{}, ttl time.Duration) {
|
||||
c.data[key] = cacheEntry{
|
||||
value: value,
|
||||
expiresAt: time.Now().Add(ttl),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MemoryCache) Get(key string) (interface{}, bool) {
|
||||
entry, ok := c.data[key]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if time.Now().After(entry.expiresAt) {
|
||||
delete(c.data, key)
|
||||
return nil, false
|
||||
}
|
||||
return entry.value, true
|
||||
}
|
||||
|
||||
func (c *MemoryCache) Delete(key string) {
|
||||
delete(c.data, key)
|
||||
}
|
||||
|
||||
func (c *MemoryCache) Clear() {
|
||||
c.data = make(map[string]cacheEntry)
|
||||
}
|
||||
|
||||
func (c *MemoryCache) Cleanup() int {
|
||||
count := 0
|
||||
now := time.Now()
|
||||
for k, v := range c.data {
|
||||
if now.After(v.expiresAt) {
|
||||
delete(c.data, k)
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
Reference in New Issue
Block a user