Skip to content

like feature to comments - #38

Open
dinesh-2047 wants to merge 3 commits into
Sohaibkundi2:mainfrom
dinesh-2047:comments
Open

like feature to comments#38
dinesh-2047 wants to merge 3 commits into
Sohaibkundi2:mainfrom
dinesh-2047:comments

Conversation

@dinesh-2047

@dinesh-2047 dinesh-2047 commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

#24 solved

Summary by CodeRabbit

Release Notes

  • New Features
    • Added ability to like and unlike comments on videos with real-time like counts.
    • Comments now display like status and counts for better engagement visibility.
    • Introduced a personalized feed showing mixed content (videos and tweets) from followed channels.
    • Enhanced comment details to include additional owner information.

@vercel

vercel Bot commented Mar 8, 2026

Copy link
Copy Markdown

@dinesh-2047 is attempting to deploy a commit to the sohaib's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 8, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive comment liking system with like state enrichment across comment operations, extends the repost model to support video reposts alongside tweets, and implements a new following feed endpoint that aggregates videos and tweets from subscribed channels with enriched metadata. Backend routes and frontend components are updated accordingly to support these features.

Changes

Cohort / File(s) Summary
Comment Liking Feature
backend/src/controllers/comment.controller.js, backend/src/controllers/like.controller.js, backend/src/models/like.model.js
Added internal helper attachCommentLikeState to compute like counts and user like status. Updated comment CRUD operations to augment responses with like state. Like model now enforces exactly one target (video or comment) via pre-validate hook with conditional unique indexes.
Comment Liking Routes
backend/src/routes/comment.route.js, backend/src/routes/like.route.js
Comment GET route now uses optional JWT verification. Added routes for toggle comment like (PATCH /comments/:commentId/toggle) and get comment like count (GET /comments/:commentId/count).
Comment Liking Frontend
frontend/src/api/index.js, frontend/src/pages/videoPlayer.jsx
Introduced toggleCommentLike API function and corresponding handler. Updated comment UI with like button showing count and like state, with visual feedback and error handling.
Feed Feature
backend/src/controllers/subscription.controller.js, backend/src/routes/subscription.route.js
Implemented new getFollowingFeed endpoint with helper functions attachFeedVideoState and attachFeedTweetState. Constructs paginated mixed feed of videos and tweets from followed channels with enriched repost and watch-later metadata.
Repost Model Enhancement
backend/src/models/repost.model.js
Restructured repost schema to support both video and tweet targets with pre-validate hook enforcing exactly one target. Added timestamps and conditional unique indexes per target type.

Sequence Diagrams

sequenceDiagram
    actor User
    participant Frontend
    participant API as Backend API
    participant DB as Database
    participant Notify as Notifications

    User->>Frontend: Click like on comment
    Frontend->>API: PATCH /likes/comments/{commentId}/toggle
    API->>DB: Check Like document (likedBy + commentId)
    alt Like exists
        API->>DB: Remove Like document
    else Like doesn't exist
        API->>DB: Create Like document
        API->>Notify: Create LIKE notification to comment owner
        Notify->>Notify: Emit socket event
    end
    API->>DB: Aggregate like count for comment
    API->>Frontend: Return updated likeCount & isLiked
    Frontend->>User: Update UI with new like state
Loading
sequenceDiagram
    actor User
    participant Frontend
    participant API as Backend API
    participant DB as Database

    User->>Frontend: Request following feed
    Frontend->>API: GET /subscriptions/feed
    API->>DB: Fetch user subscriptions
    API->>DB: Query videos from subscribed channels
    API->>DB: Query tweets from subscribed channels
    API->>DB: Fetch reposts & watch-later for context
    API->>API: Merge & paginate videos + tweets by createdAt
    API->>API: Enrich with isReposted, repostCount, isInWatchLater
    API->>Frontend: Return paginated feedItems with type & metadata
    Frontend->>User: Display mixed video/tweet feed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

