forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
1package db 2 3import ( 4 "sort" 5 "time" 6) 7 8type TimelineEvent struct { 9 *Repo 10 *Follow 11 *Star 12 13 EventAt time.Time 14 15 // optional: populate only if Repo is a fork 16 Source *Repo 17 18 // optional: populate only if event is Follow 19 *Profile 20 *FollowStats 21} 22 23type FollowStats struct { 24 Followers int 25 Following int 26} 27 28const Limit = 50 29 30// TODO: this gathers heterogenous events from different sources and aggregates 31// them in code; if we did this entirely in sql, we could order and limit and paginate easily 32func MakeTimeline(e Execer) ([]TimelineEvent, error) { 33 var events []TimelineEvent 34 35 repos, err := getTimelineRepos(e) 36 if err != nil { 37 return nil, err 38 } 39 40 stars, err := getTimelineStars(e) 41 if err != nil { 42 return nil, err 43 } 44 45 follows, err := getTimelineFollows(e) 46 if err != nil { 47 return nil, err 48 } 49 50 events = append(events, repos...) 51 events = append(events, stars...) 52 events = append(events, follows...) 53 54 sort.Slice(events, func(i, j int) bool { 55 return events[i].EventAt.After(events[j].EventAt) 56 }) 57 58 // Limit the slice to 100 events 59 if len(events) > Limit { 60 events = events[:Limit] 61 } 62 63 return events, nil 64} 65 66func getTimelineRepos(e Execer) ([]TimelineEvent, error) { 67 repos, err := GetRepos(e, Limit) 68 if err != nil { 69 return nil, err 70 } 71 72 // fetch all source repos 73 var args []string 74 for _, r := range repos { 75 if r.Source != "" { 76 args = append(args, r.Source) 77 } 78 } 79 80 var origRepos []Repo 81 if args != nil { 82 origRepos, err = GetRepos(e, 0, FilterIn("at_uri", args)) 83 } 84 if err != nil { 85 return nil, err 86 } 87 88 uriToRepo := make(map[string]Repo) 89 for _, r := range origRepos { 90 uriToRepo[r.RepoAt().String()] = r 91 } 92 93 var events []TimelineEvent 94 for _, r := range repos { 95 var source *Repo 96 if r.Source != "" { 97 if origRepo, ok := uriToRepo[r.Source]; ok { 98 source = &origRepo 99 } 100 } 101 102 events = append(events, TimelineEvent{ 103 Repo: &r, 104 EventAt: r.Created, 105 Source: source, 106 }) 107 } 108 109 return events, nil 110} 111 112func getTimelineStars(e Execer) ([]TimelineEvent, error) { 113 stars, err := GetStars(e, Limit) 114 if err != nil { 115 return nil, err 116 } 117 118 // filter star records without a repo 119 n := 0 120 for _, s := range stars { 121 if s.Repo != nil { 122 stars[n] = s 123 n++ 124 } 125 } 126 stars = stars[:n] 127 128 var events []TimelineEvent 129 for _, s := range stars { 130 events = append(events, TimelineEvent{ 131 Star: &s, 132 EventAt: s.Created, 133 }) 134 } 135 136 return events, nil 137} 138 139func getTimelineFollows(e Execer) ([]TimelineEvent, error) { 140 follows, err := GetAllFollows(e, Limit) 141 if err != nil { 142 return nil, err 143 } 144 145 var subjects []string 146 for _, f := range follows { 147 subjects = append(subjects, f.SubjectDid) 148 } 149 150 if subjects == nil { 151 return nil, nil 152 } 153 154 profileMap := make(map[string]Profile) 155 profiles, err := GetProfiles(e, FilterIn("did", subjects)) 156 if err != nil { 157 return nil, err 158 } 159 for _, p := range profiles { 160 profileMap[p.Did] = p 161 } 162 163 followStatMap := make(map[string]FollowStats) 164 for _, s := range subjects { 165 followers, following, err := GetFollowerFollowingCount(e, s) 166 if err != nil { 167 return nil, err 168 } 169 followStatMap[s] = FollowStats{ 170 Followers: followers, 171 Following: following, 172 } 173 } 174 175 var events []TimelineEvent 176 for _, f := range follows { 177 profile, _ := profileMap[f.SubjectDid] 178 followStatMap, _ := followStatMap[f.SubjectDid] 179 180 events = append(events, TimelineEvent{ 181 Follow: &f, 182 Profile: &profile, 183 FollowStats: &followStatMap, 184 EventAt: f.FollowedAt, 185 }) 186 } 187 188 return events, nil 189}