aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/fetchers/app.bsky.go20
-rw-r--r--backend/fetchers/cache.go77
-rw-r--r--backend/main.go7
3 files changed, 94 insertions, 10 deletions
diff --git a/backend/fetchers/app.bsky.go b/backend/fetchers/app.bsky.go
index b6a133b..6b25f0b 100644
--- a/backend/fetchers/app.bsky.go
+++ b/backend/fetchers/app.bsky.go
@@ -5,6 +5,7 @@ import (
"fmt"
"llostre/backend/auth"
"llostre/backend/notes"
+ "log/slog"
"net/http"
)
@@ -12,22 +13,22 @@ type ListRecordsResponse struct {
Records []notes.AppBskyFeedPostRecord `json:"records"`
}
-func FetchAppBskyFeedPosts(did, pdsURL, accessToken string, keypair *auth.DPoPKeyPair) ([]notes.Note, error) {
+func FetchAppBskyFeedPosts(logger *slog.Logger, did, pdsURL, accessToken string, keypair *auth.DPoPKeyPair, notesCache *Cache) ([]notes.Note, error) {
url := fmt.Sprintf("%s/xrpc/com.atproto.repo.listRecords?repo=%s&collection=app.bsky.feed.post", pdsURL, did)
- fmt.Printf("Generating DPoP proof for: %s\n", url)
+ logger.Debug("generating DPoP proof", "url", url)
dpopProof, err := keypair.GenerateDPoPProof("GET", url, "")
if err != nil {
- fmt.Printf("DPoP generation failed: %v\n", err)
+ logger.Error("DPoP generation failed", "error", err)
return nil, err
}
- fmt.Printf("Making request to PDS\n")
+ logger.Debug("making request to PDS")
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
- req.Header.Set("Authorization", "Bearer "+accessToken)
+ req.Header.Set("Authorization", "Bearer " + accessToken)
req.Header.Set("DPoP", dpopProof)
resp, err := http.DefaultClient.Do(req)
@@ -43,18 +44,19 @@ func FetchAppBskyFeedPosts(did, pdsURL, accessToken string, keypair *auth.DPoPKe
profile, err := FetchProfile(did, pdsURL)
if err != nil {
- fmt.Printf("Profile fetch failed for %s: %v\n", did, err)
+ logger.Error("profile fetch failed", "did", did, "error", err)
} else {
- fmt.Printf("Fetched profile: %+v\n", profile)
+ logger.Debug("fetched profile")
}
- var posts []notes.Note
+ posts := make([]notes.Note, 0, len(response.Records))
for _, record := range response.Records {
bp, err := notes.AppBskyFeedPostFromRecord(record, profile)
if err != nil {
- fmt.Printf("skipping malformed record %s: %v\n", record.URI, err)
+ logger.Error("failed to convert record", "error", err, "uri", record.URI)
continue
}
+ notesCache.Set(bp.URI, bp)
posts = append(posts, *bp)
}
diff --git a/backend/fetchers/cache.go b/backend/fetchers/cache.go
new file mode 100644
index 0000000..12c68a8
--- /dev/null
+++ b/backend/fetchers/cache.go
@@ -0,0 +1,77 @@
+package fetchers
+
+import (
+ "llostre/backend/notes"
+ "sync"
+ "time"
+)
+
+type CacheEntry struct {
+ Note *notes.Note
+ ExpiresAt time.Time
+}
+
+type Cache struct {
+ posts map[string]*CacheEntry
+ mu sync.RWMutex
+}
+
+func NewCache() *Cache {
+ return &Cache{
+ posts: make(map[string]*CacheEntry),
+ }
+}
+
+func (c *Cache) Get(uri string) (*notes.Note, bool) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ entry, exists := c.posts[uri]
+
+ if !exists {
+ return nil, false
+ }
+
+ if time.Now().After(entry.ExpiresAt) {
+ delete(c.posts, uri)
+ return nil, false
+ }
+
+ return entry.Note, true
+}
+
+func (c *Cache) Set(uri string, note *notes.Note) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ ttl := time.Now().Add(15 * time.Minute)
+
+ c.posts[uri] = &CacheEntry{
+ Note: note,
+ ExpiresAt: ttl,
+ }
+}
+
+func (c *Cache) GetMany(uris []string) map[string]*notes.Note {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ result := make(map[string]*notes.Note)
+ now := time.Now()
+
+ for _, uri := range uris {
+ entry, exists := c.posts[uri]
+ if !exists {
+ continue
+ }
+
+ if now.After(entry.ExpiresAt) {
+ delete(c.posts, uri)
+ continue
+ }
+
+ result[uri] = entry.Note
+ }
+
+ return result
+}
diff --git a/backend/main.go b/backend/main.go
index d5fbb91..f6851da 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -21,6 +21,7 @@ var (
frontendURL string
sessionsMutex sync.RWMutex
sessions = make(map[string]*auth.OAuthState)
+ notesCache *fetchers.Cache
)
func init() {
@@ -51,6 +52,8 @@ func init() {
logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: logLevel,
}))
+
+ notesCache = fetchers.NewCache()
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
@@ -266,6 +269,7 @@ func postsHandler(w http.ResponseWriter, r *http.Request) {
logger.Debug("session public key", "key", session.KeyPair.PublicKey)
}
+ // Check for access token refresh
needsRefresh := time.Now().After(session.ExpiresAt)
pds := session.PDS
refreshToken := session.RefreshToken
@@ -289,7 +293,8 @@ func postsHandler(w http.ResponseWriter, r *http.Request) {
sessionsMutex.Unlock()
}
- posts, err := fetchers.FetchAppBskyFeedPosts(session.DID, session.PDS, session.AccessToken, session.KeyPair)
+ // Fetch app.bsky.feed.post records
+ posts, err := fetchers.FetchAppBskyFeedPosts(logger, session.DID, session.PDS, session.AccessToken, session.KeyPair, notesCache)
if err != nil {
logger.Error("fetching app.bsky.feed.posts failed", "error", err)
http.Error(w, err.Error(), http.StatusInternalServerError)