forked from
tangled.org/core
Monorepo for Tangled
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}