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