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