Coffee journaling on ATProto (alpha) alpha.arabica.social
coffee
17
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: reduce number of pds requests on profile page to zero

authored by

Patrick Dewey and committed by tangled.org 65d05df5 ae06773c

+88 -26
+43
internal/firehose/index.go
··· 1071 1071 did, string(data), cached.ExpiresAt.Format(time.RFC3339Nano)) 1072 1072 } 1073 1073 1074 + // GetDIDByHandle looks up a DID from the profile cache by handle. 1075 + // Returns the DID and true if found, or empty string and false if not cached. 1076 + // This avoids a ResolveHandle API call for known Arabica users. 1077 + func (idx *FeedIndex) GetDIDByHandle(ctx context.Context, handle string) (string, bool) { 1078 + // Check in-memory cache first 1079 + idx.profileCacheMu.RLock() 1080 + for did, cached := range idx.profileCache { 1081 + if cached.Profile != nil && cached.Profile.Handle == handle && time.Now().Before(cached.ExpiresAt) { 1082 + idx.profileCacheMu.RUnlock() 1083 + return did, true 1084 + } 1085 + } 1086 + idx.profileCacheMu.RUnlock() 1087 + 1088 + // Check persistent store 1089 + rows, err := idx.db.QueryContext(ctx, `SELECT did, data FROM profiles`) 1090 + if err != nil { 1091 + return "", false 1092 + } 1093 + defer rows.Close() 1094 + 1095 + for rows.Next() { 1096 + var did, dataStr string 1097 + if err := rows.Scan(&did, &dataStr); err != nil { 1098 + continue 1099 + } 1100 + cached := &CachedProfile{} 1101 + if err := json.Unmarshal([]byte(dataStr), cached); err != nil || cached.Profile == nil { 1102 + continue 1103 + } 1104 + if cached.Profile.Handle == handle { 1105 + // Promote to in-memory cache 1106 + cached.ExpiresAt = time.Now().Add(idx.profileTTL) 1107 + idx.profileCacheMu.Lock() 1108 + idx.profileCache[did] = cached 1109 + idx.profileCacheMu.Unlock() 1110 + return did, true 1111 + } 1112 + } 1113 + 1114 + return "", false 1115 + } 1116 + 1074 1117 // InvalidateProfile removes a DID's profile from both the in-memory and persistent 1075 1118 // caches. The next GetProfile call will re-fetch from the API. 1076 1119 func (idx *FeedIndex) InvalidateProfile(did string) {
+45 -26
internal/handlers/profile.go
··· 406 406 var err error 407 407 408 408 if strings.HasPrefix(actor, "did:") { 409 - // It's already a DID 410 409 did = actor 411 410 } else { 412 - // It's a handle, resolve to DID 413 - did, err = publicClient.ResolveHandle(ctx, actor) 414 - if err != nil { 415 - log.Warn().Err(err).Str("handle", actor).Msg("Failed to resolve handle") 416 - http.Error(w, "User not found", http.StatusNotFound) 417 - return 411 + // Try feed index cache first, fall back to API 412 + if h.feedIndex != nil { 413 + did, _ = h.feedIndex.GetDIDByHandle(ctx, actor) 418 414 } 419 - 420 - // Redirect to canonical URL with handle (we'll get the handle from profile) 421 - // For now, continue with the DID we have 415 + if did == "" { 416 + did, err = publicClient.ResolveHandle(ctx, actor) 417 + if err != nil { 418 + log.Warn().Err(err).Str("handle", actor).Msg("Failed to resolve handle") 419 + http.Error(w, "User not found", http.StatusNotFound) 420 + return 421 + } 422 + } 422 423 } 423 424 424 425 // Check if user is blacklisted ··· 431 432 return 432 433 } 433 434 434 - // Fetch profile 435 - profile, err := publicClient.GetProfile(ctx, did) 436 - if err != nil { 437 - log.Warn().Err(err).Str("did", did).Msg("Failed to fetch profile") 438 - http.Error(w, "User not found", http.StatusNotFound) 439 - return 435 + // Fetch profile — try feed index cache first, fall back to API 436 + var profile *atproto.Profile 437 + if h.feedIndex != nil { 438 + profile, _ = h.feedIndex.GetProfile(ctx, did) 439 + } 440 + if profile == nil { 441 + profile, err = publicClient.GetProfile(ctx, did) 442 + if err != nil { 443 + log.Warn().Err(err).Str("did", did).Msg("Failed to fetch profile") 444 + http.Error(w, "User not found", http.StatusNotFound) 445 + return 446 + } 440 447 } 441 448 442 449 // If the URL used a DID but we have the handle, redirect to the canonical handle URL ··· 535 542 if strings.HasPrefix(actor, "did:") { 536 543 did = actor 537 544 } else { 538 - did, err = publicClient.ResolveHandle(ctx, actor) 539 - if err != nil { 540 - log.Warn().Err(err).Str("handle", actor).Msg("Failed to resolve handle") 541 - http.Error(w, "User not found", http.StatusNotFound) 542 - return 545 + // Try feed index cache first, fall back to API 546 + if h.feedIndex != nil { 547 + did, _ = h.feedIndex.GetDIDByHandle(ctx, actor) 548 + } 549 + if did == "" { 550 + did, err = publicClient.ResolveHandle(ctx, actor) 551 + if err != nil { 552 + log.Warn().Err(err).Str("handle", actor).Msg("Failed to resolve handle") 553 + http.Error(w, "User not found", http.StatusNotFound) 554 + return 555 + } 543 556 } 544 557 } 545 558 ··· 581 594 isAuthenticated := err == nil && didStr != "" 582 595 isOwnProfile := isAuthenticated && didStr == did 583 596 584 - // Get profile for card rendering 585 - profile, err := publicClient.GetProfile(ctx, did) 586 - if err != nil { 587 - log.Warn().Err(err).Str("did", did).Msg("Failed to fetch profile for profile partial") 588 - // Continue without profile - cards will show limited info 597 + // Get profile for card rendering — try feed index cache first 598 + var profile *atproto.Profile 599 + if h.feedIndex != nil { 600 + profile, _ = h.feedIndex.GetProfile(ctx, did) 601 + } 602 + if profile == nil { 603 + profile, err = publicClient.GetProfile(ctx, did) 604 + if err != nil { 605 + log.Warn().Err(err).Str("did", did).Msg("Failed to fetch profile for profile partial") 606 + // Continue without profile - cards will show limited info 607 + } 589 608 } 590 609 591 610 // Use handle from profile or fallback