diff --git a/appview/db/reaction.go b/appview/db/reaction.go
--- a/appview/db/reaction.go
+++ b/appview/db/reaction.go
@@ -1,11 +1,13 @@
package db
import (
+ "fmt"
"log"
"time"
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/appview/models"
+ "tangled.org/core/orm"
)
func AddReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind models.ReactionKind, rkey string) error {
@@ -71,58 +73,120 @@ }
return count, nil
}
+// GetReactionDisplayDataMap returns map of [models.ReactionKind]->[models.ReactionDisplayData]
func GetReactionMap(e Execer, userLimit int, threadAt syntax.ATURI) (map[models.ReactionKind]models.ReactionDisplayData, error) {
- query := `
- select kind, reacted_by_did,
- row_number() over (partition by kind order by created asc) as rn,
- count(*) over (partition by kind) as total
- from reactions
- where thread_at = ?
- order by kind, created asc`
+ reactionMaps, err := ListReactionDisplayDataMap(e, []syntax.ATURI{threadAt}, userLimit)
+ return reactionMaps[threadAt], err
+}
+
+// ListReactionDisplayDataMap returns map of [syntax.ATURI]->[models.ReactionKind]->[models.ReactionDisplayData]
+func ListReactionDisplayDataMap(e Execer, threads []syntax.ATURI, userLimit int) (map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData, error) {
+ if len(threads) == 0 {
+ return nil, nil
+ }
- rows, err := e.Query(query, threadAt)
+ filter := orm.FilterIn("thread_at", threads)
+ args := filter.Arg()
+ args = append(args, userLimit)
+ rows, err := e.Query(
+ fmt.Sprintf(
+ `with ranked_reactions as (
+ select
+ thread_at,
+ kind,
+ reacted_by_did,
+ row_number() over (partition by thread_at, kind order by created asc) as rn,
+ count(*) over (partition by thread_at, kind) as total
+ from reactions
+ where %s
+ )
+ select thread_at, kind, reacted_by_did, total
+ from ranked_reactions
+ where rn <= ?
+ order by thread_at, kind, rn asc`,
+ filter.Condition(),
+ ),
+ args...,
+ )
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("querying: %w", err)
}
defer rows.Close()
- reactionMap := map[models.ReactionKind]models.ReactionDisplayData{}
- for _, kind := range models.OrderedReactionKinds {
- reactionMap[kind] = models.ReactionDisplayData{Count: 0, Users: []string{}}
- }
+ // aturi -> kind -> {count,users}
+ result := make(map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData)
for rows.Next() {
+ var aturi syntax.ATURI
var kind models.ReactionKind
- var did string
- var rn, total int
- if err := rows.Scan(&kind, &did, &rn, &total); err != nil {
- return nil, err
+ var did syntax.DID
+ var count int
+
+ if err := rows.Scan(&aturi, &kind, &did, &count); err != nil {
+ return nil, fmt.Errorf("scanning row: %w", err)
}
- data := reactionMap[kind]
- data.Count = total
- if userLimit > 0 && rn <= userLimit {
- data.Users = append(data.Users, did)
+ if _, ok := result[aturi]; !ok {
+ result[aturi] = make(map[models.ReactionKind]models.ReactionDisplayData)
}
- reactionMap[kind] = data
+ data := result[aturi][kind]
+ data.Count = count
+ data.Users = append(data.Users, did.String())
+ result[aturi][kind] = data
+ }
+
+ if err := rows.Err(); err != nil {
+ return nil, fmt.Errorf("iterate rows: %w", err)
}
- return reactionMap, rows.Err()
+ return result, nil
+}
+
+// GetReactionStatusMap returns map of [models.ReactionKind]->[bool]
+func GetReactionStatusMap(e Execer, userDid syntax.DID, threadAt syntax.ATURI) (map[models.ReactionKind]bool, error) {
+ reactionMaps, err := ListReactionStatusMap(e, []syntax.ATURI{threadAt}, userDid)
+ return reactionMaps[threadAt], err
}
-func GetReactionStatus(e Execer, userDid string, threadAt syntax.ATURI, kind models.ReactionKind) bool {
- if _, err := GetReaction(e, userDid, threadAt, kind); err != nil {
- return false
- } else {
- return true
+// ListReactionStatusMap returns map of [syntax.ATURI]->[models.ReactionKind]->[bool]
+func ListReactionStatusMap(e Execer, threads []syntax.ATURI, userDid syntax.DID) (map[syntax.ATURI]map[models.ReactionKind]bool, error) {
+ if len(threads) == 0 {
+ return nil, nil
+ }
+
+ filter := orm.FilterIn("thread_at", threads)
+ args := []any{userDid}
+ args = append(args, filter.Arg()...)
+ rows, err := e.Query(
+ fmt.Sprintf(
+ `select thread_at, kind from reactions
+ where reacted_by_did = ? and %s`,
+ filter.Condition(),
+ ),
+ args...,
+ )
+ if err != nil {
+ return nil, err
}
-}
+ defer rows.Close()
+
+ // aturi -> kind -> bool
+ result := make(map[syntax.ATURI]map[models.ReactionKind]bool)
+
+ for rows.Next() {
+ var aturi syntax.ATURI
+ var kind models.ReactionKind
+
+ if err := rows.Scan(&aturi, &kind); err != nil {
+ return nil, fmt.Errorf("scanning row: %w", err)
+ }
-func GetReactionStatusMap(e Execer, userDid string, threadAt syntax.ATURI) map[models.ReactionKind]bool {
- statusMap := map[models.ReactionKind]bool{}
- for _, kind := range models.OrderedReactionKinds {
- count := GetReactionStatus(e, userDid, threadAt, kind)
- statusMap[kind] = count
+ if _, ok := result[aturi]; !ok {
+ result[aturi] = make(map[models.ReactionKind]bool)
+ }
+
+ result[aturi][kind] = true
}
- return statusMap
+
+ return result, nil
}
diff --git a/appview/issues/issues.go b/appview/issues/issues.go
--- a/appview/issues/issues.go
+++ b/appview/issues/issues.go
@@ -98,14 +98,18 @@ rp.pages.Error404(w)
return
}
- reactionMap, err := db.GetReactionMap(rp.db, 20, issue.AtUri())
+ entities := []syntax.ATURI{issue.AtUri()}
+ reactions, err := db.ListReactionDisplayDataMap(rp.db, entities, 20)
if err != nil {
- l.Error("failed to get issue reactions", "err", err)
+ l.Error("failed to get reactions", "err", err)
}
- userReactions := map[models.ReactionKind]bool{}
+ var userReactions map[syntax.ATURI]map[models.ReactionKind]bool
if user != nil {
- userReactions = db.GetReactionStatusMap(rp.db, user.Active.Did, issue.AtUri())
+ userReactions, err = db.ListReactionStatusMap(rp.db, entities, syntax.DID(user.Active.Did))
+ if err != nil {
+ l.Error("failed to get user reactions", "err", err)
+ }
}
backlinks, err := db.GetBacklinks(rp.db, issue.AtUri())
@@ -137,7 +141,7 @@ RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
Issue: issue,
CommentList: models.NewCommentList(issue.Comments),
Backlinks: backlinks,
- Reactions: reactionMap,
+ Reactions: reactions,
UserReacted: userReactions,
LabelDefs: defs,
})
diff --git a/appview/pages/pages.go b/appview/pages/pages.go
--- a/appview/pages/pages.go
+++ b/appview/pages/pages.go
@@ -1127,8 +1127,8 @@ CommentList []models.CommentListItem
Backlinks []models.RichReferenceLink
LabelDefs map[string]*models.LabelDefinition
- Reactions map[models.ReactionKind]models.ReactionDisplayData
- UserReacted map[models.ReactionKind]bool
+ Reactions map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData
+ UserReacted map[syntax.ATURI]map[models.ReactionKind]bool
}
func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error {
@@ -1287,8 +1287,8 @@ DiffOpts types.DiffOpts
ActiveRound int
IsInterdiff bool
- Reactions map[models.ReactionKind]models.ReactionDisplayData
- UserReacted map[models.ReactionKind]bool
+ Reactions map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData
+ UserReacted map[syntax.ATURI]map[models.ReactionKind]bool
LabelDefs map[string]*models.LabelDefinition
}
diff --git a/appview/pages/templates/repo/issues/issue.html b/appview/pages/templates/repo/issues/issue.html
--- a/appview/pages/templates/repo/issues/issue.html
+++ b/appview/pages/templates/repo/issues/issue.html
@@ -36,10 +36,11 @@ {{ if .Issue.Body }}