appview: implement GetFollowersFollowingCounts to query follow stats for multiple dids in one go #505

merged
opened by ptr.pet targeting master from [deleted fork]: followers-following-list
  • moves FollowStats type from timeline.go into follow.go
  • makes existing GetFollowersFollowingCount also use FollowStats

Signed-off-by: dusk y.bera003.06@protonmail.com

Changed files
+110 -32
appview
+79 -3
appview/db/follow.go
··· 55 return err 56 } 57 58 - func GetFollowerFollowingCount(e Execer, did string) (int, int, error) { 59 followers, following := 0, 0 60 err := e.QueryRow( 61 `SELECT ··· 63 COUNT(CASE WHEN user_did = ? THEN 1 END) AS following 64 FROM follows;`, did, did).Scan(&followers, &following) 65 if err != nil { 66 - return 0, 0, err 67 } 68 - return followers, following, nil 69 } 70 71 func GetFollows(e Execer, limit int, filters ...filter) ([]Follow, error) {
··· 55 return err 56 } 57 58 + type FollowStats struct { 59 + Followers int 60 + Following int 61 + } 62 + 63 + func GetFollowerFollowingCount(e Execer, did string) (FollowStats, error) { 64 followers, following := 0, 0 65 err := e.QueryRow( 66 `SELECT ··· 68 COUNT(CASE WHEN user_did = ? THEN 1 END) AS following 69 FROM follows;`, did, did).Scan(&followers, &following) 70 if err != nil { 71 + return FollowStats{}, err 72 + } 73 + return FollowStats{ 74 + Followers: followers, 75 + Following: following, 76 + }, nil 77 + } 78 + 79 + func GetFollowerFollowingCounts(e Execer, dids []string) (map[string]FollowStats, error) { 80 + if len(dids) == 0 { 81 + return nil, nil 82 + } 83 + 84 + placeholders := make([]string, len(dids)) 85 + for i := range placeholders { 86 + placeholders[i] = "?" 87 } 88 + placeholderStr := strings.Join(placeholders, ",") 89 + 90 + args := make([]any, len(dids)*2) 91 + for i, did := range dids { 92 + args[i] = did 93 + args[i+len(dids)] = did 94 + } 95 + 96 + query := fmt.Sprintf(` 97 + select 98 + coalesce(f.did, g.did) as did, 99 + coalesce(f.followers, 0) as followers, 100 + coalesce(g.following, 0) as following 101 + from ( 102 + select subject_did as did, count(*) as followers 103 + from follows 104 + where subject_did in (%s) 105 + group by subject_did 106 + ) f 107 + full outer join ( 108 + select user_did as did, count(*) as following 109 + from follows 110 + where user_did in (%s) 111 + group by user_did 112 + ) g on f.did = g.did`, 113 + placeholderStr, placeholderStr) 114 + 115 + result := make(map[string]FollowStats) 116 + 117 + rows, err := e.Query(query, args...) 118 + if err != nil { 119 + return nil, err 120 + } 121 + defer rows.Close() 122 + 123 + for rows.Next() { 124 + var did string 125 + var followers, following int 126 + if err := rows.Scan(&did, &followers, &following); err != nil { 127 + return nil, err 128 + } 129 + result[did] = FollowStats{ 130 + Followers: followers, 131 + Following: following, 132 + } 133 + } 134 + 135 + for _, did := range dids { 136 + if _, exists := result[did]; !exists { 137 + result[did] = FollowStats{ 138 + Followers: 0, 139 + Following: 0, 140 + } 141 + } 142 + } 143 + 144 + return result, nil 145 } 146 147 func GetFollows(e Execer, limit int, filters ...filter) ([]Follow, error) {
+3 -15
appview/db/timeline.go
··· 20 *FollowStats 21 } 22 23 - type FollowStats struct { 24 - Followers int 25 - Following int 26 - } 27 - 28 const Limit = 50 29 30 // TODO: this gathers heterogenous events from different sources and aggregates ··· 156 return nil, err 157 } 158 159 - followStatMap := make(map[string]FollowStats) 160 - for _, s := range subjects { 161 - followers, following, err := GetFollowerFollowingCount(e, s) 162 - if err != nil { 163 - return nil, err 164 - } 165 - followStatMap[s] = FollowStats{ 166 - Followers: followers, 167 - Following: following, 168 - } 169 } 170 171 var events []TimelineEvent
··· 20 *FollowStats 21 } 22 23 const Limit = 50 24 25 // TODO: this gathers heterogenous events from different sources and aggregates ··· 151 return nil, err 152 } 153 154 + followStatMap, err := GetFollowerFollowingCounts(e, subjects) 155 + if err != nil { 156 + return nil, err 157 } 158 159 var events []TimelineEvent
+25 -11
appview/state/profile.go
··· 61 log.Printf("getting profile data for %s: %s", did, err) 62 } 63 64 - followersCount, followingCount, err := db.GetFollowerFollowingCount(s.db, did) 65 if err != nil { 66 log.Printf("getting follow stats for %s: %s", did, err) 67 } ··· 80 UserHandle: ident.Handle.String(), 81 Profile: profile, 82 FollowStatus: followStatus, 83 - FollowersCount: followersCount, 84 - FollowingCount: followingCount, 85 }, 86 } 87 } ··· 212 } 213 214 id := pageWithProfile.Id 215 216 follows, err := fetchFollows(s.db, id.DID.String()) 217 if err != nil { ··· 219 } 220 221 if len(follows) == 0 { 222 - return nil 223 } 224 225 followDids := make([]string, 0, len(follows)) ··· 230 profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids)) 231 if err != nil { 232 log.Printf("getting profile for %s: %s", followDids, err) 233 - return nil 234 } 235 236 - loggedInUser := pageWithProfile.LoggedInUser 237 var loggedInUserFollowing map[string]struct{} 238 if loggedInUser != nil { 239 following, err := db.GetFollowing(s.db, loggedInUser.Did) ··· 250 251 followCards := make([]pages.FollowCard, 0, len(follows)) 252 for _, did := range followDids { 253 - followersCount, followingCount, err := db.GetFollowerFollowingCount(s.db, did) 254 - if err != nil { 255 - log.Printf("getting follow stats for %s: %s", did, err) 256 } 257 followStatus := db.IsNotFollowing 258 if loggedInUserFollowing != nil { ··· 272 followCards = append(followCards, pages.FollowCard{ 273 UserDid: did, 274 FollowStatus: followStatus, 275 - FollowersCount: followersCount, 276 - FollowingCount: followingCount, 277 Profile: profile, 278 }) 279 } ··· 287 288 func (s *State) followersPage(w http.ResponseWriter, r *http.Request) { 289 followPage := s.followPage(w, r, db.GetFollowers, func(f db.Follow) string { return f.UserDid }) 290 291 s.pages.FollowersPage(w, pages.FollowersPageParams{ 292 LoggedInUser: followPage.LoggedInUser, ··· 297 298 func (s *State) followingPage(w http.ResponseWriter, r *http.Request) { 299 followPage := s.followPage(w, r, db.GetFollowing, func(f db.Follow) string { return f.SubjectDid }) 300 301 s.pages.FollowingPage(w, pages.FollowingPageParams{ 302 LoggedInUser: followPage.LoggedInUser,
··· 61 log.Printf("getting profile data for %s: %s", did, err) 62 } 63 64 + followStats, err := db.GetFollowerFollowingCount(s.db, did) 65 if err != nil { 66 log.Printf("getting follow stats for %s: %s", did, err) 67 } ··· 80 UserHandle: ident.Handle.String(), 81 Profile: profile, 82 FollowStatus: followStatus, 83 + FollowersCount: followStats.Followers, 84 + FollowingCount: followStats.Following, 85 }, 86 } 87 } ··· 212 } 213 214 id := pageWithProfile.Id 215 + loggedInUser := pageWithProfile.LoggedInUser 216 217 follows, err := fetchFollows(s.db, id.DID.String()) 218 if err != nil { ··· 220 } 221 222 if len(follows) == 0 { 223 + return &FollowsPageParams{ 224 + LoggedInUser: loggedInUser, 225 + Follows: []pages.FollowCard{}, 226 + Card: pageWithProfile.Card, 227 + } 228 } 229 230 followDids := make([]string, 0, len(follows)) ··· 235 profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids)) 236 if err != nil { 237 log.Printf("getting profile for %s: %s", followDids, err) 238 } 239 240 + followStatsMap, err := db.GetFollowerFollowingCounts(s.db, followDids) 241 + if err != nil { 242 + log.Printf("getting follow counts for %s: %s", followDids, err) 243 + } 244 + 245 var loggedInUserFollowing map[string]struct{} 246 if loggedInUser != nil { 247 following, err := db.GetFollowing(s.db, loggedInUser.Did) ··· 258 259 followCards := make([]pages.FollowCard, 0, len(follows)) 260 for _, did := range followDids { 261 + followStats, exists := followStatsMap[did] 262 + if !exists { 263 + followStats = db.FollowStats{} 264 } 265 followStatus := db.IsNotFollowing 266 if loggedInUserFollowing != nil { ··· 280 followCards = append(followCards, pages.FollowCard{ 281 UserDid: did, 282 FollowStatus: followStatus, 283 + FollowersCount: followStats.Followers, 284 + FollowingCount: followStats.Following, 285 Profile: profile, 286 }) 287 } ··· 295 296 func (s *State) followersPage(w http.ResponseWriter, r *http.Request) { 297 followPage := s.followPage(w, r, db.GetFollowers, func(f db.Follow) string { return f.UserDid }) 298 + if followPage == nil { 299 + return 300 + } 301 302 s.pages.FollowersPage(w, pages.FollowersPageParams{ 303 LoggedInUser: followPage.LoggedInUser, ··· 308 309 func (s *State) followingPage(w http.ResponseWriter, r *http.Request) { 310 followPage := s.followPage(w, r, db.GetFollowing, func(f db.Follow) string { return f.SubjectDid }) 311 + if followPage == nil { 312 + return 313 + } 314 315 s.pages.FollowingPage(w, pages.FollowingPageParams{ 316 LoggedInUser: followPage.LoggedInUser,
+3 -3
appview/strings/strings.go
··· 203 followStatus = db.GetFollowStatus(s.Db, loggedInUser.Did, id.DID.String()) 204 } 205 206 - followersCount, followingCount, err := db.GetFollowerFollowingCount(s.Db, id.DID.String()) 207 if err != nil { 208 l.Error("failed to get follow stats", "err", err) 209 } ··· 215 UserHandle: id.Handle.String(), 216 Profile: profile, 217 FollowStatus: followStatus, 218 - FollowersCount: followersCount, 219 - FollowingCount: followingCount, 220 }, 221 Strings: all, 222 })
··· 203 followStatus = db.GetFollowStatus(s.Db, loggedInUser.Did, id.DID.String()) 204 } 205 206 + followStats, err := db.GetFollowerFollowingCount(s.Db, id.DID.String()) 207 if err != nil { 208 l.Error("failed to get follow stats", "err", err) 209 } ··· 215 UserHandle: id.Handle.String(), 216 Profile: profile, 217 FollowStatus: followStatus, 218 + FollowersCount: followStats.Followers, 219 + FollowingCount: followStats.Following, 220 }, 221 Strings: all, 222 })