A community based topic aggregation platform built on atproto
1package communityFeed
2
3import (
4 "Coves/internal/api/handlers/common"
5 "Coves/internal/core/communityFeeds"
6 "Coves/internal/core/posts"
7 "Coves/internal/core/votes"
8 "encoding/json"
9 "log"
10 "net/http"
11 "strconv"
12)
13
14// GetCommunityHandler handles community feed retrieval
15type GetCommunityHandler struct {
16 service communityFeeds.Service
17 voteService votes.Service
18}
19
20// NewGetCommunityHandler creates a new community feed handler
21func NewGetCommunityHandler(service communityFeeds.Service, voteService votes.Service) *GetCommunityHandler {
22 return &GetCommunityHandler{
23 service: service,
24 voteService: voteService,
25 }
26}
27
28// HandleGetCommunity retrieves posts from a community with sorting
29// GET /xrpc/social.coves.communityFeed.getCommunity?community={did_or_handle}&sort=hot&limit=15&cursor=...
30func (h *GetCommunityHandler) HandleGetCommunity(w http.ResponseWriter, r *http.Request) {
31 if r.Method != http.MethodGet {
32 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
33 return
34 }
35
36 // Parse query parameters
37 req, err := h.parseRequest(r)
38 if err != nil {
39 writeError(w, http.StatusBadRequest, "InvalidRequest", err.Error())
40 return
41 }
42
43 // Get community feed
44 response, err := h.service.GetCommunityFeed(r.Context(), req)
45 if err != nil {
46 handleServiceError(w, err)
47 return
48 }
49
50 // Populate viewer vote state if authenticated
51 common.PopulateViewerVoteState(r.Context(), r, h.voteService, response.Feed)
52
53 // Transform blob refs to URLs for all posts
54 for _, feedPost := range response.Feed {
55 if feedPost.Post != nil {
56 posts.TransformBlobRefsToURLs(feedPost.Post)
57 }
58 }
59
60 // Return feed
61 w.Header().Set("Content-Type", "application/json")
62 w.WriteHeader(http.StatusOK)
63 if err := json.NewEncoder(w).Encode(response); err != nil {
64 // Log encoding errors but don't return error response (headers already sent)
65 log.Printf("ERROR: Failed to encode feed response: %v", err)
66 }
67}
68
69// parseRequest parses query parameters into GetCommunityFeedRequest
70func (h *GetCommunityHandler) parseRequest(r *http.Request) (communityFeeds.GetCommunityFeedRequest, error) {
71 req := communityFeeds.GetCommunityFeedRequest{}
72
73 // Required: community
74 req.Community = r.URL.Query().Get("community")
75
76 // Optional: sort (default: hot)
77 req.Sort = r.URL.Query().Get("sort")
78 if req.Sort == "" {
79 req.Sort = "hot"
80 }
81
82 // Optional: timeframe (default: day for top sort)
83 req.Timeframe = r.URL.Query().Get("timeframe")
84 if req.Timeframe == "" && req.Sort == "top" {
85 req.Timeframe = "day"
86 }
87
88 // Optional: limit (default: 15, max: 50)
89 req.Limit = 15
90 if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
91 if limit, err := strconv.Atoi(limitStr); err == nil {
92 req.Limit = limit
93 }
94 }
95
96 // Optional: cursor
97 if cursor := r.URL.Query().Get("cursor"); cursor != "" {
98 req.Cursor = &cursor
99 }
100
101 return req, nil
102}