aboutsummaryrefslogtreecommitdiff
path: root/backend/fetchers/cache.go
diff options
context:
space:
mode:
Diffstat (limited to 'backend/fetchers/cache.go')
-rw-r--r--backend/fetchers/cache.go184
1 files changed, 121 insertions, 63 deletions
diff --git a/backend/fetchers/cache.go b/backend/fetchers/cache.go
index 01c37fe..26be070 100644
--- a/backend/fetchers/cache.go
+++ b/backend/fetchers/cache.go
@@ -1,109 +1,167 @@
package fetchers
import (
- "llostre/backend/notes"
- "sync"
+ "context"
+ "encoding/json"
+ "fmt"
+ "sort"
"time"
-)
-type CacheEntry struct {
- Note *notes.Note
- ExpiresAt time.Time
-}
+ "llostre/backend/notes"
+
+ "github.com/redis/go-redis/v9"
+)
type Cache struct {
- posts map[string]*CacheEntry
- profiles map[string]*AuthorCacheEntry
- mu sync.RWMutex
+ client *redis.Client
+ ctx context.Context
+ expiryTime time.Duration
}
-func NewCache() *Cache {
- return &Cache{
- posts: make(map[string]*CacheEntry),
- profiles: make(map[string]*AuthorCacheEntry),
+func NewCache(addr, password string, expiryMinutes int) (*Cache, error) {
+ client := redis.NewClient(&redis.Options{
+ Addr: addr,
+ Password: password,
+ DB: 0,
+ })
+
+ ctx := context.Background()
+ if err := client.Ping(ctx).Err(); err != nil {
+ return nil, fmt.Errorf("redis connection failed: %w", err)
}
+
+ return &Cache{
+ client: client,
+ ctx: ctx,
+ expiryTime: time.Duration(expiryMinutes) * time.Minute,
+ }, nil
}
func (c *Cache) Get(uri string) (*notes.Note, bool) {
- c.mu.Lock()
- defer c.mu.Unlock()
+ key := fmt.Sprintf("post:%s", uri)
+ data, err := c.client.Get(c.ctx, key).Bytes()
+ if err != nil {
+ return nil, false
+ }
+
+ var note notes.Note
+ if err := json.Unmarshal(data, &note); err != nil {
+ return nil, false
+ }
+
+ return &note, true
+}
- entry, exists := c.posts[uri]
+func (c *Cache) Set(uri string, note *notes.Note) {
+ key := fmt.Sprintf("post:%s", uri)
+ data, err := json.Marshal(note)
+ if err != nil {
+ return
+ }
+
+ c.client.Set(c.ctx, key, data, c.expiryTime)
+}
- if !exists {
+func (c *Cache) GetAuthor(did string) (*notes.Author, bool) {
+ key := fmt.Sprintf("profile:%s", did)
+ data, err := c.client.Get(c.ctx, key).Bytes()
+ if err != nil {
return nil, false
}
- if time.Now().After(entry.ExpiresAt) {
- delete(c.posts, uri)
+ var author notes.Author
+ if err := json.Unmarshal(data, &author); err != nil {
return nil, false
}
- return entry.Note, true
+ return &author, true
}
-func (c *Cache) Set(uri string, note *notes.Note) {
- c.mu.Lock()
- defer c.mu.Unlock()
+func (c *Cache) SetAuthor(did string, author *notes.Author) {
+ key := fmt.Sprintf("profile:%s", did)
+ data, err := json.Marshal(author)
+ if err != nil {
+ return
+ }
+
+ c.client.Set(c.ctx, key, data, c.expiryTime)
+}
- c.posts[uri] = &CacheEntry{
- Note: note,
- ExpiresAt: time.Now().Add(15 * time.Minute),
+func (c *Cache) GetCursor(did string) (string, bool) {
+ key := fmt.Sprintf("cursor:%s", did)
+ cursor, err := c.client.Get(c.ctx, key).Result()
+ if err != nil {
+ return "", false
}
+ return cursor, true
}
-func (c *Cache) GetMany(uris []string) map[string]*notes.Note {
- c.mu.Lock()
- defer c.mu.Unlock()
+func (c *Cache) SetCursor(did, cursor string) {
+ key := fmt.Sprintf("cursor:%s", did)
+ if cursor == "" {
+ c.client.Del(c.ctx, key)
+ return
+ }
+ c.client.Set(c.ctx, key, cursor, 24*time.Hour)
+}
- result := make(map[string]*notes.Note)
- now := time.Now()
+func (c *Cache) GetPostsByDID(did string) ([]notes.Note, error) {
+ pattern := fmt.Sprintf("post:at://%s/*", did)
+ var posts []notes.Note
- for _, uri := range uris {
- entry, exists := c.posts[uri]
- if !exists {
+ iter := c.client.Scan(c.ctx, 0, pattern, 0).Iterator()
+ for iter.Next(c.ctx) {
+ data, err := c.client.Get(c.ctx, iter.Val()).Bytes()
+ if err != nil {
continue
}
-
- if now.After(entry.ExpiresAt) {
- delete(c.posts, uri)
+ var note notes.Note
+ if err := json.Unmarshal(data, &note); err != nil {
continue
}
+ posts = append(posts, note)
+ }
- result[uri] = entry.Note
+ if err := iter.Err(); err != nil {
+ return nil, err
}
- return result
+ sort.Slice(posts, func(i, j int) bool {
+ return posts[i].CreatedAt.After(posts[j].CreatedAt)
+ })
+
+ return posts, nil
}
-type AuthorCacheEntry struct {
- Author *notes.Author
- ExpiresAt time.Time
+// track oldest sent post per profile
+func (c *Cache) GetOldestSent(did string) (time.Time, bool) {
+ key := fmt.Sprintf("oldest_sent:%s", did)
+ ts, err := c.client.Get(c.ctx, key).Int64()
+ if err != nil {
+ return time.Time{}, false
+ }
+ return time.Unix(ts, 0), true
}
-func (c *Cache) GetProfile(did string) (*notes.Author, bool) {
- c.mu.Lock()
- defer c.mu.Unlock()
+func (c *Cache) SetOldestSent(did string, timestamp time.Time) {
+ key := fmt.Sprintf("oldest_sent:%s", did)
+ c.client.Set(c.ctx, key, timestamp.Unix(), 24*time.Hour)
+}
- entry, exists := c.profiles[did]
- if !exists {
- return nil, false
+// count posts older than timestamp for a profile
+func (c *Cache) CountPostsOlderThan(did string, timestamp time.Time) (int, error) {
+ posts, err := c.GetPostsByDID(did)
+ if err != nil {
+ return 0, err
}
-
- if time.Now().After(entry.ExpiresAt) {
- delete(c.profiles, did)
- return nil, false
+ for i, p := range posts {
+ if p.CreatedAt.Before(timestamp) {
+ return len(posts) - i, nil
+ }
}
-
- return entry.Author, true
+ return 0, nil
}
-func (c *Cache) SetAuthor(did string, author *notes.Author) {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- c.profiles[did] = &AuthorCacheEntry{
- Author: author,
- ExpiresAt: time.Now().Add(15 * time.Minute),
- }
+func (c *Cache) Close() error {
+ return c.client.Close()
}