apertre3.0, hard

Poem

🐰 Comments now sparkle with likes so grand,
Mixed feeds delight where reposts expand,
From tweets to videos, all in one view,
The validation hops ensure just right—true!
A fluffy feast of features, fresh and new! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'like feature to comments' directly and clearly describes the main objective of this pull request, which adds a complete like feature to the comment system across backend and frontend.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/src/models/repost.model.js (1)

53-73: ⚠️ Potential issue | 🔴 Critical

Critical: Duplicate schema definitions will break video reposts.

The file contains two conflicting repostSchema definitions. The second definition (lines 55-69) overwrites the first and exports a schema where tweet is required, which will cause video reposts to fail validation.

The static analyzer flagged this correctly - the Repost export on line 73 redeclares and overwrites the export on line 52.

Remove lines 53-73 entirely to retain only the new polymorphic schema that supports both video and tweet targets.

🔧 Fix: Remove duplicate schema
 export const Repost = mongoose.model('Repost', repostSchema)
-import mongoose from "mongoose";
-
-const repostSchema = new mongoose.Schema(
-  {
-    tweet: {
-      type: mongoose.Schema.Types.ObjectId,
-      ref: "Tweet",
-      required: true,
-    },
-    repostedBy: {
-      type: mongoose.Schema.Types.ObjectId,
-      ref: "User",
-      required: true,
-    },
-  },
-  { timestamps: true }
-);
-
-repostSchema.index({ tweet: 1, repostedBy: 1 }, { unique: true });
-
-export const Repost = mongoose.model("Repost", repostSchema);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/models/repost.model.js` around lines 53 - 73, There are two
conflicting repostSchema definitions causing the earlier schema (with tweet
required) to overwrite the polymorphic one; remove the duplicate older schema
block and its export so only the polymorphic repostSchema and its
mongoose.model("Repost", repostSchema) export remain; specifically delete the
redundant repostSchema declaration and the export that reference the required
tweet field so the final Repost model uses the polymorphic schema.
🧹 Nitpick comments (4)
backend/src/models/repost.model.js (1)

32-50: Consider using $type: 'objectId' for consistency with like.model.js.

The partial filter expressions use { $exists: true }, but since the fields can be undefined (not set) rather than explicitly null, this works. However, like.model.js uses { $type: 'objectId' } which is more explicit. Consider aligning the patterns for consistency.

♻️ Align with like.model.js pattern
 repostSchema.index(
   { repostedBy: 1, video: 1 },
   {
     unique: true,
     partialFilterExpression: {
-      video: { $exists: true },
+      video: { $type: 'objectId' },
     },
   },
 )

 repostSchema.index(
   { repostedBy: 1, tweet: 1 },
   {
     unique: true,
     partialFilterExpression: {
-      tweet: { $exists: true },
+      tweet: { $type: 'objectId' },
     },
   },
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/models/repost.model.js` around lines 32 - 50, The
partialFilterExpression for the two repostSchema.index calls should use $type:
'objectId' instead of $exists to match like.model.js and be more explicit:
update the partialFilterExpression objects in the repostSchema.index invocations
(the ones targeting {repostedBy:1, video:1} and {repostedBy:1, tweet:1}) to
check video and tweet with { $type: 'objectId' } (and keep unique: true and the
same keys) so the indexes only apply when those fields are actual ObjectId
values.
backend/src/controllers/comment.controller.js (1)

190-191: Delete order leaves orphan likes on failure.

If Like.deleteMany throws after comment.deleteOne() succeeds, orphan Like documents referencing the deleted comment will persist. This matches the existing pattern in tweet.controller.js (line 241-242), so it's consistent with the codebase, but worth noting.

