forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
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/orm"
9)
10
11// TODO: this gathers heterogenous events from different sources and aggregates
12// them in code; if we did this entirely in sql, we could order and limit and paginate easily
13func MakeTimeline(e Execer, limit int, loggedInUserDid string, limitToUsersIsFollowing bool) ([]models.TimelineEvent, error) {
14 var events []models.TimelineEvent
15
16 var userIsFollowing []string
17 if limitToUsersIsFollowing {
18 following, err := GetFollowing(e, loggedInUserDid)
19 if err != nil {
20 return nil, err
21 }
22
23 userIsFollowing = make([]string, 0, len(following))
24 for _, follow := range following {
25 userIsFollowing = append(userIsFollowing, follow.SubjectDid)
26 }
27 }
28
29 repos, err := getTimelineRepos(e, limit, loggedInUserDid, userIsFollowing)
30 if err != nil {
31 return nil, err
32 }
33
34 stars, err := getTimelineStars(e, limit, loggedInUserDid, userIsFollowing)
35 if err != nil {
36 return nil, err
37 }
38
39 follows, err := getTimelineFollows(e, limit, loggedInUserDid, userIsFollowing)
40 if err != nil {
41 return nil, err
42 }
43
44 events = append(events, repos...)
45 events = append(events, stars...)
46 events = append(events, follows...)
47
48 sort.Slice(events, func(i, j int) bool {
49 return events[i].EventAt.After(events[j].EventAt)
50 })
51
52 // Limit the slice to 100 events
53 if len(events) > limit {
54 events = events[:limit]
55 }
56
57 return events, nil
58}
59
60func fetchStarStatuses(e Execer, loggedInUserDid string, repos []models.Repo) (map[string]bool, error) {
61 if loggedInUserDid == "" {
62 return nil, nil
63 }
64
65 var repoAts []syntax.ATURI
66 for _, r := range repos {
67 repoAts = append(repoAts, r.RepoAt())
68 }
69
70 return GetStarStatuses(e, loggedInUserDid, repoAts)
71}
72
73func getRepoStarInfo(repo *models.Repo, starStatuses map[string]bool) (bool, int64) {
74 var isStarred bool
75 if starStatuses != nil {
76 isStarred = starStatuses[repo.RepoAt().String()]
77 }
78
79 var starCount int64
80 if repo.RepoStats != nil {
81 starCount = int64(repo.RepoStats.StarCount)
82 }
83
84 return isStarred, starCount
85}
86
87func getTimelineRepos(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
88 filters := make([]orm.Filter, 0)
89 if userIsFollowing != nil {
90 filters = append(filters, orm.FilterIn("did", userIsFollowing))
91 }
92
93 repos, err := GetRepos(e, limit, filters...)
94 if err != nil {
95 return nil, err
96 }
97
98 // fetch all source repos
99 var args []string
100 for _, r := range repos {
101 if r.Source != "" {
102 args = append(args, r.Source)
103 }
104 }
105
106 var origRepos []models.Repo
107 if args != nil {
108 origRepos, err = GetRepos(e, 0, orm.FilterIn("at_uri", args))
109 }
110 if err != nil {
111 return nil, err
112 }
113
114 uriToRepo := make(map[string]models.Repo)
115 for _, r := range origRepos {
116 uriToRepo[r.RepoAt().String()] = r
117 }
118
119 starStatuses, err := fetchStarStatuses(e, loggedInUserDid, repos)
120 if err != nil {
121 return nil, err
122 }
123
124 var events []models.TimelineEvent
125 for _, r := range repos {
126 var source *models.Repo
127 if r.Source != "" {
128 if origRepo, ok := uriToRepo[r.Source]; ok {
129 source = &origRepo
130 }
131 }
132
133 isStarred, starCount := getRepoStarInfo(&r, starStatuses)
134
135 events = append(events, models.TimelineEvent{
136 Repo: &r,
137 EventAt: r.Created,
138 Source: source,
139 IsStarred: isStarred,
140 StarCount: starCount,
141 })
142 }
143
144 return events, nil
145}
146
147func getTimelineStars(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
148 filters := make([]orm.Filter, 0)
149 if userIsFollowing != nil {
150 filters = append(filters, orm.FilterIn("did", userIsFollowing))
151 }
152
153 stars, err := GetRepoStars(e, limit, filters...)
154 if err != nil {
155 return nil, err
156 }
157
158 var repos []models.Repo
159 for _, s := range stars {
160 repos = append(repos, *s.Repo)
161 }
162
163 starStatuses, err := fetchStarStatuses(e, loggedInUserDid, repos)
164 if err != nil {
165 return nil, err
166 }
167
168 var events []models.TimelineEvent
169 for _, s := range stars {
170 isStarred, starCount := getRepoStarInfo(s.Repo, starStatuses)
171
172 events = append(events, models.TimelineEvent{
173 RepoStar: &s,
174 EventAt: s.Created,
175 IsStarred: isStarred,
176 StarCount: starCount,
177 })
178 }
179
180 return events, nil
181}
182
183func getTimelineFollows(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
184 filters := make([]orm.Filter, 0)
185 if userIsFollowing != nil {
186 filters = append(filters, orm.FilterIn("user_did", userIsFollowing))
187 }
188
189 follows, err := GetFollows(e, limit, filters...)
190 if err != nil {
191 return nil, err
192 }
193
194 var subjects []string
195 for _, f := range follows {
196 subjects = append(subjects, f.SubjectDid)
197 }
198
199 if subjects == nil {
200 return nil, nil
201 }
202
203 profiles, err := GetProfiles(e, orm.FilterIn("did", subjects))
204 if err != nil {
205 return nil, err
206 }
207
208 followStatMap, err := GetFollowerFollowingCounts(e, subjects)
209 if err != nil {
210 return nil, err
211 }
212
213 var followStatuses map[string]models.FollowStatus
214 if loggedInUserDid != "" {
215 followStatuses, err = GetFollowStatuses(e, loggedInUserDid, subjects)
216 if err != nil {
217 return nil, err
218 }
219 }
220
221 var events []models.TimelineEvent
222 for _, f := range follows {
223 profile, _ := profiles[f.SubjectDid]
224 followStatMap, _ := followStatMap[f.SubjectDid]
225
226 followStatus := models.IsNotFollowing
227 if followStatuses != nil {
228 followStatus = followStatuses[f.SubjectDid]
229 }
230
231 events = append(events, models.TimelineEvent{
232 Follow: &f,
233 Profile: profile,
234 FollowStats: &followStatMap,
235 FollowStatus: &followStatus,
236 EventAt: f.FollowedAt,
237 })
238 }
239
240 return events, nil
241}