| 1 | package community |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "strings" |
| 7 | |
| 8 | "github.com/FelineStateMachine/tinyrelay/internal/communityread" |
| 9 | ) |
| 10 | |
| 11 | // Members exposes the community-owned membership projection to records. The |
| 12 | // records package consumes this narrow read model and never relies on table |
| 13 | // names or columns. |
| 14 | func (s *Service) Members(ctx context.Context) ([]communityread.Member, error) { |
| 15 | rows, err := s.store.DB().QueryContext(ctx, `SELECT pubkey,name,role FROM community_members ORDER BY pubkey` ) |
| 16 | if err != nil { |
| 17 | if strings.Contains(err.Error(), "no such table" ) { |
| 18 | return nil, nil |
| 19 | } |
| 20 | return nil, err |
| 21 | } |
| 22 | defer rows.Close() |
| 23 | var out []communityread.Member |
| 24 | for rows.Next() { |
| 25 | var member communityread.Member |
| 26 | if err := rows.Scan(&member.PubKey, &member.Name, &member.Role); err != nil { |
| 27 | return nil, err |
| 28 | } |
| 29 | out = append(out, member) |
| 30 | } |
| 31 | return out, rows.Err() |
| 32 | } |
| 33 | |
| 34 | // MemberStatus reports the key's membership and the current member count in one |
| 35 | // call so succession checks use a consistent community snapshot. |
| 36 | func (s *Service) MemberStatus(ctx context.Context, pubkey string) (bool, int, error) { |
| 37 | var count int |
| 38 | var member int |
| 39 | if err := s.store.DB().QueryRowContext(ctx, `SELECT count(*), coalesce(max(pubkey=?),0) FROM community_members` , pubkey).Scan(&count, &member); err != nil { |
| 40 | if strings.Contains(err.Error(), "no such table" ) { |
| 41 | return false, 0, nil |
| 42 | } |
| 43 | return false, 0, err |
| 44 | } |
| 45 | return member != 0, count, nil |
| 46 | } |
| 47 | |
| 48 | // ModerationCounts returns the counters needed by the moderation view. |
| 49 | func (s *Service) ModerationCounts(ctx context.Context, start int64) (communityread.ModerationCounts, error) { |
| 50 | var out communityread.ModerationCounts |
| 51 | queries := []struct { |
| 52 | table string |
| 53 | to *int |
| 54 | where string |
| 55 | }{ |
| 56 | {"community_bans" , &out.Bans, "at>=?" }, |
| 57 | {"community_reports" , &out.Reports, "at>=?" }, |
| 58 | {"community_reports" , &out.Resolved, "resolved_at>=?" }, |
| 59 | {"community_event_bans" , &out.Hidden, "at>=?" }, |
| 60 | {"community_ip_blocks" , &out.BlockedAddresses, "at>=?" }, |
| 61 | } |
| 62 | var failures error |
| 63 | for _, query := range queries { |
| 64 | if err := s.store.DB().QueryRowContext(ctx, "SELECT count(*) FROM " +query.table+" WHERE " +query.where, start).Scan(query.to); err != nil { |
| 65 | if strings.Contains(err.Error(), "no such table" ) { |
| 66 | continue |
| 67 | } |
| 68 | failures = errors.Join(failures, err) |
| 69 | } |
| 70 | } |
| 71 | return out, failures |
| 72 | } |