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