this repo has no description
1package db
2
3import (
4 "sort"
5 "time"
6
7 "github.com/bluesky-social/indigo/atproto/syntax"
8)
9
10type TimelineEvent struct {
11 *Repo
12 *Follow
13 *Star
14
15 EventAt time.Time
16
17 // optional: populate only if Repo is a fork
18 Source *Repo
19
20 // optional: populate only if event is Follow
21 *Profile
22 *FollowStats
23 *FollowStatus
24
25 // optional: populate only if event is Repo
26 IsStarred bool
27 StarCount int64
28}
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, limit int, loggedInUserDid string) ([]TimelineEvent, error) {
33 var events []TimelineEvent
34
35 repos, err := getTimelineRepos(e, limit, loggedInUserDid)
36 if err != nil {
37 return nil, err
38 }
39
40 stars, err := getTimelineStars(e, limit)
41 if err != nil {
42 return nil, err
43 }
44
45 follows, err := getTimelineFollows(e, limit, loggedInUserDid)
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, limit int, loggedInUserDid string) ([]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 starStatuses map[string]bool
94 if loggedInUserDid != "" {
95 var repoAts []syntax.ATURI
96 for _, r := range repos {
97 repoAts = append(repoAts, r.RepoAt())
98 }
99 var err error
100 starStatuses, err = GetStarStatuses(e, loggedInUserDid, repoAts)
101 if err != nil {
102 return nil, err
103 }
104 }
105
106 var events []TimelineEvent
107 for _, r := range repos {
108 var source *Repo
109 if r.Source != "" {
110 if origRepo, ok := uriToRepo[r.Source]; ok {
111 source = &origRepo
112 }
113 }
114
115 var isStarred bool
116 if starStatuses != nil {
117 isStarred = starStatuses[r.RepoAt().String()]
118 }
119
120 var starCount int64
121 if r.RepoStats != nil {
122 starCount = int64(r.RepoStats.StarCount)
123 }
124
125 events = append(events, TimelineEvent{
126 Repo: &r,
127 EventAt: r.Created,
128 Source: source,
129 IsStarred: isStarred,
130 StarCount: starCount,
131 })
132 }
133
134 return events, nil
135}
136
137func getTimelineStars(e Execer, limit int) ([]TimelineEvent, error) {
138 stars, err := GetStars(e, limit)
139 if err != nil {
140 return nil, err
141 }
142
143 // filter star records without a repo
144 n := 0
145 for _, s := range stars {
146 if s.Repo != nil {
147 stars[n] = s
148 n++
149 }
150 }
151 stars = stars[:n]
152
153 var events []TimelineEvent
154 for _, s := range stars {
155 events = append(events, TimelineEvent{
156 Star: &s,
157 EventAt: s.Created,
158 })
159 }
160
161 return events, nil
162}
163
164func getTimelineFollows(e Execer, limit int, loggedInUserDid string) ([]TimelineEvent, error) {
165 follows, err := GetFollows(e, limit)
166 if err != nil {
167 return nil, err
168 }
169
170 var subjects []string
171 for _, f := range follows {
172 subjects = append(subjects, f.SubjectDid)
173 }
174
175 if subjects == nil {
176 return nil, nil
177 }
178
179 profiles, err := GetProfiles(e, FilterIn("did", subjects))
180 if err != nil {
181 return nil, err
182 }
183
184 followStatMap, err := GetFollowerFollowingCounts(e, subjects)
185 if err != nil {
186 return nil, err
187 }
188
189 var followStatuses map[string]FollowStatus
190 if loggedInUserDid != "" {
191 followStatuses, err = GetFollowStatuses(e, loggedInUserDid, subjects)
192 if err != nil {
193 return nil, err
194 }
195 }
196
197 var events []TimelineEvent
198 for _, f := range follows {
199 profile, _ := profiles[f.SubjectDid]
200 followStatMap, _ := followStatMap[f.SubjectDid]
201
202 followStatus := IsNotFollowing
203 if followStatuses != nil {
204 followStatus = followStatuses[f.SubjectDid]
205 }
206
207 events = append(events, TimelineEvent{
208 Follow: &f,
209 Profile: profile,
210 FollowStats: &followStatMap,
211 FollowStatus: &followStatus,
212 EventAt: f.FollowedAt,
213 })
214 }
215
216 return events, nil
217}