feat: post reshare enhancements

This commit is contained in:
Krispire Admin
2026-06-03 14:41:15 +05:30
parent 3f6b7b6636
commit 7f5b0fee11
9 changed files with 397 additions and 190 deletions
+24 -31
View File
@@ -75,39 +75,33 @@ func (s *PostService) Get(ctx context.Context, id, viewerID string) (*model.Post
return p, nil
}
// Reshare records that the caller has reshared the given post. Idempotent
// — calling it twice with the same (post, user) is a no-op. Resharing
// your own post is rejected: there's nothing to bring onto an account
// that already authored the post.
func (s *PostService) Reshare(ctx context.Context, postID, callerID string) error {
p, err := s.posts.FindByID(ctx, postID)
if err != nil {
return err
}
if p.UserID == callerID {
return errors.New("cannot reshare your own post")
}
return s.posts.AddReshare(ctx, postID, callerID)
// Reshare materialises a reshare as a brand-new posts row owned by the
// caller, with reshared_from_post_id pointing to the canonical original.
// Idempotent — calling it twice returns the same reshare row. Resharing
// one's own post (canonical or chained) is rejected at the repo layer.
//
// Returns the new post so the frontend can drop it into local state
// without a round-trip.
func (s *PostService) Reshare(ctx context.Context, postID, callerID string) (*model.Post, error) {
return s.posts.Reshare(ctx, postID, callerID)
}
// Unreshare removes a previously recorded reshare. Idempotent — no-ops
// when no row exists.
// Unreshare removes the caller's reshare of a canonical post by deleting
// their own reshare posts row. Idempotent — no-ops when nothing to delete.
func (s *PostService) Unreshare(ctx context.Context, postID, callerID string) error {
return s.posts.RemoveReshare(ctx, postID, callerID)
return s.posts.Unreshare(ctx, postID, callerID)
}
// hydrateReshareStates fills in ViewerHasReshared on each post via one
// batched lookup against post_reshares. Skips the query when the viewer
// is anonymous (`viewerID == ""`) since the answer is uniformly false.
// Also fast-paths posts authored by the viewer — they can't be reshared
// by themselves, so we leave the flag at its zero value.
// batched lookup. Skips the query when the viewer is anonymous (the
// answer is uniformly false) or the slice is empty.
func (s *PostService) hydrateReshareStates(ctx context.Context, viewerID string, posts []*model.Post) error {
if viewerID == "" || len(posts) == 0 {
return nil
}
ids := make([]string, 0, len(posts))
for _, p := range posts {
if p == nil || p.UserID == viewerID {
if p == nil {
continue
}
ids = append(ids, p.ID)
@@ -150,9 +144,9 @@ func (s *PostService) Delete(ctx context.Context, postID, callerID string) error
return s.posts.Delete(ctx, postID, callerID)
}
// ReplaceImages swaps the image set on a post. The caller must own the
// post. Returns the fresh post payload so the client can reconcile its
// local state without a separate fetch.
// ReplaceImages swaps the image set on a post. Owner-only — every
// reshare is its own posts row, owned by the resharer, so this works
// uniformly for canonical posts and reshares.
func (s *PostService) ReplaceImages(ctx context.Context, postID, callerID string, imageURLs []string) (*model.Post, error) {
if err := s.posts.ReplaceImages(ctx, postID, callerID, imageURLs); err != nil {
return nil, err
@@ -180,12 +174,11 @@ func (s *PostService) ListByHandle(ctx context.Context, handle, viewerID string,
return posts, total, nil
}
// ListMine is the owner's view of their own profile feed — authored
// posts plus reshares (the repo's ListByUser unions the two). The
// viewer here is by construction the feed owner, so every reshared
// row is one the viewer themselves reshared — we flip
// ViewerHasReshared inline rather than spending a hydration round-trip
// to learn what we already know.
// ListMine is the owner's view of their own profile feed. Every post
// they own — authored or reshared — comes from posts.user_id, so the
// viewer is by construction the owner. ViewerHasReshared is true for
// reshare rows (the user IS the resharer) and false for canonical
// rows (you don't "reshare your own post").
func (s *PostService) ListMine(ctx context.Context, userID string, limit, offset int) ([]model.Post, int, error) {
if limit <= 0 || limit > 50 {
limit = 20
@@ -198,7 +191,7 @@ func (s *PostService) ListMine(ctx context.Context, userID string, limit, offset
return nil, 0, err
}
for i := range posts {
if posts[i].ResharedAt != nil {
if posts[i].ResharedFromPostID != nil {
posts[i].ViewerHasReshared = true
}
}