fix: feed repitition
This commit is contained in:
@@ -36,11 +36,12 @@ type PostService struct {
|
||||
likes *repository.LikeRepo
|
||||
mentions *repository.MentionRepo
|
||||
comments *repository.CommentRepo
|
||||
feedSeen *repository.FeedRepo // per-user "already served" set for the home feed
|
||||
notif *NotificationService // optional; set via SetNotifier
|
||||
}
|
||||
|
||||
func NewPostService(posts *repository.PostRepo, r2 *repository.R2, likes *repository.LikeRepo, mentions *repository.MentionRepo, comments *repository.CommentRepo) *PostService {
|
||||
return &PostService{posts: posts, r2: r2, likes: likes, mentions: mentions, comments: comments}
|
||||
func NewPostService(posts *repository.PostRepo, r2 *repository.R2, likes *repository.LikeRepo, mentions *repository.MentionRepo, comments *repository.CommentRepo, feedSeen *repository.FeedRepo) *PostService {
|
||||
return &PostService{posts: posts, r2: r2, likes: likes, mentions: mentions, comments: comments, feedSeen: feedSeen}
|
||||
}
|
||||
|
||||
// SetNotifier wires the notification seam after construction (the
|
||||
@@ -224,7 +225,23 @@ func (s *PostService) Feed(ctx context.Context, viewerID string, limit, offset i
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
posts, err := s.posts.FeedForViewer(ctx, viewerID, limit, offset)
|
||||
|
||||
// Non-repetition: a per-user Redis "seen" set is the cursor. offset==0 is
|
||||
// the start of a fresh session, so we clear it; every page then excludes
|
||||
// already-served ids. Because the feed's tier ordering depends on viewer
|
||||
// state (a like re-tiers a post), exclusion — not OFFSET — is what keeps
|
||||
// pages from repeating or skipping rows. With Redis enabled we drive
|
||||
// selection by the set alone (offset 0); without it we fall back to plain
|
||||
// offset paging and the client de-dups the occasional drift.
|
||||
if offset == 0 {
|
||||
s.feedSeen.Reset(ctx, viewerID)
|
||||
}
|
||||
exclude := s.feedSeen.Seen(ctx, viewerID)
|
||||
queryOffset := offset
|
||||
if s.feedSeen.Enabled() {
|
||||
queryOffset = 0
|
||||
}
|
||||
posts, err := s.posts.FeedForViewer(ctx, viewerID, limit, queryOffset, exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -243,9 +260,24 @@ func (s *PostService) Feed(ctx context.Context, viewerID string, limit, offset i
|
||||
if err := s.posts.HydrateViewerRelationships(ctx, viewerID, posts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Record what we served so the next page skips it (no-op without Redis).
|
||||
if len(posts) > 0 {
|
||||
ids := make([]string, len(posts))
|
||||
for i := range posts {
|
||||
ids[i] = posts[i].ID
|
||||
}
|
||||
s.feedSeen.MarkSeen(ctx, viewerID, ids)
|
||||
}
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
// ResetFeed clears the viewer's home-feed seen-set — called on logout and when
|
||||
// the session ends (the page unloads / the realtime socket drops), so a stale
|
||||
// set doesn't linger in Redis past its owner. No-op without Redis.
|
||||
func (s *PostService) ResetFeed(ctx context.Context, viewerID string) {
|
||||
s.feedSeen.Reset(ctx, viewerID)
|
||||
}
|
||||
|
||||
// hydratePinnedSlice fills PinnedComment on a value slice.
|
||||
func (s *PostService) hydratePinnedSlice(ctx context.Context, viewerID string, posts []model.Post) error {
|
||||
if len(posts) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user