Consider wrapping in a transaction for atomicity if data consistency becomes a concern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/controllers/comment.controller.js` around lines 190 - 191,
Currently comment.deleteOne() runs before Like.deleteMany(), which can leave
orphaned Like documents if the second op fails; fix by performing both deletions
within a mongoose transaction: obtain a session via mongoose.startSession(),
session.startTransaction(), call Like.deleteMany({ comment: comment._id
}).session(session) and comment.deleteOne({ session }) (or Model.deleteOne with
session), then await session.commitTransaction() and session.endSession(); on
error call session.abortTransaction() and session.endSession(). This ensures
atomicity for the comment.deleteOne() and Like.deleteMany() operations.
backend/src/controllers/subscription.controller.js (2)

281-290: Consider removing redundant videos and tweets from response.

The response includes both items (the paginated feed) and full videos/tweets arrays. Since items already contains the video/tweet data wrapped with type discriminator, including the separate arrays duplicates data and increases payload size.

If the frontend only needs the paginated feed, consider:

 {
   items: pagedItems,
-  videos,
-  tweets,
   channels: followedChannels,
   page,
   limit,
   totalItems: feedItems.length,
   hasMore: sliceStart + limit < feedItems.length,
 },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/controllers/subscription.controller.js` around lines 281 - 290,
The response object currently returns duplicate payloads by including both the
paginated feed (items/pagedItems) and the full videos and tweets arrays; remove
the redundant properties by deleting videos and tweets from the returned object
(keep items: pagedItems, channels: followedChannels, page, limit, totalItems,
hasMore) in the controller response where the object is constructed, and ensure
any consumer references to videos or tweets are updated to read from items (or
map items to their underlying data) before deployment.

203-206: Pagination scalability concern with fetchCount = page * limit.

The current approach fetches page * limit items from each collection. On page 10 with limit 12, this fetches 120 items from each collection. This grows linearly with page number.

For an MVP this is acceptable, but consider cursor-based pagination (using createdAt as cursor) for better scalability if this feed grows large.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/controllers/subscription.controller.js` around lines 203 - 206,
The current pagination computes fetchCount = page * limit and sliceStart = (page
- 1) * limit which causes the DB to fetch all prior pages for each request;
change to cursor-based pagination (use createdAt as the cursor) so each call
only retrieves up to limit items. Specifically, replace the
fetchCount/sliceStart approach in the controller with logic that accepts a
cursor (e.g., req.query.cursor or lastCreatedAt) and queries each collection
with a filter like { createdAt: { $lt: cursor } } (or $gt for ascending), sorted
by createdAt and limited by limit, removing the need to multiply page*limit;
ensure the API returns the next cursor (the last returned createdAt) and update
any references to fetchCount/sliceStart accordingly (look for variables
fetchCount, sliceStart, page, limit).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/src/controllers/like.controller.js`:
- Line 138: The notification URL construction uses `url:
\`/watch/${comment.video?._id || ''}\`` which produces `/watch/` when
`comment.video` is null; modify the logic in like.controller.js to first check
that `comment.video` and `comment.video._id` exist and either skip
creating/sending the notification when the video no longer exists or populate
`url` with a safe fallback (e.g., null/empty/overview path) and ensure
downstream code handles that case; update the block that builds the notification
payload so it only sets the `/watch/...` URL when `comment.video._id` is
present.

---

Outside diff comments:
In `@backend/src/models/repost.model.js`:
- Around line 53-73: There are two conflicting repostSchema definitions causing
the earlier schema (with tweet required) to overwrite the polymorphic one;
remove the duplicate older schema block and its export so only the polymorphic
repostSchema and its mongoose.model("Repost", repostSchema) export remain;
specifically delete the redundant repostSchema declaration and the export that
reference the required tweet field so the final Repost model uses the
polymorphic schema.

---

