forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
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}