forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package db
2
3import (
4 "fmt"
5 "log"
6 "strings"
7 "time"
8
9 "tangled.org/core/appview/models"
10 "tangled.org/core/orm"
11)
12
13func AddFollow(e Execer, follow *models.Follow) error {
14 query := `insert or ignore into follows (user_did, subject_did, rkey) values (?, ?, ?)`
15 _, err := e.Exec(query, follow.UserDid, follow.SubjectDid, follow.Rkey)
16 return err
17}
18
19// Get a follow record
20func GetFollow(e Execer, userDid, subjectDid string) (*models.Follow, error) {
21 query := `select user_did, subject_did, followed_at, rkey from follows where user_did = ? and subject_did = ?`
22 row := e.QueryRow(query, userDid, subjectDid)
23
24 var follow models.Follow
25 var followedAt string
26 err := row.Scan(&follow.UserDid, &follow.SubjectDid, &followedAt, &follow.Rkey)
27 if err != nil {
28 return nil, err
29 }
30
31 followedAtTime, err := time.Parse(time.RFC3339, followedAt)
32 if err != nil {
33 log.Println("unable to determine followed at time")
34 follow.FollowedAt = time.Now()
35 } else {
36 follow.FollowedAt = followedAtTime
37 }
38
39 return &follow, nil
40}
41
42// Remove a follow
43func DeleteFollow(e Execer, userDid, subjectDid string) error {
44 _, err := e.Exec(`delete from follows where user_did = ? and subject_did = ?`, userDid, subjectDid)
45 return err
46}
47
48// Remove a follow
49func DeleteFollowByRkey(e Execer, userDid, rkey string) error {
50 _, err := e.Exec(`delete from follows where user_did = ? and rkey = ?`, userDid, rkey)
51 return err
52}
53
54func GetFollowerFollowingCount(e Execer, did string) (models.FollowStats, error) {
55 var followers, following int64
56 err := e.QueryRow(
57 `SELECT
58 COUNT(CASE WHEN subject_did = ? THEN 1 END) AS followers,
59 COUNT(CASE WHEN user_did = ? THEN 1 END) AS following
60 FROM follows;`, did, did).Scan(&followers, &following)
61 if err != nil {
62 return models.FollowStats{}, err
63 }
64 return models.FollowStats{
65 Followers: followers,
66 Following: following,
67 }, nil
68}
69
70func GetFollowerFollowingCounts(e Execer, dids []string) (map[string]models.FollowStats, error) {
71 if len(dids) == 0 {
72 return nil, nil
73 }
74
75 placeholders := make([]string, len(dids))
76 for i := range placeholders {
77 placeholders[i] = "?"
78 }
79 placeholderStr := strings.Join(placeholders, ",")
80
81 args := make([]any, len(dids)*2)
82 for i, did := range dids {
83 args[i] = did
84 args[i+len(dids)] = did
85 }
86
87 query := fmt.Sprintf(`
88 select
89 coalesce(f.did, g.did) as did,
90 coalesce(f.followers, 0) as followers,
91 coalesce(g.following, 0) as following
92 from (
93 select subject_did as did, count(*) as followers
94 from follows
95 where subject_did in (%s)
96 group by subject_did
97 ) f
98 full outer join (
99 select user_did as did, count(*) as following
100 from follows
101 where user_did in (%s)
102 group by user_did
103 ) g on f.did = g.did`,
104 placeholderStr, placeholderStr)
105
106 result := make(map[string]models.FollowStats)
107
108 rows, err := e.Query(query, args...)
109 if err != nil {
110 return nil, err
111 }
112 defer rows.Close()
113
114 for rows.Next() {
115 var did string
116 var followers, following int64
117 if err := rows.Scan(&did, &followers, &following); err != nil {
118 return nil, err
119 }
120 result[did] = models.FollowStats{
121 Followers: followers,
122 Following: following,
123 }
124 }
125
126 for _, did := range dids {
127 if _, exists := result[did]; !exists {
128 result[did] = models.FollowStats{
129 Followers: 0,
130 Following: 0,
131 }
132 }
133 }
134
135 return result, nil
136}
137
138func GetFollows(e Execer, limit int, filters ...orm.Filter) ([]models.Follow, error) {
139 var follows []models.Follow
140
141 var conditions []string
142 var args []any
143 for _, filter := range filters {
144 conditions = append(conditions, filter.Condition())
145 args = append(args, filter.Arg()...)
146 }
147
148 whereClause := ""
149 if conditions != nil {
150 whereClause = " where " + strings.Join(conditions, " and ")
151 }
152 limitClause := ""
153 if limit > 0 {
154 limitClause = " limit ?"
155 args = append(args, limit)
156 }
157
158 query := fmt.Sprintf(
159 `select user_did, subject_did, followed_at, rkey
160 from follows
161 %s
162 order by followed_at desc
163 %s
164 `, whereClause, limitClause)
165
166 rows, err := e.Query(query, args...)
167 if err != nil {
168 return nil, err
169 }
170 for rows.Next() {
171 var follow models.Follow
172 var followedAt string
173 err := rows.Scan(
174 &follow.UserDid,
175 &follow.SubjectDid,
176 &followedAt,
177 &follow.Rkey,
178 )
179 if err != nil {
180 return nil, err
181 }
182 followedAtTime, err := time.Parse(time.RFC3339, followedAt)
183 if err != nil {
184 log.Println("unable to determine followed at time")
185 follow.FollowedAt = time.Now()
186 } else {
187 follow.FollowedAt = followedAtTime
188 }
189 follows = append(follows, follow)
190 }
191 return follows, nil
192}
193
194func GetFollowers(e Execer, did string) ([]models.Follow, error) {
195 return GetFollows(e, 0, orm.FilterEq("subject_did", did))
196}
197
198func GetFollowing(e Execer, did string) ([]models.Follow, error) {
199 return GetFollows(e, 0, orm.FilterEq("user_did", did))
200}
201
202func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]models.FollowStatus, error) {
203 if len(subjectDids) == 0 || userDid == "" {
204 return make(map[string]models.FollowStatus), nil
205 }
206
207 result := make(map[string]models.FollowStatus)
208
209 for _, subjectDid := range subjectDids {
210 if userDid == subjectDid {
211 result[subjectDid] = models.IsSelf
212 } else {
213 result[subjectDid] = models.IsNotFollowing
214 }
215 }
216
217 var querySubjects []string
218 for _, subjectDid := range subjectDids {
219 if userDid != subjectDid {
220 querySubjects = append(querySubjects, subjectDid)
221 }
222 }
223
224 if len(querySubjects) == 0 {
225 return result, nil
226 }
227
228 placeholders := make([]string, len(querySubjects))
229 args := make([]any, len(querySubjects)+1)
230 args[0] = userDid
231
232 for i, subjectDid := range querySubjects {
233 placeholders[i] = "?"
234 args[i+1] = subjectDid
235 }
236
237 query := fmt.Sprintf(`
238 SELECT subject_did
239 FROM follows
240 WHERE user_did = ? AND subject_did IN (%s)
241 `, strings.Join(placeholders, ","))
242
243 rows, err := e.Query(query, args...)
244 if err != nil {
245 return nil, err
246 }
247 defer rows.Close()
248
249 for rows.Next() {
250 var subjectDid string
251 if err := rows.Scan(&subjectDid); err != nil {
252 return nil, err
253 }
254 result[subjectDid] = models.IsFollowing
255 }
256
257 return result, nil
258}
259
260func GetFollowStatus(e Execer, userDid, subjectDid string) models.FollowStatus {
261 statuses, err := getFollowStatuses(e, userDid, []string{subjectDid})
262 if err != nil {
263 return models.IsNotFollowing
264 }
265 return statuses[subjectDid]
266}
267
268func GetFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]models.FollowStatus, error) {
269 return getFollowStatuses(e, userDid, subjectDids)
270}