+9
-4
cmd/server/main.go
······
+219
-17
docs/COMMENT_SYSTEM_IMPLEMENTATION.md
···This document details the complete implementation of the comment system for Coves, a forum-like atProto social media platform. The comment system follows the established vote system pattern, with comments living in user repositories and being indexed by the AppView via Jetstream firehose.+**Last Updated:** November 6, 2025 (Final PR review fixes complete - lexicon compliance, data integrity, SQL correctness)···+1. `internal/db/postgres/comment_repo.go` - Added query methods (~450 lines), fixed INNER→LEFT JOIN, fixed window function SQL···+After initial implementation, a thorough PR review identified several critical issues that were addressed before production deployment:+- **Files:** `internal/core/comments/interfaces.go`, `internal/db/postgres/comment_repo.go`, `internal/core/comments/comment_service.go`+- **Problem:** When fetching comments for non-existent post, service returned wrapped `posts.ErrNotFound` which handler didn't recognize+- **Problem:** Comment queries with deep nesting expensive but only protected by global 100 req/min limit+- **Problem:** No validation before base64 decoding - attacker could send massive cursor string+- **Problem:** Both `postView.record` and `commentView.record` fields were null despite lexicon marking them as required+- `postView.author.handle` contained DID instead of proper handle (violates `format:"handle"`)+- **Impact:** Lexicon format constraints violated, poor UX showing DIDs instead of readable names+- **Files:** `internal/core/comments/comment_service.go:34-37`, `:292-325`, `cmd/server/main.go:297`+- **Problem:** Three query methods used `INNER JOIN users` which dropped comments when user not indexed yet+- **Impact:** New user's first comments would disappear until user consumer caught up (violates out-of-order design)+- **Files:** `internal/db/postgres/comment_repo.go:396`, `:407`, `:415`, `:694-706`, `:761-836`+- **Problem:** `ListByParentsBatch` used `ORDER BY hot_rank DESC` in window function, but PostgreSQL doesn't allow SELECT aliases in window ORDER BY+- **Impact:** SQL error "column hot_rank does not exist" caused silent failure, dropping ALL nested replies in hot sort mode+- **Critical Note:** This affected default sorting mode (hot) and would have broken production UX······+**Problem Solved:** The initial implementation had a classic N+1 query problem where nested reply loading made separate database queries for each comment's children. For a post with 50 top-level comments and 3 levels of depth, this could result in ~1,551 queries.···
+218
-44
internal/core/comments/comment_service.go
·········func (s *commentService) GetComments(ctx context.Context, req *GetCommentsRequest) (*GetCommentsResponse, error) {···············+func (s *commentService) buildPostView(ctx context.Context, post *posts.Post, viewerDID *string) *posts.PostView {
+11
internal/core/comments/interfaces.go
···GetVoteStateForComments(ctx context.Context, viewerDID string, commentURIs []string) (map[string]interface{}, error)
+148
-5
internal/db/postgres/comment_repo.go
···log(greatest(2, c.score + 2)) / power(((EXTRACT(EPOCH FROM (NOW() - c.created_at)) / 3600) + 2), 1.8) as hot_rank,···+// LEFT JOIN prevents data loss when user record hasn't been indexed yet (out-of-order Jetstream events)······+// LEFT JOIN prevents data loss when user record hasn't been indexed yet (out-of-order Jetstream events)······+log(greatest(2, c.score + 2)) / power(((EXTRACT(EPOCH FROM (NOW() - c.created_at)) / 3600) + 2), 1.8) as hot_rank,+// CRITICAL: Must inline hot_rank formula - PostgreSQL doesn't allow SELECT aliases in window ORDER BY+windowOrderBy = `log(greatest(2, c.score + 2)) / power(((EXTRACT(EPOCH FROM (NOW() - c.created_at)) / 3600) + 2), 1.8) DESC, c.score DESC, c.created_at DESC`+log(greatest(2, c.score + 2)) / power(((EXTRACT(EPOCH FROM (NOW() - c.created_at)) / 3600) + 2), 1.8) as hot_rank,+// CRITICAL: Must inline hot_rank formula - PostgreSQL doesn't allow SELECT aliases in window ORDER BY+windowOrderBy = `log(greatest(2, c.score + 2)) / power(((EXTRACT(EPOCH FROM (NOW() - c.created_at)) / 3600) + 2), 1.8) DESC, c.score DESC, c.created_at DESC`+// LEFT JOIN prevents data loss when user record hasn't been indexed yet (out-of-order Jetstream events)
+8
-2
tests/integration/comment_query_test.go
······