A community based topic aggregation platform built on atproto
1package postgres 2 3import ( 4 "context" 5 "database/sql" 6 "fmt" 7 "log" 8 "strings" 9 10 "Coves/internal/core/communities" 11) 12 13// CreateMembership creates a new membership record 14func (r *postgresCommunityRepo) CreateMembership(ctx context.Context, membership *communities.Membership) (*communities.Membership, error) { 15 query := ` 16 INSERT INTO community_memberships ( 17 user_did, community_did, reputation_score, contribution_count, 18 joined_at, last_active_at, is_banned, is_moderator 19 ) 20 VALUES ($1, $2, $3, $4, $5, $6, $7, $8) 21 RETURNING id, joined_at, last_active_at` 22 23 err := r.db.QueryRowContext(ctx, query, 24 membership.UserDID, 25 membership.CommunityDID, 26 membership.ReputationScore, 27 membership.ContributionCount, 28 membership.JoinedAt, 29 membership.LastActiveAt, 30 membership.IsBanned, 31 membership.IsModerator, 32 ).Scan(&membership.ID, &membership.JoinedAt, &membership.LastActiveAt) 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 func() { 124 if closeErr := rows.Close(); closeErr != nil { 125 log.Printf("Failed to close rows: %v", closeErr) 126 } 127 }() 128 129 result := []*communities.Membership{} 130 for rows.Next() { 131 membership := &communities.Membership{} 132 133 scanErr := rows.Scan( 134 &membership.ID, 135 &membership.UserDID, 136 &membership.CommunityDID, 137 &membership.ReputationScore, 138 &membership.ContributionCount, 139 &membership.JoinedAt, 140 &membership.LastActiveAt, 141 &membership.IsBanned, 142 &membership.IsModerator, 143 ) 144 if scanErr != nil { 145 return nil, fmt.Errorf("failed to scan member: %w", scanErr) 146 } 147 148 result = append(result, membership) 149 } 150 151 if err = rows.Err(); err != nil { 152 return nil, fmt.Errorf("error iterating members: %w", err) 153 } 154 155 return result, nil 156} 157 158// CreateModerationAction records a moderation action 159func (r *postgresCommunityRepo) CreateModerationAction(ctx context.Context, action *communities.ModerationAction) (*communities.ModerationAction, error) { 160 query := ` 161 INSERT INTO community_moderation ( 162 community_did, action, reason, instance_did, broadcast, created_at, expires_at 163 ) 164 VALUES ($1, $2, $3, $4, $5, $6, $7) 165 RETURNING id, created_at` 166 167 err := r.db.QueryRowContext(ctx, query, 168 action.CommunityDID, 169 action.Action, 170 nullString(action.Reason), 171 action.InstanceDID, 172 action.Broadcast, 173 action.CreatedAt, 174 action.ExpiresAt, 175 ).Scan(&action.ID, &action.CreatedAt) 176 if err != nil { 177 if strings.Contains(err.Error(), "foreign key") { 178 return nil, communities.ErrCommunityNotFound 179 } 180 return nil, fmt.Errorf("failed to create moderation action: %w", err) 181 } 182 183 return action, nil 184} 185 186// ListModerationActions retrieves moderation actions for a community 187func (r *postgresCommunityRepo) ListModerationActions(ctx context.Context, communityDID string, limit, offset int) ([]*communities.ModerationAction, error) { 188 query := ` 189 SELECT id, community_did, action, reason, instance_did, broadcast, created_at, expires_at 190 FROM community_moderation 191 WHERE community_did = $1 192 ORDER BY created_at DESC 193 LIMIT $2 OFFSET $3` 194 195 rows, err := r.db.QueryContext(ctx, query, communityDID, limit, offset) 196 if err != nil { 197 return nil, fmt.Errorf("failed to list moderation actions: %w", err) 198 } 199 defer func() { 200 if closeErr := rows.Close(); closeErr != nil { 201 log.Printf("Failed to close rows: %v", closeErr) 202 } 203 }() 204 205 result := []*communities.ModerationAction{} 206 for rows.Next() { 207 action := &communities.ModerationAction{} 208 var reason sql.NullString 209 210 scanErr := rows.Scan( 211 &action.ID, 212 &action.CommunityDID, 213 &action.Action, 214 &reason, 215 &action.InstanceDID, 216 &action.Broadcast, 217 &action.CreatedAt, 218 &action.ExpiresAt, 219 ) 220 if scanErr != nil { 221 return nil, fmt.Errorf("failed to scan moderation action: %w", scanErr) 222 } 223 224 action.Reason = reason.String 225 result = append(result, action) 226 } 227 228 if err = rows.Err(); err != nil { 229 return nil, fmt.Errorf("error iterating moderation actions: %w", err) 230 } 231 232 return result, nil 233} 234 235// Statistics methods 236func (r *postgresCommunityRepo) IncrementMemberCount(ctx context.Context, communityDID string) error { 237 query := `UPDATE communities SET member_count = member_count + 1 WHERE did = $1` 238 _, err := r.db.ExecContext(ctx, query, communityDID) 239 if err != nil { 240 return fmt.Errorf("failed to increment member count: %w", err) 241 } 242 return nil 243} 244 245func (r *postgresCommunityRepo) DecrementMemberCount(ctx context.Context, communityDID string) error { 246 query := `UPDATE communities SET member_count = GREATEST(0, member_count - 1) WHERE did = $1` 247 _, err := r.db.ExecContext(ctx, query, communityDID) 248 if err != nil { 249 return fmt.Errorf("failed to decrement member count: %w", err) 250 } 251 return nil 252} 253 254func (r *postgresCommunityRepo) IncrementSubscriberCount(ctx context.Context, communityDID string) error { 255 query := `UPDATE communities SET subscriber_count = subscriber_count + 1 WHERE did = $1` 256 _, err := r.db.ExecContext(ctx, query, communityDID) 257 if err != nil { 258 return fmt.Errorf("failed to increment subscriber count: %w", err) 259 } 260 return nil 261} 262 263func (r *postgresCommunityRepo) DecrementSubscriberCount(ctx context.Context, communityDID string) error { 264 query := `UPDATE communities SET subscriber_count = GREATEST(0, subscriber_count - 1) WHERE did = $1` 265 _, err := r.db.ExecContext(ctx, query, communityDID) 266 if err != nil { 267 return fmt.Errorf("failed to decrement subscriber count: %w", err) 268 } 269 return nil 270} 271 272func (r *postgresCommunityRepo) IncrementPostCount(ctx context.Context, communityDID string) error { 273 query := `UPDATE communities SET post_count = post_count + 1 WHERE did = $1` 274 _, err := r.db.ExecContext(ctx, query, communityDID) 275 if err != nil { 276 return fmt.Errorf("failed to increment post count: %w", err) 277 } 278 return nil 279}