diff options
Diffstat (limited to 'backend/fetchers/cache.go')
| -rw-r--r-- | backend/fetchers/cache.go | 184 |
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, ¬e); err != nil { + return nil, false + } + + return ¬e, 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, ¬e); 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() } |
