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