forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
at master 6.5 kB view raw
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}