forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
1package db 2 3import ( 4 "sort" 5 6 "github.com/bluesky-social/indigo/atproto/syntax" 7 "tangled.org/core/appview/models" 8) 9 10// TODO: this gathers heterogenous events from different sources and aggregates 11// them in code; if we did this entirely in sql, we could order and limit and paginate easily 12func MakeTimeline(e Execer, limit int, loggedInUserDid string, limitToUsersIsFollowing bool) ([]models.TimelineEvent, error) { 13 var events []models.TimelineEvent 14 15 var userIsFollowing []string 16 if limitToUsersIsFollowing { 17 following, err := GetFollowing(e, loggedInUserDid) 18 if err != nil { 19 return nil, err 20 } 21 22 userIsFollowing = make([]string, 0, len(following)) 23 for _, follow := range following { 24 userIsFollowing = append(userIsFollowing, follow.SubjectDid) 25 } 26 } 27 28 repos, err := getTimelineRepos(e, limit, loggedInUserDid, userIsFollowing) 29 if err != nil { 30 return nil, err 31 } 32 33 stars, err := getTimelineStars(e, limit, loggedInUserDid, userIsFollowing) 34 if err != nil { 35 return nil, err 36 } 37 38 follows, err := getTimelineFollows(e, limit, loggedInUserDid, userIsFollowing) 39 if err != nil { 40 return nil, err 41 } 42 43 events = append(events, repos...) 44 events = append(events, stars...) 45 events = append(events, follows...) 46 47 sort.Slice(events, func(i, j int) bool { 48 return events[i].EventAt.After(events[j].EventAt) 49 }) 50 51 // Limit the slice to 100 events 52 if len(events) > limit { 53 events = events[:limit] 54 } 55 56 return events, nil 57} 58 59func fetchStarStatuses(e Execer, loggedInUserDid string, repos []models.Repo) (map[string]bool, error) { 60 if loggedInUserDid == "" { 61 return nil, nil 62 } 63 64 var repoAts []syntax.ATURI 65 for _, r := range repos { 66 repoAts = append(repoAts, r.RepoAt()) 67 } 68 69 return GetStarStatuses(e, loggedInUserDid, repoAts) 70} 71 72func getRepoStarInfo(repo *models.Repo, starStatuses map[string]bool) (bool, int64) { 73 var isStarred bool 74 if starStatuses != nil { 75 isStarred = starStatuses[repo.RepoAt().String()] 76 } 77 78 var starCount int64 79 if repo.RepoStats != nil { 80 starCount = int64(repo.RepoStats.StarCount) 81 } 82 83 return isStarred, starCount 84} 85 86func getTimelineRepos(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { 87 filters := make([]filter, 0) 88 if userIsFollowing != nil { 89 filters = append(filters, FilterIn("did", userIsFollowing)) 90 } 91 92 repos, err := GetRepos(e, limit, filters...) 93 if err != nil { 94 return nil, err 95 } 96 97 // fetch all source repos 98 var args []string 99 for _, r := range repos { 100 if r.Source != "" { 101 args = append(args, r.Source) 102 } 103 } 104 105 var origRepos []models.Repo 106 if args != nil { 107 origRepos, err = GetRepos(e, 0, FilterIn("at_uri", args)) 108 } 109 if err != nil { 110 return nil, err 111 } 112 113 uriToRepo := make(map[string]models.Repo) 114 for _, r := range origRepos { 115 uriToRepo[r.RepoAt().String()] = r 116 } 117 118 starStatuses, err := fetchStarStatuses(e, loggedInUserDid, repos) 119 if err != nil { 120 return nil, err 121 } 122 123 var events []models.TimelineEvent 124 for _, r := range repos { 125 var source *models.Repo 126 if r.Source != "" { 127 if origRepo, ok := uriToRepo[r.Source]; ok { 128 source = &origRepo 129 } 130 } 131 132 isStarred, starCount := getRepoStarInfo(&r, starStatuses) 133 134 events = append(events, models.TimelineEvent{ 135 Repo: &r, 136 EventAt: r.Created, 137 Source: source, 138 IsStarred: isStarred, 139 StarCount: starCount, 140 }) 141 } 142 143 return events, nil 144} 145 146func getTimelineStars(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { 147 filters := make([]filter, 0) 148 if userIsFollowing != nil { 149 filters = append(filters, FilterIn("starred_by_did", userIsFollowing)) 150 } 151 152 stars, err := GetStars(e, limit, filters...) 153 if err != nil { 154 return nil, err 155 } 156 157 // filter star records without a repo 158 n := 0 159 for _, s := range stars { 160 if s.Repo != nil { 161 stars[n] = s 162 n++ 163 } 164 } 165 stars = stars[:n] 166 167 var repos []models.Repo 168 for _, s := range stars { 169 repos = append(repos, *s.Repo) 170 } 171 172 starStatuses, err := fetchStarStatuses(e, loggedInUserDid, repos) 173 if err != nil { 174 return nil, err 175 } 176 177 var events []models.TimelineEvent 178 for _, s := range stars { 179 isStarred, starCount := getRepoStarInfo(s.Repo, starStatuses) 180 181 events = append(events, models.TimelineEvent{ 182 Star: &s, 183 EventAt: s.Created, 184 IsStarred: isStarred, 185 StarCount: starCount, 186 }) 187 } 188 189 return events, nil 190} 191 192func getTimelineFollows(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { 193 filters := make([]filter, 0) 194 if userIsFollowing != nil { 195 filters = append(filters, FilterIn("user_did", userIsFollowing)) 196 } 197 198 follows, err := GetFollows(e, limit, filters...) 199 if err != nil { 200 return nil, err 201 } 202 203 var subjects []string 204 for _, f := range follows { 205 subjects = append(subjects, f.SubjectDid) 206 } 207 208 if subjects == nil { 209 return nil, nil 210 } 211 212 profiles, err := GetProfiles(e, FilterIn("did", subjects)) 213 if err != nil { 214 return nil, err 215 } 216 217 followStatMap, err := GetFollowerFollowingCounts(e, subjects) 218 if err != nil { 219 return nil, err 220 } 221 222 var followStatuses map[string]models.FollowStatus 223 if loggedInUserDid != "" { 224 followStatuses, err = GetFollowStatuses(e, loggedInUserDid, subjects) 225 if err != nil { 226 return nil, err 227 } 228 } 229 230 var events []models.TimelineEvent 231 for _, f := range follows { 232 profile, _ := profiles[f.SubjectDid] 233 followStatMap, _ := followStatMap[f.SubjectDid] 234 235 followStatus := models.IsNotFollowing 236 if followStatuses != nil { 237 followStatus = followStatuses[f.SubjectDid] 238 } 239 240 events = append(events, models.TimelineEvent{ 241 Follow: &f, 242 Profile: profile, 243 FollowStats: &followStatMap, 244 FollowStatus: &followStatus, 245 EventAt: f.FollowedAt, 246 }) 247 } 248 249 return events, nil 250}