- Implemented Citations component to display citation data. - Created WatchlistDrawer and ChatSidebar components for managing watchlists and AI chat functionality. - Integrated API calls for watchlist management and chat interactions. - Updated index.tsx to include new components in the main application layout. - Added API client in lib/api.ts for structured API interactions. - Developed Alerts, Dashboard, Portfolio, Routines, Screener, and Report pages with relevant data fetching and UI components. - Introduced styles in tokens.css for consistent theming across the application. - Configured TypeScript and Vite for project setup and development.
85 lines
1.8 KiB
Go
85 lines
1.8 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
// Cache wraps Redis with an in-memory TTL fallback so the demo runs without
|
|
// REDIS_URL. Reference data (registry/taxonomy/tags) uses 24h TTL.
|
|
type Cache struct {
|
|
rdb *redis.Client
|
|
mu sync.Mutex
|
|
mem map[string]memEntry
|
|
}
|
|
|
|
type memEntry struct {
|
|
val string
|
|
exp time.Time
|
|
}
|
|
|
|
// NewCache builds a cache; empty redisURL selects the in-memory fallback.
|
|
func NewCache(redisURL string) *Cache {
|
|
c := &Cache{mem: map[string]memEntry{}}
|
|
if redisURL != "" {
|
|
opt, err := redis.ParseURL(redisURL)
|
|
if err == nil {
|
|
c.rdb = redis.NewClient(opt)
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
// HasRedis reports whether a live Redis backs this cache.
|
|
func (c *Cache) HasRedis() bool { return c.rdb != nil }
|
|
|
|
// Get returns the cached value or "" on miss/expiry.
|
|
func (c *Cache) Get(ctx context.Context, key string) string {
|
|
if c.rdb != nil {
|
|
if v, err := c.rdb.Get(ctx, key).Result(); err == nil {
|
|
return v
|
|
}
|
|
return ""
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
e, ok := c.mem[key]
|
|
if !ok || time.Now().After(e.exp) {
|
|
return ""
|
|
}
|
|
return e.val
|
|
}
|
|
|
|
// Set stores a value with TTL.
|
|
func (c *Cache) Set(ctx context.Context, key, val string, ttl time.Duration) {
|
|
if c.rdb != nil {
|
|
_ = c.rdb.Set(ctx, key, val, ttl).Err()
|
|
return
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.mem[key] = memEntry{val: val, exp: time.Now().Add(ttl)}
|
|
}
|
|
|
|
// GetJSON unmarshals a cached JSON value; ok=false on miss.
|
|
func (c *Cache) GetJSON(ctx context.Context, key string, v any) bool {
|
|
raw := c.Get(ctx, key)
|
|
if raw == "" {
|
|
return false
|
|
}
|
|
return json.Unmarshal([]byte(raw), v) == nil
|
|
}
|
|
|
|
// SetJSON marshals and stores a value with TTL.
|
|
func (c *Cache) SetJSON(ctx context.Context, key string, v any, ttl time.Duration) {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Set(ctx, key, string(b), ttl)
|
|
}
|