A community based topic aggregation platform built on atproto
1package postgres 2 3import ( 4 "Coves/internal/core/communities" 5 "context" 6 "database/sql" 7 "fmt" 8) 9 10// BlockCommunity creates a new block record (idempotent) 11func (r *postgresCommunityRepo) BlockCommunity(ctx context.Context, block *communities.CommunityBlock) (*communities.CommunityBlock, error) { 12 query := ` 13 INSERT INTO community_blocks (user_did, community_did, blocked_at, record_uri, record_cid) 14 VALUES ($1, $2, $3, $4, $5) 15 ON CONFLICT (user_did, community_did) DO UPDATE SET 16 record_uri = EXCLUDED.record_uri, 17 record_cid = EXCLUDED.record_cid, 18 blocked_at = EXCLUDED.blocked_at 19 RETURNING id, blocked_at` 20 21 err := r.db.QueryRowContext(ctx, query, 22 block.UserDID, 23 block.CommunityDID, 24 block.BlockedAt, 25 block.RecordURI, 26 block.RecordCID, 27 ).Scan(&block.ID, &block.BlockedAt) 28 if err != nil { 29 return nil, fmt.Errorf("failed to create block: %w", err) 30 } 31 32 return block, nil 33} 34 35// UnblockCommunity removes a block record 36func (r *postgresCommunityRepo) UnblockCommunity(ctx context.Context, userDID, communityDID string) error { 37 query := `DELETE FROM community_blocks WHERE user_did = $1 AND community_did = $2` 38 39 result, err := r.db.ExecContext(ctx, query, userDID, communityDID) 40 if err != nil { 41 return fmt.Errorf("failed to unblock community: %w", err) 42 } 43 44 rowsAffected, err := result.RowsAffected() 45 if err != nil { 46 return fmt.Errorf("failed to check unblock result: %w", err) 47 } 48 49 if rowsAffected == 0 { 50 return communities.ErrBlockNotFound 51 } 52 53 return nil 54} 55 56// GetBlock retrieves a block record by user DID and community DID 57func (r *postgresCommunityRepo) GetBlock(ctx context.Context, userDID, communityDID string) (*communities.CommunityBlock, error) { 58 query := ` 59 SELECT id, user_did, community_did, blocked_at, record_uri, record_cid 60 FROM community_blocks 61 WHERE user_did = $1 AND community_did = $2` 62 63 var block communities.CommunityBlock 64 65 err := r.db.QueryRowContext(ctx, query, userDID, communityDID).Scan( 66 &block.ID, 67 &block.UserDID, 68 &block.CommunityDID, 69 &block.BlockedAt, 70 &block.RecordURI, 71 &block.RecordCID, 72 ) 73 if err != nil { 74 if err == sql.ErrNoRows { 75 return nil, communities.ErrBlockNotFound 76 } 77 return nil, fmt.Errorf("failed to get block: %w", err) 78 } 79 80 return &block, nil 81} 82 83// GetBlockByURI retrieves a block record by its AT-URI (for Jetstream DELETE operations) 84func (r *postgresCommunityRepo) GetBlockByURI(ctx context.Context, recordURI string) (*communities.CommunityBlock, error) { 85 query := ` 86 SELECT id, user_did, community_did, blocked_at, record_uri, record_cid 87 FROM community_blocks 88 WHERE record_uri = $1` 89 90 var block communities.CommunityBlock 91 92 err := r.db.QueryRowContext(ctx, query, recordURI).Scan( 93 &block.ID, 94 &block.UserDID, 95 &block.CommunityDID, 96 &block.BlockedAt, 97 &block.RecordURI, 98 &block.RecordCID, 99 ) 100 if err != nil { 101 if err == sql.ErrNoRows { 102 return nil, communities.ErrBlockNotFound 103 } 104 return nil, fmt.Errorf("failed to get block by URI: %w", err) 105 } 106 107 return &block, nil 108} 109 110// ListBlockedCommunities retrieves all communities blocked by a user 111func (r *postgresCommunityRepo) ListBlockedCommunities(ctx context.Context, userDID string, limit, offset int) ([]*communities.CommunityBlock, error) { 112 query := ` 113 SELECT id, user_did, community_did, blocked_at, record_uri, record_cid 114 FROM community_blocks 115 WHERE user_did = $1 116 ORDER BY blocked_at DESC 117 LIMIT $2 OFFSET $3` 118 119 rows, err := r.db.QueryContext(ctx, query, userDID, limit, offset) 120 if err != nil { 121 return nil, fmt.Errorf("failed to list blocked communities: %w", err) 122 } 123 defer func() { 124 if closeErr := rows.Close(); closeErr != nil { 125 // Log error but don't override the main error 126 fmt.Printf("Failed to close rows: %v\n", closeErr) 127 } 128 }() 129 130 var blocks []*communities.CommunityBlock 131 for rows.Next() { 132 var block communities.CommunityBlock 133 134 err = rows.Scan( 135 &block.ID, 136 &block.UserDID, 137 &block.CommunityDID, 138 &block.BlockedAt, 139 &block.RecordURI, 140 &block.RecordCID, 141 ) 142 if err != nil { 143 return nil, fmt.Errorf("failed to scan block: %w", err) 144 } 145 146 blocks = append(blocks, &block) 147 } 148 149 if err = rows.Err(); err != nil { 150 return nil, fmt.Errorf("error iterating blocks: %w", err) 151 } 152 153 return blocks, nil 154} 155 156// IsBlocked checks if a user has blocked a specific community (fast EXISTS check) 157func (r *postgresCommunityRepo) IsBlocked(ctx context.Context, userDID, communityDID string) (bool, error) { 158 query := ` 159 SELECT EXISTS( 160 SELECT 1 FROM community_blocks 161 WHERE user_did = $1 AND community_did = $2 162 )` 163 164 var exists bool 165 err := r.db.QueryRowContext(ctx, query, userDID, communityDID).Scan(&exists) 166 if err != nil { 167 return false, fmt.Errorf("failed to check if blocked: %w", err) 168 } 169 170 return exists, nil 171} 172