Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

1package db 2 3import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 "tangled.org/core/appview/models" 10 "tangled.org/core/orm" 11) 12 13func AddReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind models.ReactionKind, rkey string, created time.Time) error { 14 query := `insert or ignore into reactions (reacted_by_did, thread_at, kind, rkey, created) values (?, ?, ?, ?, ?)` 15 _, err := e.Exec(query, reactedByDid, threadAt, kind, rkey, created.UTC().Format(time.RFC3339)) 16 return err 17} 18 19// Get a reaction record 20func GetReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind models.ReactionKind) (*models.Reaction, error) { 21 query := ` 22 select reacted_by_did, thread_at, created, rkey 23 from reactions 24 where reacted_by_did = ? and thread_at = ? and kind = ?` 25 row := e.QueryRow(query, reactedByDid, threadAt, kind) 26 27 var reaction models.Reaction 28 var created string 29 err := row.Scan(&reaction.ReactedByDid, &reaction.ThreadAt, &created, &reaction.Rkey) 30 if err != nil { 31 return nil, err 32 } 33 34 createdAtTime, err := time.Parse(time.RFC3339, created) 35 if err != nil { 36 log.Println("unable to determine followed at time") 37 reaction.Created = time.Now() 38 } else { 39 reaction.Created = createdAtTime 40 } 41 42 return &reaction, nil 43} 44 45// Remove a reaction 46func DeleteReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind models.ReactionKind) error { 47 _, err := e.Exec(`delete from reactions where reacted_by_did = ? and thread_at = ? and kind = ?`, reactedByDid, threadAt, kind) 48 return err 49} 50 51// Remove a reaction 52func DeleteReactionByRkey(e Execer, reactedByDid string, rkey string) error { 53 _, err := e.Exec(`delete from reactions where reacted_by_did = ? and rkey = ?`, reactedByDid, rkey) 54 return err 55} 56 57func GetReactionCount(e Execer, threadAt syntax.ATURI) (int, error) { 58 count := 0 59 err := e.QueryRow(`select count(reacted_by_did) from reactions where thread_at = ?`, threadAt).Scan(&count) 60 if err != nil { 61 return 0, err 62 } 63 return count, nil 64} 65 66func GetReactionCountByKind(e Execer, threadAt syntax.ATURI, kind models.ReactionKind) (int, error) { 67 count := 0 68 err := e.QueryRow( 69 `select count(reacted_by_did) from reactions where thread_at = ? and kind = ?`, threadAt, kind).Scan(&count) 70 if err != nil { 71 return 0, err 72 } 73 return count, nil 74} 75 76// GetReactionDisplayDataMap returns map of [models.ReactionKind]->[models.ReactionDisplayData] 77func GetReactionMap(e Execer, userLimit int, threadAt syntax.ATURI) (map[models.ReactionKind]models.ReactionDisplayData, error) { 78 reactionMaps, err := ListReactionDisplayDataMap(e, []syntax.ATURI{threadAt}, userLimit) 79 return reactionMaps[threadAt], err 80} 81 82// ListReactionDisplayDataMap returns map of [syntax.ATURI]->[models.ReactionKind]->[models.ReactionDisplayData] 83func ListReactionDisplayDataMap(e Execer, threads []syntax.ATURI, userLimit int) (map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData, error) { 84 if len(threads) == 0 { 85 return nil, nil 86 } 87 88 filter := orm.FilterIn("thread_at", threads) 89 args := filter.Arg() 90 args = append(args, userLimit) 91 rows, err := e.Query( 92 fmt.Sprintf( 93 `with ranked_reactions as ( 94 select 95 thread_at, 96 kind, 97 reacted_by_did, 98 row_number() over (partition by thread_at, kind order by created asc) as rn, 99 count(*) over (partition by thread_at, kind) as total 100 from reactions 101 where %s 102 ) 103 select thread_at, kind, reacted_by_did, total 104 from ranked_reactions 105 where rn <= ? 106 order by thread_at, kind, rn asc`, 107 filter.Condition(), 108 ), 109 args..., 110 ) 111 if err != nil { 112 return nil, fmt.Errorf("querying: %w", err) 113 } 114 defer rows.Close() 115 116 // aturi -> kind -> {count,users} 117 result := make(map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData) 118 119 for rows.Next() { 120 var aturi syntax.ATURI 121 var kind models.ReactionKind 122 var did syntax.DID 123 var count int 124 125 if err := rows.Scan(&aturi, &kind, &did, &count); err != nil { 126 return nil, fmt.Errorf("scanning row: %w", err) 127 } 128 129 if _, ok := result[aturi]; !ok { 130 result[aturi] = make(map[models.ReactionKind]models.ReactionDisplayData) 131 } 132 data := result[aturi][kind] 133 data.Count = count 134 data.Users = append(data.Users, did.String()) 135 result[aturi][kind] = data 136 } 137 138 if err := rows.Err(); err != nil { 139 return nil, fmt.Errorf("iterate rows: %w", err) 140 } 141 142 return result, nil 143} 144 145// GetReactionStatusMap returns map of [models.ReactionKind]->[bool] 146func GetReactionStatusMap(e Execer, userDid syntax.DID, threadAt syntax.ATURI) (map[models.ReactionKind]bool, error) { 147 reactionMaps, err := ListReactionStatusMap(e, []syntax.ATURI{threadAt}, userDid) 148 return reactionMaps[threadAt], err 149} 150 151// ListReactionStatusMap returns map of [syntax.ATURI]->[models.ReactionKind]->[bool] 152func ListReactionStatusMap(e Execer, threads []syntax.ATURI, userDid syntax.DID) (map[syntax.ATURI]map[models.ReactionKind]bool, error) { 153 if len(threads) == 0 { 154 return nil, nil 155 } 156 157 filter := orm.FilterIn("thread_at", threads) 158 args := []any{userDid} 159 args = append(args, filter.Arg()...) 160 rows, err := e.Query( 161 fmt.Sprintf( 162 `select thread_at, kind from reactions 163 where reacted_by_did = ? and %s`, 164 filter.Condition(), 165 ), 166 args..., 167 ) 168 if err != nil { 169 return nil, err 170 } 171 defer rows.Close() 172 173 // aturi -> kind -> bool 174 result := make(map[syntax.ATURI]map[models.ReactionKind]bool) 175 176 for rows.Next() { 177 var aturi syntax.ATURI 178 var kind models.ReactionKind 179 180 if err := rows.Scan(&aturi, &kind); err != nil { 181 return nil, fmt.Errorf("scanning row: %w", err) 182 } 183 184 if _, ok := result[aturi]; !ok { 185 result[aturi] = make(map[models.ReactionKind]bool) 186 } 187 188 result[aturi][kind] = true 189 } 190 191 return result, nil 192}