A community based topic aggregation platform built on atproto
1package communityFeeds
2
3import (
4 "Coves/internal/core/communities"
5 "context"
6 "fmt"
7)
8
9type feedService struct {
10 repo Repository
11 communityService communities.Service
12}
13
14// NewCommunityFeedService creates a new feed service
15func NewCommunityFeedService(
16 repo Repository,
17 communityService communities.Service,
18) Service {
19 return &feedService{
20 repo: repo,
21 communityService: communityService,
22 }
23}
24
25// GetCommunityFeed retrieves posts from a community with sorting
26func (s *feedService) GetCommunityFeed(ctx context.Context, req GetCommunityFeedRequest) (*FeedResponse, error) {
27 // 1. Validate request
28 if err := s.validateRequest(&req); err != nil {
29 return nil, err
30 }
31
32 // 2. Resolve community identifier (handle or DID) to DID
33 communityDID, err := s.communityService.ResolveCommunityIdentifier(ctx, req.Community)
34 if err != nil {
35 if communities.IsNotFound(err) {
36 return nil, ErrCommunityNotFound
37 }
38 if communities.IsValidationError(err) {
39 return nil, NewValidationError("community", err.Error())
40 }
41 return nil, fmt.Errorf("failed to resolve community identifier: %w", err)
42 }
43
44 // 3. Update request with resolved DID
45 req.Community = communityDID
46
47 // 4. Fetch feed from repository (hydrated posts)
48 feedPosts, cursor, err := s.repo.GetCommunityFeed(ctx, req)
49 if err != nil {
50 return nil, fmt.Errorf("failed to get community feed: %w", err)
51 }
52
53 // 5. Return feed response
54 return &FeedResponse{
55 Feed: feedPosts,
56 Cursor: cursor,
57 }, nil
58}
59
60// validateRequest validates the feed request parameters
61func (s *feedService) validateRequest(req *GetCommunityFeedRequest) error {
62 // Validate community identifier
63 if req.Community == "" {
64 return NewValidationError("community", "community parameter is required")
65 }
66
67 // Validate and set defaults for sort
68 if req.Sort == "" {
69 req.Sort = "hot"
70 }
71 validSorts := map[string]bool{"hot": true, "top": true, "new": true}
72 if !validSorts[req.Sort] {
73 return NewValidationError("sort", "sort must be one of: hot, top, new")
74 }
75
76 // Validate and set defaults for limit
77 if req.Limit <= 0 {
78 req.Limit = 15
79 }
80 if req.Limit > 50 {
81 return NewValidationError("limit", "limit must not exceed 50")
82 }
83
84 // Validate and set defaults for timeframe (only used with top sort)
85 if req.Sort == "top" && req.Timeframe == "" {
86 req.Timeframe = "day"
87 }
88 validTimeframes := map[string]bool{
89 "hour": true, "day": true, "week": true,
90 "month": true, "year": true, "all": true,
91 }
92 if req.Timeframe != "" && !validTimeframes[req.Timeframe] {
93 return NewValidationError("timeframe", "timeframe must be one of: hour, day, week, month, year, all")
94 }
95
96 return nil
97}