Nitpick comments:
In `@backend/src/controllers/comment.controller.js`:
- Around line 190-191: Currently comment.deleteOne() runs before
Like.deleteMany(), which can leave orphaned Like documents if the second op
fails; fix by performing both deletions within a mongoose transaction: obtain a
session via mongoose.startSession(), session.startTransaction(), call
Like.deleteMany({ comment: comment._id }).session(session) and
comment.deleteOne({ session }) (or Model.deleteOne with session), then await
session.commitTransaction() and session.endSession(); on error call
session.abortTransaction() and session.endSession(). This ensures atomicity for
the comment.deleteOne() and Like.deleteMany() operations.

In `@backend/src/controllers/subscription.controller.js`:
- Around line 281-290: The response object currently returns duplicate payloads
by including both the paginated feed (items/pagedItems) and the full videos and
tweets arrays; remove the redundant properties by deleting videos and tweets
from the returned object (keep items: pagedItems, channels: followedChannels,
page, limit, totalItems, hasMore) in the controller response where the object is
constructed, and ensure any consumer references to videos or tweets are updated
to read from items (or map items to their underlying data) before deployment.
- Around line 203-206: The current pagination computes fetchCount = page * limit
and sliceStart = (page - 1) * limit which causes the DB to fetch all prior pages
for each request; change to cursor-based pagination (use createdAt as the
cursor) so each call only retrieves up to limit items. Specifically, replace the
fetchCount/sliceStart approach in the controller with logic that accepts a
cursor (e.g., req.query.cursor or lastCreatedAt) and queries each collection
with a filter like { createdAt: { $lt: cursor } } (or $gt for ascending), sorted
by createdAt and limited by limit, removing the need to multiply page*limit;
ensure the API returns the next cursor (the last returned createdAt) and update
any references to fetchCount/sliceStart accordingly (look for variables
fetchCount, sliceStart, page, limit).

In `@backend/src/models/repost.model.js`:
- Around line 32-50: The partialFilterExpression for the two repostSchema.index
calls should use $type: 'objectId' instead of $exists to match like.model.js and
be more explicit: update the partialFilterExpression objects in the
repostSchema.index invocations (the ones targeting {repostedBy:1, video:1} and
{repostedBy:1, tweet:1}) to check video and tweet with { $type: 'objectId' }
(and keep unique: true and the same keys) so the indexes only apply when those
fields are actual ObjectId values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4a50b189-8be2-402e-89c0-b7c539bc4103

📥 Commits

Reviewing files that changed from the base of the PR and between ad7e722 and e6fd79f.

⛔ Files ignored due to path filters (1)
  • backend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • backend/src/controllers/comment.controller.js
  • backend/src/controllers/like.controller.js
  • backend/src/controllers/subscription.controller.js
  • backend/src/models/like.model.js
  • backend/src/models/repost.model.js
  • backend/src/routes/comment.route.js
  • backend/src/routes/like.route.js
  • backend/src/routes/subscription.route.js
  • frontend/src/api/index.js
  • frontend/src/pages/videoPlayer.jsx

sender: req.user._id,
type: 'LIKE',
message: `${req.user.username} liked your comment`,
url: `/watch/${comment.video?._id || ''}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Notification URL may be malformed if video was deleted.

If the commented video has been deleted, comment.video will be null after population, resulting in a notification URL of /watch/ which leads nowhere.

Consider adding a fallback or skipping the notification when the video no longer exists:

🛡️ Suggested defensive check
-          url: `/watch/${comment.video?._id || ''}`,
+          url: comment.video?._id ? `/watch/${comment.video._id}` : undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/controllers/like.controller.js` at line 138, The notification URL
construction uses `url: \`/watch/${comment.video?._id || ''}\`` which produces
`/watch/` when `comment.video` is null; modify the logic in like.controller.js
to first check that `comment.video` and `comment.video._id` exist and either
skip creating/sending the notification when the video no longer exists or
populate `url` with a safe fallback (e.g., null/empty/overview path) and ensure
downstream code handles that case; update the block that builds the notification
payload so it only sets the `/watch/...` URL when `comment.video._id` is
present.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant