this repo has no description
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 := GetFollows(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 profiles, err := GetProfiles(e, FilterIn("did", subjects))
155 if err != nil {
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
172 for _, f := range follows {
173 profile, _ := profiles[f.SubjectDid]
174 followStatMap, _ := followStatMap[f.SubjectDid]
175
176 events = append(events, TimelineEvent{
177 Follow: &f,
178 Profile: profile,
179 FollowStats: &followStatMap,
180 EventAt: f.FollowedAt,
181 })
182 }
183
184 return events, nil
185}