A community based topic aggregation platform built on atproto
1package postgres
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "strings"
8
9 "Coves/internal/core/communities"
10)
11
12// CreateMembership creates a new membership record
13func (r *postgresCommunityRepo) CreateMembership(ctx context.Context, membership *communities.Membership) (*communities.Membership, error) {
14 query := `
15 INSERT INTO community_memberships (
16 user_did, community_did, reputation_score, contribution_count,
17 joined_at, last_active_at, is_banned, is_moderator
18 )
19 VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
20 RETURNING id, joined_at, last_active_at`
21
22 err := r.db.QueryRowContext(ctx, query,
23 membership.UserDID,
24 membership.CommunityDID,
25 membership.ReputationScore,
26 membership.ContributionCount,
27 membership.JoinedAt,
28 membership.LastActiveAt,
29 membership.IsBanned,
30 membership.IsModerator,
31 ).Scan(&membership.ID, &membership.JoinedAt, &membership.LastActiveAt)
32
33 if err != nil {
34 if strings.Contains(err.Error(), "duplicate key") {
35 return nil, fmt.Errorf("membership already exists")
36 }
37 if strings.Contains(err.Error(), "foreign key") {
38 return nil, communities.ErrCommunityNotFound
39 }
40 return nil, fmt.Errorf("failed to create membership: %w", err)
41 }
42
43 return membership, nil
44}
45
46// GetMembership retrieves a specific membership
47func (r *postgresCommunityRepo) GetMembership(ctx context.Context, userDID, communityDID string) (*communities.Membership, error) {
48 membership := &communities.Membership{}
49 query := `
50 SELECT id, user_did, community_did, reputation_score, contribution_count,
51 joined_at, last_active_at, is_banned, is_moderator
52 FROM community_memberships
53 WHERE user_did = $1 AND community_did = $2`
54
55 err := r.db.QueryRowContext(ctx, query, userDID, communityDID).Scan(
56 &membership.ID,
57 &membership.UserDID,
58 &membership.CommunityDID,
59 &membership.ReputationScore,
60 &membership.ContributionCount,
61 &membership.JoinedAt,
62 &membership.LastActiveAt,
63 &membership.IsBanned,
64 &membership.IsModerator,
65 )
66
67 if err == sql.ErrNoRows {
68 return nil, communities.ErrMembershipNotFound
69 }
70 if err != nil {
71 return nil, fmt.Errorf("failed to get membership: %w", err)
72 }
73
74 return membership, nil
75}
76
77// UpdateMembership updates an existing membership
78func (r *postgresCommunityRepo) UpdateMembership(ctx context.Context, membership *communities.Membership) (*communities.Membership, error) {
79 query := `
80 UPDATE community_memberships
81 SET reputation_score = $3,
82 contribution_count = $4,
83 last_active_at = $5,
84 is_banned = $6,
85 is_moderator = $7
86 WHERE user_did = $1 AND community_did = $2
87 RETURNING last_active_at`
88
89 err := r.db.QueryRowContext(ctx, query,
90 membership.UserDID,
91 membership.CommunityDID,
92 membership.ReputationScore,
93 membership.ContributionCount,
94 membership.LastActiveAt,
95 membership.IsBanned,
96 membership.IsModerator,
97 ).Scan(&membership.LastActiveAt)
98
99 if err == sql.ErrNoRows {
100 return nil, communities.ErrMembershipNotFound
101 }
102 if err != nil {
103 return nil, fmt.Errorf("failed to update membership: %w", err)
104 }
105
106 return membership, nil
107}
108
109// ListMembers retrieves members of a community ordered by reputation
110func (r *postgresCommunityRepo) ListMembers(ctx context.Context, communityDID string, limit, offset int) ([]*communities.Membership, error) {
111 query := `
112 SELECT id, user_did, community_did, reputation_score, contribution_count,
113 joined_at, last_active_at, is_banned, is_moderator
114 FROM community_memberships
115 WHERE community_did = $1
116 ORDER BY reputation_score DESC, joined_at ASC
117 LIMIT $2 OFFSET $3`
118
119 rows, err := r.db.QueryContext(ctx, query, communityDID, limit, offset)
120 if err != nil {
121 return nil, fmt.Errorf("failed to list members: %w", err)
122 }
123 defer rows.Close()
124
125 result := []*communities.Membership{}
126 for rows.Next() {
127 membership := &communities.Membership{}
128
129 err := rows.Scan(
130 &membership.ID,
131 &membership.UserDID,
132 &membership.CommunityDID,
133 &membership.ReputationScore,
134 &membership.ContributionCount,
135 &membership.JoinedAt,
136 &membership.LastActiveAt,
137 &membership.IsBanned,
138 &membership.IsModerator,
139 )
140 if err != nil {
141 return nil, fmt.Errorf("failed to scan member: %w", err)
142 }
143
144 result = append(result, membership)
145 }
146
147 if err = rows.Err(); err != nil {
148 return nil, fmt.Errorf("error iterating members: %w", err)
149 }
150
151 return result, nil
152}
153
154// CreateModerationAction records a moderation action
155func (r *postgresCommunityRepo) CreateModerationAction(ctx context.Context, action *communities.ModerationAction) (*communities.ModerationAction, error) {
156 query := `
157 INSERT INTO community_moderation (
158 community_did, action, reason, instance_did, broadcast, created_at, expires_at
159 )
160 VALUES ($1, $2, $3, $4, $5, $6, $7)
161 RETURNING id, created_at`
162
163 err := r.db.QueryRowContext(ctx, query,
164 action.CommunityDID,
165 action.Action,
166 nullString(action.Reason),
167 action.InstanceDID,
168 action.Broadcast,
169 action.CreatedAt,
170 action.ExpiresAt,
171 ).Scan(&action.ID, &action.CreatedAt)
172
173 if err != nil {
174 if strings.Contains(err.Error(), "foreign key") {
175 return nil, communities.ErrCommunityNotFound
176 }
177 return nil, fmt.Errorf("failed to create moderation action: %w", err)
178 }
179
180 return action, nil
181}
182
183// ListModerationActions retrieves moderation actions for a community
184func (r *postgresCommunityRepo) ListModerationActions(ctx context.Context, communityDID string, limit, offset int) ([]*communities.ModerationAction, error) {
185 query := `
186 SELECT id, community_did, action, reason, instance_did, broadcast, created_at, expires_at
187 FROM community_moderation
188 WHERE community_did = $1
189 ORDER BY created_at DESC
190 LIMIT $2 OFFSET $3`
191
192 rows, err := r.db.QueryContext(ctx, query, communityDID, limit, offset)
193 if err != nil {
194 return nil, fmt.Errorf("failed to list moderation actions: %w", err)
195 }
196 defer rows.Close()
197
198 result := []*communities.ModerationAction{}
199 for rows.Next() {
200 action := &communities.ModerationAction{}
201 var reason sql.NullString
202
203 err := rows.Scan(
204 &action.ID,
205 &action.CommunityDID,
206 &action.Action,
207 &reason,
208 &action.InstanceDID,
209 &action.Broadcast,
210 &action.CreatedAt,
211 &action.ExpiresAt,
212 )
213 if err != nil {
214 return nil, fmt.Errorf("failed to scan moderation action: %w", err)
215 }
216
217 action.Reason = reason.String
218 result = append(result, action)
219 }
220
221 if err = rows.Err(); err != nil {
222 return nil, fmt.Errorf("error iterating moderation actions: %w", err)
223 }
224
225 return result, nil
226}
227
228// Statistics methods
229func (r *postgresCommunityRepo) IncrementMemberCount(ctx context.Context, communityDID string) error {
230 query := `UPDATE communities SET member_count = member_count + 1 WHERE did = $1`
231 _, err := r.db.ExecContext(ctx, query, communityDID)
232 if err != nil {
233 return fmt.Errorf("failed to increment member count: %w", err)
234 }
235 return nil
236}
237
238func (r *postgresCommunityRepo) DecrementMemberCount(ctx context.Context, communityDID string) error {
239 query := `UPDATE communities SET member_count = GREATEST(0, member_count - 1) WHERE did = $1`
240 _, err := r.db.ExecContext(ctx, query, communityDID)
241 if err != nil {
242 return fmt.Errorf("failed to decrement member count: %w", err)
243 }
244 return nil
245}
246
247func (r *postgresCommunityRepo) IncrementSubscriberCount(ctx context.Context, communityDID string) error {
248 query := `UPDATE communities SET subscriber_count = subscriber_count + 1 WHERE did = $1`
249 _, err := r.db.ExecContext(ctx, query, communityDID)
250 if err != nil {
251 return fmt.Errorf("failed to increment subscriber count: %w", err)
252 }
253 return nil
254}
255
256func (r *postgresCommunityRepo) DecrementSubscriberCount(ctx context.Context, communityDID string) error {
257 query := `UPDATE communities SET subscriber_count = GREATEST(0, subscriber_count - 1) WHERE did = $1`
258 _, err := r.db.ExecContext(ctx, query, communityDID)
259 if err != nil {
260 return fmt.Errorf("failed to decrement subscriber count: %w", err)
261 }
262 return nil
263}
264
265func (r *postgresCommunityRepo) IncrementPostCount(ctx context.Context, communityDID string) error {
266 query := `UPDATE communities SET post_count = post_count + 1 WHERE did = $1`
267 _, err := r.db.ExecContext(ctx, query, communityDID)
268 if err != nil {
269 return fmt.Errorf("failed to increment post count: %w", err)
270 }
271 return nil
272}