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}