From 05567ad43874f16aac7875e4c6fe3fbeccabbdf9 Mon Sep 17 00:00:00 2001 From: Krispire Admin Date: Sat, 13 Jun 2026 02:24:21 +0530 Subject: [PATCH] feat: add comments count --- internal/handler/social.go | 2 +- internal/model/social.go | 4 +++ internal/repository/comment_repo.go | 40 ++++++++++++++++++++++++----- internal/service/social_service.go | 4 +-- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/internal/handler/social.go b/internal/handler/social.go index 92c26f0..0e7e6b6 100644 --- a/internal/handler/social.go +++ b/internal/handler/social.go @@ -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 { 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: diff --git a/internal/model/social.go b/internal/model/social.go index 464e25b..92ab12d 100644 --- a/internal/model/social.go +++ b/internal/model/social.go @@ -55,6 +55,10 @@ type Post struct { // comment layer so the card footer can show it without a per-card fetch. // Nil when nothing is pinned. 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: diff --git a/internal/repository/comment_repo.go b/internal/repository/comment_repo.go index fd60a4b..f890b99 100644 --- a/internal/repository/comment_repo.go +++ b/internal/repository/comment_repo.go @@ -178,9 +178,10 @@ func (r *CommentRepo) ListReplies(ctx context.Context, parentID, viewerID string return out, total, err } -// HydratePinned attaches each post's pinned comment (if any) to -// Post.PinnedComment in a single query for the page. -func (r *CommentRepo) HydratePinned(ctx context.Context, viewerID string, posts []*model.Post) error { +// Hydrate fills each post's comment summary — its pinned comment (if any) and +// total comment count (top-level + replies) — in two batched queries for the +// 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)) for _, p := range posts { if p != nil { @@ -190,27 +191,52 @@ func (r *CommentRepo) HydratePinned(ctx context.Context, viewerID string, posts if len(ids) == 0 { return nil } + + // Pinned comment per post. q := `SELECT ` + commentSelect + commentFrom + ` WHERE c.post_id = ANY($1) AND c.pinned_at IS NOT NULL` rows, err := r.pg.Pool.Query(ctx, q, ids, nullUUIDArg(viewerID)) if err != nil { return fmt.Errorf("hydrate pinned: %w", err) } - list, err := scanComments(rows) + list, err := scanComments(rows) // closes rows if err != nil { return err } - byPost := make(map[string]*model.Comment, len(list)) + pinnedByPost := make(map[string]*model.Comment, len(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 { if p == nil { continue } - if c, ok := byPost[p.ID]; ok { + if c, ok := pinnedByPost[p.ID]; ok { p.PinnedComment = c } + p.CommentsCount = counts[p.ID] } return nil } diff --git a/internal/service/social_service.go b/internal/service/social_service.go index 1485654..7cc8db3 100644 --- a/internal/service/social_service.go +++ b/internal/service/social_service.go @@ -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 { 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 p, nil @@ -221,7 +221,7 @@ func (s *PostService) hydratePinnedSlice(ctx context.Context, viewerID string, p for i := range posts { 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.