Monorepo for Tangled tangled.org
763
fork

Configure Feed

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

appview: fetch `aturi->reactionMap` from DB

There can be multiple reactable entities in same page.
Fetch every reactions in `aturi->reactionMap` format where reactionMap
is `kind->T` map

Signed-off-by: Seongmin Lee <git@boltless.me>

+128 -54
+99 -35
appview/db/reaction.go
··· 1 1 package db 2 2 3 3 import ( 4 + "fmt" 4 5 "log" 5 6 "time" 6 7 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 8 9 "tangled.org/core/appview/models" 10 + "tangled.org/core/orm" 9 11 ) 10 12 11 13 func AddReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind models.ReactionKind, rkey string) error { ··· 71 73 return count, nil 72 74 } 73 75 76 + // GetReactionDisplayDataMap returns map of [models.ReactionKind]->[models.ReactionDisplayData] 74 77 func GetReactionMap(e Execer, userLimit int, threadAt syntax.ATURI) (map[models.ReactionKind]models.ReactionDisplayData, error) { 75 - query := ` 76 - select kind, reacted_by_did, 77 - row_number() over (partition by kind order by created asc) as rn, 78 - count(*) over (partition by kind) as total 79 - from reactions 80 - where thread_at = ? 81 - order by kind, created asc` 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] 83 + func 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 + } 82 87 83 - rows, err := e.Query(query, threadAt) 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 + ) 84 111 if err != nil { 85 - return nil, err 112 + return nil, fmt.Errorf("querying: %w", err) 86 113 } 87 114 defer rows.Close() 88 115 89 - reactionMap := map[models.ReactionKind]models.ReactionDisplayData{} 90 - for _, kind := range models.OrderedReactionKinds { 91 - reactionMap[kind] = models.ReactionDisplayData{Count: 0, Users: []string{}} 92 - } 116 + // aturi -> kind -> {count,users} 117 + result := make(map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData) 93 118 94 119 for rows.Next() { 120 + var aturi syntax.ATURI 95 121 var kind models.ReactionKind 96 - var did string 97 - var rn, total int 98 - if err := rows.Scan(&kind, &did, &rn, &total); err != nil { 99 - return nil, err 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) 100 127 } 101 128 102 - data := reactionMap[kind] 103 - data.Count = total 104 - if userLimit > 0 && rn <= userLimit { 105 - data.Users = append(data.Users, did) 129 + if _, ok := result[aturi]; !ok { 130 + result[aturi] = make(map[models.ReactionKind]models.ReactionDisplayData) 106 131 } 107 - reactionMap[kind] = data 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) 108 140 } 109 141 110 - return reactionMap, rows.Err() 142 + return result, nil 143 + } 144 + 145 + // GetReactionStatusMap returns map of [models.ReactionKind]->[bool] 146 + func 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 111 149 } 112 150 113 - func GetReactionStatus(e Execer, userDid string, threadAt syntax.ATURI, kind models.ReactionKind) bool { 114 - if _, err := GetReaction(e, userDid, threadAt, kind); err != nil { 115 - return false 116 - } else { 117 - return true 151 + // ListReactionStatusMap returns map of [syntax.ATURI]->[models.ReactionKind]->[bool] 152 + func 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 118 170 } 119 - } 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 + } 120 183 121 - func GetReactionStatusMap(e Execer, userDid string, threadAt syntax.ATURI) map[models.ReactionKind]bool { 122 - statusMap := map[models.ReactionKind]bool{} 123 - for _, kind := range models.OrderedReactionKinds { 124 - count := GetReactionStatus(e, userDid, threadAt, kind) 125 - statusMap[kind] = count 184 + if _, ok := result[aturi]; !ok { 185 + result[aturi] = make(map[models.ReactionKind]bool) 186 + } 187 + 188 + result[aturi][kind] = true 126 189 } 127 - return statusMap 190 + 191 + return result, nil 128 192 }
+9 -5
appview/issues/issues.go
··· 98 98 return 99 99 } 100 100 101 - reactionMap, err := db.GetReactionMap(rp.db, 20, issue.AtUri()) 101 + entities := []syntax.ATURI{issue.AtUri()} 102 + reactions, err := db.ListReactionDisplayDataMap(rp.db, entities, 20) 102 103 if err != nil { 103 - l.Error("failed to get issue reactions", "err", err) 104 + l.Error("failed to get reactions", "err", err) 104 105 } 105 106 106 - userReactions := map[models.ReactionKind]bool{} 107 + var userReactions map[syntax.ATURI]map[models.ReactionKind]bool 107 108 if user != nil { 108 - userReactions = db.GetReactionStatusMap(rp.db, user.Active.Did, issue.AtUri()) 109 + userReactions, err = db.ListReactionStatusMap(rp.db, entities, syntax.DID(user.Active.Did)) 110 + if err != nil { 111 + l.Error("failed to get user reactions", "err", err) 112 + } 109 113 } 110 114 111 115 backlinks, err := db.GetBacklinks(rp.db, issue.AtUri()) ··· 137 141 Issue: issue, 138 142 CommentList: models.NewCommentList(issue.Comments), 139 143 Backlinks: backlinks, 140 - Reactions: reactionMap, 144 + Reactions: reactions, 141 145 UserReacted: userReactions, 142 146 LabelDefs: defs, 143 147 })
+4 -4
appview/pages/pages.go
··· 1127 1127 Backlinks []models.RichReferenceLink 1128 1128 LabelDefs map[string]*models.LabelDefinition 1129 1129 1130 - Reactions map[models.ReactionKind]models.ReactionDisplayData 1131 - UserReacted map[models.ReactionKind]bool 1130 + Reactions map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData 1131 + UserReacted map[syntax.ATURI]map[models.ReactionKind]bool 1132 1132 } 1133 1133 1134 1134 func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error { ··· 1287 1287 ActiveRound int 1288 1288 IsInterdiff bool 1289 1289 1290 - Reactions map[models.ReactionKind]models.ReactionDisplayData 1291 - UserReacted map[models.ReactionKind]bool 1290 + Reactions map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData 1291 + UserReacted map[syntax.ATURI]map[models.ReactionKind]bool 1292 1292 1293 1293 LabelDefs map[string]*models.LabelDefinition 1294 1294 }
+4 -3
appview/pages/templates/repo/issues/issue.html
··· 36 36 <article id="body" class="mt-4 prose dark:prose-invert">{{ .Issue.Body | markdown }}</article> 37 37 {{ end }} 38 38 <div class="mt-4"> 39 + {{ $aturi := .Issue.AtUri }} 39 40 {{ template "repo/fragments/reactions" 40 - (dict "Reactions" .Reactions 41 - "UserReacted" .UserReacted 42 - "ThreadAt" .Issue.AtUri) }} 41 + (dict "Reactions" (index .Reactions $aturi) 42 + "UserReacted" (index .UserReacted $aturi) 43 + "ThreadAt" $aturi) }} 43 44 </div> 44 45 </section> 45 46 {{ end }}
+4 -3
appview/pages/templates/repo/pulls/fragments/pullHeader.html
··· 48 48 {{ end }} 49 49 50 50 <div class="mt-2"> 51 + {{ $aturi := .Pull.AtUri }} 51 52 {{ template "repo/fragments/reactions" 52 - (dict "Reactions" .Reactions 53 - "UserReacted" .UserReacted 54 - "ThreadAt" .Pull.AtUri) }} 53 + (dict "Reactions" (index .Reactions $aturi) 54 + "UserReacted" (index .UserReacted $aturi) 55 + "ThreadAt" $aturi) }} 55 56 </div> 56 57 </section> 57 58
+8 -4
appview/pulls/pulls.go
··· 240 240 m[p.Sha] = p 241 241 } 242 242 243 - reactionMap, err := db.GetReactionMap(s.db, 20, pull.AtUri()) 243 + entities := []syntax.ATURI{pull.AtUri()} 244 + reactions, err := db.ListReactionDisplayDataMap(s.db, entities, 20) 244 245 if err != nil { 245 246 l.Error("failed to get pull reactions", "err", err) 246 247 } 247 248 248 - userReactions := map[models.ReactionKind]bool{} 249 + var userReactions map[syntax.ATURI]map[models.ReactionKind]bool 249 250 if user != nil { 250 - userReactions = db.GetReactionStatusMap(s.db, user.Active.Did, pull.AtUri()) 251 + userReactions, err = db.ListReactionStatusMap(s.db, entities, syntax.DID(user.Active.Did)) 252 + if err != nil { 253 + s.logger.Error("failed to get user reactions", "err", err) 254 + } 251 255 } 252 256 253 257 labelDefs, err := db.GetLabelDefinitions( ··· 303 307 ActiveRound: roundIdInt, 304 308 IsInterdiff: interdiff, 305 309 306 - Reactions: reactionMap, 310 + Reactions: reactions, 307 311 UserReacted: userReactions, 308 312 309 313 LabelDefs: defs,