From d26339c320bb7878548565fe201fede6ab49e595 Mon Sep 17 00:00:00 2001 From: dusk Date: Sun, 17 Aug 2025 15:07:46 +0300 Subject: [PATCH] appview: implement GetFollowersFollowingCounts to query follow stats for multiple dids in one go Change-Id: zxotvwroyxnnulmuuvympswxxwotzwlz - moves FollowStats type from timeline.go into follow.go - makes existing GetFollowersFollowingCount also use FollowStats Signed-off-by: dusk --- appview/db/follow.go | 82 ++++++++++++++++++++++++++++++++++++-- appview/db/timeline.go | 18 ++------- appview/state/profile.go | 21 ++++++---- appview/strings/strings.go | 6 +-- 4 files changed, 98 insertions(+), 29 deletions(-) diff --git a/appview/db/follow.go b/appview/db/follow.go index 8eb53c3..b28d57d 100644 --- a/appview/db/follow.go +++ b/appview/db/follow.go @@ -55,7 +55,12 @@ func DeleteFollowByRkey(e Execer, userDid, rkey string) error { return err } -func GetFollowerFollowingCount(e Execer, did string) (int, int, error) { +type FollowStats struct { + Followers int + Following int +} + +func GetFollowerFollowingCount(e Execer, did string) (FollowStats, error) { followers, following := 0, 0 err := e.QueryRow( `SELECT @@ -63,9 +68,80 @@ func GetFollowerFollowingCount(e Execer, did string) (int, int, error) { COUNT(CASE WHEN user_did = ? THEN 1 END) AS following FROM follows;`, did, did).Scan(&followers, &following) if err != nil { - return 0, 0, err + return FollowStats{}, err + } + return FollowStats{ + Followers: followers, + Following: following, + }, nil +} + +func GetFollowerFollowingCounts(e Execer, dids []string) (map[string]FollowStats, error) { + if len(dids) == 0 { + return nil, nil + } + + placeholders := make([]string, len(dids)) + for i := range placeholders { + placeholders[i] = "?" } - return followers, following, nil + placeholderStr := strings.Join(placeholders, ",") + + args := make([]any, len(dids)*2) + for i, did := range dids { + args[i] = did + args[i+len(dids)] = did + } + + query := fmt.Sprintf(` + select + coalesce(f.did, g.did) as did, + coalesce(f.followers, 0) as followers, + coalesce(g.following, 0) as following + from ( + select subject_did as did, count(*) as followers + from follows + where subject_did in (%s) + group by subject_did + ) f + full outer join ( + select user_did as did, count(*) as following + from follows + where user_did in (%s) + group by user_did + ) g on f.did = g.did`, + placeholderStr, placeholderStr) + + result := make(map[string]FollowStats) + + rows, err := e.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var did string + var followers, following int + if err := rows.Scan(&did, &followers, &following); err != nil { + return nil, err + } + result[did] = FollowStats{ + Followers: followers, + Following: following, + } + } + + for _, did := range dids { + if _, exists := result[did]; !exists { + result[did] = FollowStats{ + Followers: 0, + Following: 0, + } + } + } + + return result, nil } func GetFollows(e Execer, limit int, filters ...filter) ([]Follow, error) { diff --git a/appview/db/timeline.go b/appview/db/timeline.go index 8acbd93..b2b69dd 100644 --- a/appview/db/timeline.go +++ b/appview/db/timeline.go @@ -20,11 +20,6 @@ type TimelineEvent struct { *FollowStats } -type FollowStats struct { - Followers int - Following int -} - const Limit = 50 // TODO: this gathers heterogenous events from different sources and aggregates @@ -156,16 +151,9 @@ func getTimelineFollows(e Execer) ([]TimelineEvent, error) { return nil, err } - followStatMap := make(map[string]FollowStats) - for _, s := range subjects { - followers, following, err := GetFollowerFollowingCount(e, s) - if err != nil { - return nil, err - } - followStatMap[s] = FollowStats{ - Followers: followers, - Following: following, - } + followStatMap, err := GetFollowerFollowingCounts(e, subjects) + if err != nil { + return nil, err } var events []TimelineEvent diff --git a/appview/state/profile.go b/appview/state/profile.go index 53a04f2..a7ff27b 100644 --- a/appview/state/profile.go +++ b/appview/state/profile.go @@ -63,7 +63,7 @@ func (s *State) profilePage(w http.ResponseWriter, r *http.Request) *ProfilePage return nil } - followersCount, followingCount, err := db.GetFollowerFollowingCount(s.db, did) + followStats, err := db.GetFollowerFollowingCount(s.db, did) if err != nil { log.Printf("getting follow stats for %s: %s", did, err) } @@ -82,8 +82,8 @@ func (s *State) profilePage(w http.ResponseWriter, r *http.Request) *ProfilePage UserHandle: ident.Handle.String(), Profile: profile, FollowStatus: followStatus, - FollowersCount: followersCount, - FollowingCount: followingCount, + FollowersCount: followStats.Followers, + FollowingCount: followStats.Following, }, } } @@ -241,6 +241,11 @@ func (s *State) followPage(w http.ResponseWriter, r *http.Request, fetchFollows return FollowsPageParams{}, err } + followStatsMap, err := db.GetFollowerFollowingCounts(s.db, followDids) + if err != nil { + log.Printf("getting follow counts for %s: %s", followDids, err) + } + var loggedInUserFollowing map[string]struct{} if loggedInUser != nil { following, err := db.GetFollowing(s.db, loggedInUser.Did) @@ -257,9 +262,9 @@ func (s *State) followPage(w http.ResponseWriter, r *http.Request, fetchFollows followCards := make([]pages.FollowCard, 0, len(follows)) for _, did := range followDids { - followersCount, followingCount, err := db.GetFollowerFollowingCount(s.db, did) - if err != nil { - log.Printf("getting follow stats for %s: %s", did, err) + followStats, exists := followStatsMap[did] + if !exists { + followStats = db.FollowStats{} } followStatus := db.IsNotFollowing if loggedInUserFollowing != nil { @@ -279,8 +284,8 @@ func (s *State) followPage(w http.ResponseWriter, r *http.Request, fetchFollows followCards = append(followCards, pages.FollowCard{ UserDid: did, FollowStatus: followStatus, - FollowersCount: followersCount, - FollowingCount: followingCount, + FollowersCount: followStats.Followers, + FollowingCount: followStats.Following, Profile: profile, }) } diff --git a/appview/strings/strings.go b/appview/strings/strings.go index ba82e4a..4af0b36 100644 --- a/appview/strings/strings.go +++ b/appview/strings/strings.go @@ -202,7 +202,7 @@ func (s *Strings) dashboard(w http.ResponseWriter, r *http.Request) { followStatus = db.GetFollowStatus(s.Db, loggedInUser.Did, id.DID.String()) } - followersCount, followingCount, err := db.GetFollowerFollowingCount(s.Db, id.DID.String()) + followStats, err := db.GetFollowerFollowingCount(s.Db, id.DID.String()) if err != nil { l.Error("failed to get follow stats", "err", err) } @@ -214,8 +214,8 @@ func (s *Strings) dashboard(w http.ResponseWriter, r *http.Request) { UserHandle: id.Handle.String(), Profile: profile, FollowStatus: followStatus, - FollowersCount: followersCount, - FollowingCount: followingCount, + FollowersCount: followStats.Followers, + FollowingCount: followStats.Following, }, Strings: all, }) -- 2.43.0