···
"Coves/internal/core/posts"
"Coves/internal/core/users"
···
postRepo posts.Repository
communityRepo communities.Repository
userService users.UserService
// NewPostEventConsumer creates a new Jetstream consumer for post events
···
postRepo posts.Repository,
communityRepo communities.Repository,
userService users.UserService,
return &PostEventConsumer{
communityRepo: communityRepo,
userService: userService,
···
-
// Index in AppView database (idempotent - safe for Jetstream replays)
-
err = c.postRepo.Create(ctx, post)
-
// Check if it already exists (idempotency)
-
if posts.IsConflict(err) {
-
log.Printf("Post already indexed: %s", uri)
-
return fmt.Errorf("failed to index post: %w", err)
log.Printf("✓ Indexed post: %s (author: %s, community: %s, rkey: %s)",
uri, post.AuthorDID, post.CommunityDID, commit.RKey)
···
"Coves/internal/core/posts"
"Coves/internal/core/users"
···
postRepo posts.Repository
communityRepo communities.Repository
userService users.UserService
+
db *sql.DB // Direct DB access for atomic count reconciliation
// NewPostEventConsumer creates a new Jetstream consumer for post events
···
postRepo posts.Repository,
communityRepo communities.Repository,
userService users.UserService,
return &PostEventConsumer{
communityRepo: communityRepo,
userService: userService,
···
+
// Atomically: Index post + Reconcile comment count for out-of-order arrivals
+
if err := c.indexPostAndReconcileCounts(ctx, post); err != nil {
+
return fmt.Errorf("failed to index post and reconcile counts: %w", err)
log.Printf("✓ Indexed post: %s (author: %s, community: %s, rkey: %s)",
uri, post.AuthorDID, post.CommunityDID, commit.RKey)
+
// indexPostAndReconcileCounts atomically indexes a post and reconciles comment counts
+
// This fixes the race condition where comments arrive before their parent post
+
func (c *PostEventConsumer) indexPostAndReconcileCounts(ctx context.Context, post *posts.Post) error {
+
tx, err := c.db.BeginTx(ctx, nil)
+
return fmt.Errorf("failed to begin transaction: %w", err)
+
if rollbackErr := tx.Rollback(); rollbackErr != nil && rollbackErr != sql.ErrTxDone {
+
log.Printf("Failed to rollback transaction: %v", rollbackErr)
+
// 1. Insert the post (idempotent with RETURNING clause)
+
var facetsJSON, embedJSON, labelsJSON sql.NullString
+
if post.ContentFacets != nil {
+
facetsJSON.String = *post.ContentFacets
+
facetsJSON.Valid = true
+
embedJSON.String = *post.Embed
+
if post.ContentLabels != nil {
+
labelsJSON.String = *post.ContentLabels
+
labelsJSON.Valid = true
+
uri, cid, rkey, author_did, community_did,
+
title, content, content_facets, embed, content_labels,
+
ON CONFLICT (uri) DO NOTHING
+
insertErr := tx.QueryRowContext(
+
post.URI, post.CID, post.RKey, post.AuthorDID, post.CommunityDID,
+
post.Title, post.Content, facetsJSON, embedJSON, labelsJSON,
+
// If no rows returned, post already exists (idempotent - OK for Jetstream replays)
+
if insertErr == sql.ErrNoRows {
+
log.Printf("Post already indexed: %s (idempotent)", post.URI)
+
if commitErr := tx.Commit(); commitErr != nil {
+
return fmt.Errorf("failed to commit transaction: %w", commitErr)
+
return fmt.Errorf("failed to insert post: %w", insertErr)
+
// 2. Reconcile comment_count for this newly inserted post
+
// In case any comments arrived out-of-order before this post was indexed
+
// This is the CRITICAL FIX for the race condition identified in the PR review
+
WHERE c.parent_uri = $1 AND c.deleted_at IS NULL
+
_, reconcileErr := tx.ExecContext(ctx, reconcileQuery, post.URI, postID)
+
if reconcileErr != nil {
+
log.Printf("Warning: Failed to reconcile comment_count for %s: %v", post.URI, reconcileErr)
+
// Continue anyway - this is a best-effort reconciliation
+
if err := tx.Commit(); err != nil {
+
return fmt.Errorf("failed to commit transaction: %w", err)