feat: add comments count
This commit is contained in:
@@ -704,7 +704,7 @@ func (h *ProfileHandler) hydrateFeed(r *http.Request, posts []model.Post) error
|
|||||||
if err := h.mentions.HydrateCounts(r.Context(), ptrs); err != nil {
|
if err := h.mentions.HydrateCounts(r.Context(), ptrs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return h.comments.HydratePinned(r.Context(), viewerID, ptrs)
|
return h.comments.Hydrate(r.Context(), viewerID, ptrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// followListResp is the payload for the followers / following list endpoints:
|
// followListResp is the payload for the followers / following list endpoints:
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ type Post struct {
|
|||||||
// comment layer so the card footer can show it without a per-card fetch.
|
// comment layer so the card footer can show it without a per-card fetch.
|
||||||
// Nil when nothing is pinned.
|
// Nil when nothing is pinned.
|
||||||
PinnedComment *Comment `json:"pinned_comment,omitempty"`
|
PinnedComment *Comment `json:"pinned_comment,omitempty"`
|
||||||
|
|
||||||
|
// CommentsCount is the total number of comments + replies on the post
|
||||||
|
// (drives the footer's comment count). Hydrated by the comment layer.
|
||||||
|
CommentsCount int `json:"comments_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment is one comment (or reply) on a post. Threading is one level deep:
|
// Comment is one comment (or reply) on a post. Threading is one level deep:
|
||||||
|
|||||||
@@ -178,9 +178,10 @@ func (r *CommentRepo) ListReplies(ctx context.Context, parentID, viewerID string
|
|||||||
return out, total, err
|
return out, total, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// HydratePinned attaches each post's pinned comment (if any) to
|
// Hydrate fills each post's comment summary — its pinned comment (if any) and
|
||||||
// Post.PinnedComment in a single query for the page.
|
// total comment count (top-level + replies) — in two batched queries for the
|
||||||
func (r *CommentRepo) HydratePinned(ctx context.Context, viewerID string, posts []*model.Post) error {
|
// page. Drives the card footer's pinned line and comment count.
|
||||||
|
func (r *CommentRepo) Hydrate(ctx context.Context, viewerID string, posts []*model.Post) error {
|
||||||
ids := make([]string, 0, len(posts))
|
ids := make([]string, 0, len(posts))
|
||||||
for _, p := range posts {
|
for _, p := range posts {
|
||||||
if p != nil {
|
if p != nil {
|
||||||
@@ -190,27 +191,52 @@ func (r *CommentRepo) HydratePinned(ctx context.Context, viewerID string, posts
|
|||||||
if len(ids) == 0 {
|
if len(ids) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pinned comment per post.
|
||||||
q := `SELECT ` + commentSelect + commentFrom + `
|
q := `SELECT ` + commentSelect + commentFrom + `
|
||||||
WHERE c.post_id = ANY($1) AND c.pinned_at IS NOT NULL`
|
WHERE c.post_id = ANY($1) AND c.pinned_at IS NOT NULL`
|
||||||
rows, err := r.pg.Pool.Query(ctx, q, ids, nullUUIDArg(viewerID))
|
rows, err := r.pg.Pool.Query(ctx, q, ids, nullUUIDArg(viewerID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("hydrate pinned: %w", err)
|
return fmt.Errorf("hydrate pinned: %w", err)
|
||||||
}
|
}
|
||||||
list, err := scanComments(rows)
|
list, err := scanComments(rows) // closes rows
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
byPost := make(map[string]*model.Comment, len(list))
|
pinnedByPost := make(map[string]*model.Comment, len(list))
|
||||||
for i := range list {
|
for i := range list {
|
||||||
byPost[list[i].PostID] = &list[i]
|
pinnedByPost[list[i].PostID] = &list[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Total comment count per post (top-level + replies).
|
||||||
|
counts := make(map[string]int, len(ids))
|
||||||
|
crows, err := r.pg.Pool.Query(ctx,
|
||||||
|
`SELECT post_id, count(*) FROM post_comments WHERE post_id = ANY($1) GROUP BY post_id`,
|
||||||
|
ids)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("hydrate comment counts: %w", err)
|
||||||
|
}
|
||||||
|
defer crows.Close()
|
||||||
|
for crows.Next() {
|
||||||
|
var id string
|
||||||
|
var n int
|
||||||
|
if err := crows.Scan(&id, &n); err != nil {
|
||||||
|
return fmt.Errorf("scan comment count: %w", err)
|
||||||
|
}
|
||||||
|
counts[id] = n
|
||||||
|
}
|
||||||
|
if err := crows.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range posts {
|
for _, p := range posts {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c, ok := byPost[p.ID]; ok {
|
if c, ok := pinnedByPost[p.ID]; ok {
|
||||||
p.PinnedComment = c
|
p.PinnedComment = c
|
||||||
}
|
}
|
||||||
|
p.CommentsCount = counts[p.ID]
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func (s *PostService) Get(ctx context.Context, id, viewerID string) (*model.Post
|
|||||||
if err := s.mentions.HydrateCounts(ctx, []*model.Post{p}); err != nil {
|
if err := s.mentions.HydrateCounts(ctx, []*model.Post{p}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := s.comments.HydratePinned(ctx, viewerID, []*model.Post{p}); err != nil {
|
if err := s.comments.Hydrate(ctx, viewerID, []*model.Post{p}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
@@ -221,7 +221,7 @@ func (s *PostService) hydratePinnedSlice(ctx context.Context, viewerID string, p
|
|||||||
for i := range posts {
|
for i := range posts {
|
||||||
ptrs[i] = &posts[i]
|
ptrs[i] = &posts[i]
|
||||||
}
|
}
|
||||||
return s.comments.HydratePinned(ctx, viewerID, ptrs)
|
return s.comments.Hydrate(ctx, viewerID, ptrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hydrateLikesSlice fills LikesCount + ViewerHasLiked on a value slice.
|
// hydrateLikesSlice fills LikesCount + ViewerHasLiked on a value slice.
|
||||||
|
|||||||
Reference in New Issue
Block a user