A community based topic aggregation platform built on atproto

refactor(consumer): use RepositoryTx for atomic delete operations

Replace inline SQL in deleteCommentAndUpdateCounts with call to
SoftDeleteWithReasonTx via type assertion to RepositoryTx interface.

This eliminates duplicate deletion logic between consumer and repo
while maintaining atomic transaction for delete + count updates.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+15 -12
+15 -12
internal/atproto/jetstream/comment_consumer.go
··· 260 260 // Comment was soft-deleted, now being recreated (resurrection) 261 261 // This is a NEW record with same rkey - update ALL fields including threading refs 262 262 // User may have deleted old comment and created a new one on a different parent/root 263 + // Clear deletion metadata to restore the comment 263 264 log.Printf("Resurrecting previously deleted comment: %s", comment.URI) 264 265 commentID = existingID 265 266 ··· 280 281 created_at = $12, 281 282 indexed_at = $13, 282 283 deleted_at = NULL, 284 + deletion_reason = NULL, 285 + deleted_by = NULL, 283 286 reply_count = 0 284 287 WHERE id = $14 285 288 ` ··· 420 423 } 421 424 422 425 // deleteCommentAndUpdateCounts atomically soft-deletes a comment and updates parent counts 426 + // Blanks content to preserve thread structure while respecting user privacy 427 + // The comment remains in the database but is shown as "[deleted]" in thread views 423 428 func (c *CommentEventConsumer) deleteCommentAndUpdateCounts(ctx context.Context, comment *comments.Comment) error { 424 429 tx, err := c.db.BeginTx(ctx, nil) 425 430 if err != nil { ··· 431 436 } 432 437 }() 433 438 434 - // 1. Soft-delete the comment (idempotent) 435 - deleteQuery := ` 436 - UPDATE comments 437 - SET deleted_at = $2 438 - WHERE uri = $1 AND deleted_at IS NULL 439 - ` 440 - 441 - result, err := tx.ExecContext(ctx, deleteQuery, comment.URI, time.Now()) 442 - if err != nil { 443 - return fmt.Errorf("failed to delete comment: %w", err) 439 + // 1. Soft-delete the comment: blank content but preserve structure 440 + // DELETE event from Jetstream = author deleted their own comment 441 + // Content is blanked to respect user privacy while preserving thread structure 442 + // Use the repository's transaction-aware method for DRY 443 + repoTx, ok := c.commentRepo.(comments.RepositoryTx) 444 + if !ok { 445 + return fmt.Errorf("comment repository does not support transactional operations") 444 446 } 445 447 446 - rowsAffected, err := result.RowsAffected() 448 + rowsAffected, err := repoTx.SoftDeleteWithReasonTx(ctx, tx, comment.URI, comments.DeletionReasonAuthor, comment.CommenterDID) 447 449 if err != nil { 448 - return fmt.Errorf("failed to check delete result: %w", err) 450 + return fmt.Errorf("failed to delete comment: %w", err) 449 451 } 450 452 451 453 // Idempotent: If no rows affected, comment already deleted ··· 462 464 collection := utils.ExtractCollectionFromURI(comment.ParentURI) 463 465 464 466 var updateQuery string 467 + var result sql.Result 465 468 switch collection { 466 469 case "social.coves.community.post": 467 470 // Comment on post - decrement posts.comment_count