A community based topic aggregation platform built on atproto
at main 114 lines 3.2 kB view raw
1package common 2 3import ( 4 "Coves/internal/api/middleware" 5 "Coves/internal/core/communities" 6 "Coves/internal/core/posts" 7 "Coves/internal/core/votes" 8 "context" 9 "log" 10 "net/http" 11) 12 13// FeedPostProvider is implemented by any feed post wrapper that contains a PostView. 14// This allows the helper to work with different feed post types (discover, timeline, communityFeed). 15type FeedPostProvider interface { 16 GetPost() *posts.PostView 17} 18 19// PopulateViewerVoteState enriches feed posts with the authenticated user's vote state. 20// This is a no-op if voteService is nil or the request is unauthenticated. 21// 22// Parameters: 23// - ctx: Request context for PDS calls 24// - r: HTTP request (used to extract OAuth session) 25// - voteService: Vote service for cache lookup (may be nil) 26// - feedPosts: Posts to enrich with viewer state (must implement FeedPostProvider) 27// 28// The function logs but does not fail on errors - viewer state is optional enrichment. 29func PopulateViewerVoteState[T FeedPostProvider]( 30 ctx context.Context, 31 r *http.Request, 32 voteService votes.Service, 33 feedPosts []T, 34) { 35 if voteService == nil { 36 return 37 } 38 39 session := middleware.GetOAuthSession(r) 40 if session == nil { 41 return 42 } 43 44 userDID := middleware.GetUserDID(r) 45 46 // Ensure vote cache is populated from PDS 47 if err := voteService.EnsureCachePopulated(ctx, session); err != nil { 48 log.Printf("Warning: failed to populate vote cache: %v", err) 49 return 50 } 51 52 // Collect post URIs to batch lookup 53 postURIs := make([]string, 0, len(feedPosts)) 54 for _, feedPost := range feedPosts { 55 if post := feedPost.GetPost(); post != nil { 56 postURIs = append(postURIs, post.URI) 57 } 58 } 59 60 // Get viewer votes for all posts 61 viewerVotes := voteService.GetViewerVotesForSubjects(userDID, postURIs) 62 63 // Populate viewer state on each post 64 for _, feedPost := range feedPosts { 65 if post := feedPost.GetPost(); post != nil { 66 if vote, exists := viewerVotes[post.URI]; exists { 67 post.Viewer = &posts.ViewerState{ 68 Vote: &vote.Direction, 69 VoteURI: &vote.URI, 70 } 71 } 72 } 73 } 74} 75 76// PopulateCommunityViewerState enriches communities with the authenticated user's subscription state. 77// This is a no-op if the request is unauthenticated. 78func PopulateCommunityViewerState( 79 ctx context.Context, 80 r *http.Request, 81 repo communities.Repository, 82 communityList []*communities.Community, 83) { 84 if repo == nil || len(communityList) == 0 { 85 return 86 } 87 88 userDID := middleware.GetUserDID(r) 89 if userDID == "" { 90 return // Not authenticated, leave viewer state nil 91 } 92 93 // Collect community DIDs 94 communityDIDs := make([]string, len(communityList)) 95 for i, c := range communityList { 96 communityDIDs[i] = c.DID 97 } 98 99 // Batch query subscriptions 100 subscribed, err := repo.GetSubscribedCommunityDIDs(ctx, userDID, communityDIDs) 101 if err != nil { 102 log.Printf("Warning: failed to get subscription state for user %s (%d communities): %v", 103 userDID, len(communityDIDs), err) 104 return 105 } 106 107 // Populate viewer state on each community 108 for _, c := range communityList { 109 isSubscribed := subscribed[c.DID] 110 c.Viewer = &communities.CommunityViewerState{ 111 Subscribed: &isSubscribed, 112 } 113 } 114}