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