this repo has no description
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 defer rows.Close()
171
172 for rows.Next() {
173 var follow models.Follow
174 var followedAt string
175 err := rows.Scan(
176 &follow.UserDid,
177 &follow.SubjectDid,
178 &followedAt,
179 &follow.Rkey,
180 )
181 if err != nil {
182 return nil, err
183 }
184 followedAtTime, err := time.Parse(time.RFC3339, followedAt)
185 if err != nil {
186 log.Println("unable to determine followed at time")
187 follow.FollowedAt = time.Now()
188 } else {
189 follow.FollowedAt = followedAtTime
190 }
191 follows = append(follows, follow)
192 }
193 return follows, nil
194}
195
196func GetFollowers(e Execer, did string) ([]models.Follow, error) {
197 return GetFollows(e, 0, orm.FilterEq("subject_did", did))
198}
199
200func GetFollowing(e Execer, did string) ([]models.Follow, error) {
201 return GetFollows(e, 0, orm.FilterEq("user_did", did))
202}
203
204func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]models.FollowStatus, error) {
205 if len(subjectDids) == 0 || userDid == "" {
206 return make(map[string]models.FollowStatus), nil
207 }
208
209 result := make(map[string]models.FollowStatus)
210
211 for _, subjectDid := range subjectDids {
212 if userDid == subjectDid {
213 result[subjectDid] = models.IsSelf
214 } else {
215 result[subjectDid] = models.IsNotFollowing
216 }
217 }
218
219 var querySubjects []string
220 for _, subjectDid := range subjectDids {
221 if userDid != subjectDid {
222 querySubjects = append(querySubjects, subjectDid)
223 }
224 }
225
226 if len(querySubjects) == 0 {
227 return result, nil
228 }
229
230 placeholders := make([]string, len(querySubjects))
231 args := make([]any, len(querySubjects)+1)
232 args[0] = userDid
233
234 for i, subjectDid := range querySubjects {
235 placeholders[i] = "?"
236 args[i+1] = subjectDid
237 }
238
239 query := fmt.Sprintf(`
240 SELECT subject_did
241 FROM follows
242 WHERE user_did = ? AND subject_did IN (%s)
243 `, strings.Join(placeholders, ","))
244
245 rows, err := e.Query(query, args...)
246 if err != nil {
247 return nil, err
248 }
249 defer rows.Close()
250
251 for rows.Next() {
252 var subjectDid string
253 if err := rows.Scan(&subjectDid); err != nil {
254 return nil, err
255 }
256 result[subjectDid] = models.IsFollowing
257 }
258
259 return result, nil
260}
261
262func GetFollowStatus(e Execer, userDid, subjectDid string) models.FollowStatus {
263 statuses, err := getFollowStatuses(e, userDid, []string{subjectDid})
264 if err != nil {
265 return models.IsNotFollowing
266 }
267 return statuses[subjectDid]
268}
269
270func GetFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]models.FollowStatus, error) {
271 return getFollowStatuses(e, userDid, subjectDids)
272}