A community based topic aggregation platform built on atproto
1package timeline 2 3import ( 4 "Coves/internal/api/middleware" 5 "Coves/internal/core/timeline" 6 "encoding/json" 7 "log" 8 "net/http" 9 "strconv" 10 "strings" 11) 12 13// GetTimelineHandler handles timeline feed retrieval 14type GetTimelineHandler struct { 15 service timeline.Service 16} 17 18// NewGetTimelineHandler creates a new timeline handler 19func NewGetTimelineHandler(service timeline.Service) *GetTimelineHandler { 20 return &GetTimelineHandler{ 21 service: service, 22 } 23} 24 25// HandleGetTimeline retrieves posts from all communities the user subscribes to 26// GET /xrpc/social.coves.feed.getTimeline?sort=hot&limit=15&cursor=... 27// Requires authentication (user must be logged in) 28func (h *GetTimelineHandler) HandleGetTimeline(w http.ResponseWriter, r *http.Request) { 29 if r.Method != http.MethodGet { 30 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 31 return 32 } 33 34 // Extract authenticated user DID from context (set by RequireAuth middleware) 35 userDID := middleware.GetUserDID(r) 36 if userDID == "" || !strings.HasPrefix(userDID, "did:") { 37 writeError(w, http.StatusUnauthorized, "AuthenticationRequired", "User must be authenticated to view timeline") 38 return 39 } 40 41 // Parse query parameters 42 req, err := h.parseRequest(r, userDID) 43 if err != nil { 44 writeError(w, http.StatusBadRequest, "InvalidRequest", err.Error()) 45 return 46 } 47 48 // Get timeline 49 response, err := h.service.GetTimeline(r.Context(), req) 50 if err != nil { 51 handleServiceError(w, err) 52 return 53 } 54 55 // Return feed 56 w.Header().Set("Content-Type", "application/json") 57 w.WriteHeader(http.StatusOK) 58 if err := json.NewEncoder(w).Encode(response); err != nil { 59 // Log encoding errors but don't return error response (headers already sent) 60 log.Printf("ERROR: Failed to encode timeline response: %v", err) 61 } 62} 63 64// parseRequest parses query parameters into GetTimelineRequest 65func (h *GetTimelineHandler) parseRequest(r *http.Request, userDID string) (timeline.GetTimelineRequest, error) { 66 req := timeline.GetTimelineRequest{ 67 UserDID: userDID, // Set from authenticated context 68 } 69 70 // Optional: sort (default: hot) 71 req.Sort = r.URL.Query().Get("sort") 72 if req.Sort == "" { 73 req.Sort = "hot" 74 } 75 76 // Optional: timeframe (default: day for top sort) 77 req.Timeframe = r.URL.Query().Get("timeframe") 78 if req.Timeframe == "" && req.Sort == "top" { 79 req.Timeframe = "day" 80 } 81 82 // Optional: limit (default: 15, max: 50) 83 req.Limit = 15 84 if limitStr := r.URL.Query().Get("limit"); limitStr != "" { 85 if limit, err := strconv.Atoi(limitStr); err == nil { 86 req.Limit = limit 87 } 88 } 89 90 // Optional: cursor 91 if cursor := r.URL.Query().Get("cursor"); cursor != "" { 92 req.Cursor = &cursor 93 } 94 95 return req, nil 96}