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