diff --git a/ghost/core/core/server/models/comment.js b/ghost/core/core/server/models/comment.js index f836b402bf..2c09c32a74 100644 --- a/ghost/core/core/server/models/comment.js +++ b/ghost/core/core/server/models/comment.js @@ -193,7 +193,7 @@ const Comment = ghostBookshelf.Model.extend({ // Relations 'member', 'count.replies', 'count.likes', 'count.liked', // Replies (limited to 3) - 'replies', 'replies.member', 'replies.count.likes', 'replies.count.liked' + 'replies', 'replies.member' , 'replies.count.likes', 'replies.count.liked' ]; } } @@ -202,24 +202,19 @@ const Comment = ghostBookshelf.Model.extend({ return options; }, - async commentCount(options) { - const query = this.forge().query(); - - if (options.postId) { - query.where('post_id', options.postId); - } - - if (options.status) { - query.where('status', options.status); - } - - return query.count('id as count').then((result) => { - return Number(result[0].count) || 0; - }).catch((err) => { - throw new errors.InternalServerError({ - err: err - }); - }); + async findMostLikedComment(options = {}) { + let query = ghostBookshelf.knex('comments') + .select('comments.*') + .count('comment_likes.id as count__likes') // Counting likes for sorting + .leftJoin('comment_likes', 'comments.id', 'comment_likes.comment_id') + .groupBy('comments.id') // Group by comment ID to aggregate likes count + .orderBy('count__likes', 'desc') // Order by likes in descending order (most likes first) + .limit(1); // Limit to just 1 result + // Execute the query and get the result + const result = await query.first(); // Fetch the single top comment + const id = result && result.id; + // Fetch the comment model by ID + return this.findOne({id}, options); }, async findPage(options) { @@ -238,6 +233,16 @@ const Comment = ghostBookshelf.Model.extend({ await model.load(relationsToLoadIndividually, _.omit(options, 'withRelated')); } + // if options.order === 'best', we findMostLikedComment + // then we remove it from the result set and add it as the first element + if (options.order === 'best' && options.page === '1') { + const mostLikedComment = await this.findMostLikedComment(options); + if (mostLikedComment) { + result.data = result.data.filter(comment => comment.id !== mostLikedComment.id); + result.data.unshift(mostLikedComment); + } + } + return result; }, @@ -284,8 +289,10 @@ const Comment = ghostBookshelf.Model.extend({ */ permittedOptions: function permittedOptions(methodName) { let options = ghostBookshelf.Model.permittedOptions.call(this, methodName); + // The comment model additionally supports having a parentId option options.push('parentId'); + return options; } }); diff --git a/ghost/core/core/server/services/comments/CommentsController.js b/ghost/core/core/server/services/comments/CommentsController.js index a2da700a8d..4a5a4828f0 100644 --- a/ghost/core/core/server/services/comments/CommentsController.js +++ b/ghost/core/core/server/services/comments/CommentsController.js @@ -51,11 +51,6 @@ module.exports = class CommentsController { frame.options.filter = `post_id:${frame.options.post_id}`; } } - - if (frame.options.order === 'best') { - return this.service.getBestComments(frame.options); - } - return this.service.getComments(frame.options); } diff --git a/ghost/core/core/server/services/comments/CommentsService.js b/ghost/core/core/server/services/comments/CommentsService.js index bc6c515bdf..b9049fb6d1 100644 --- a/ghost/core/core/server/services/comments/CommentsService.js +++ b/ghost/core/core/server/services/comments/CommentsService.js @@ -173,60 +173,6 @@ class CommentsService { return page; } - async getBestComments(options) { - this.checkEnabled(); - const postId = options.post_id; - - const allOrderedComments = await this.models.Comment.query() - .where('comments.post_id', postId) // Filter by postId - .select('comments.id') - .count('comment_likes.id as count__likes') - .leftJoin('comment_likes', 'comments.id', 'comment_likes.comment_id') - .groupBy('comments.id') - .orderByRaw(` - count__likes DESC, - comments.created_at DESC - `); - - const totalComments = allOrderedComments.length; - - if (totalComments === 0) { - const page = await this.models.Comment.findPage({...options, parentId: null}); - - return page; - } - - const limit = Number(options.limit) || 15; - const currentPage = Number(options.page) || 1; - - const orderedIds = allOrderedComments - .slice((options.page - 1) * limit, currentPage * limit) - .map(comment => comment.id); - - const findPageOptions = { - ...options, - filter: `id:[${orderedIds.join(',')}]`, - withRelated: options.withRelated - }; - - const page = await this.models.Comment.findPage(findPageOptions); - - page.data = orderedIds - .map(id => page.data.find(comment => comment && comment.id === id)) - .filter(comment => comment !== undefined); - - page.meta.pagination = { - page: currentPage, - limit: limit, - pages: Math.ceil(totalComments / limit), - total: totalComments, - next: currentPage < Math.ceil(totalComments / limit) ? currentPage + 1 : null, - prev: currentPage > 1 ? currentPage - 1 : null - }; - - return page; - } - /** * @param {string} id - The ID of the Comment to get replies from * @param {any} options diff --git a/ghost/core/test/e2e-api/members-comments/comments.test.js b/ghost/core/test/e2e-api/members-comments/comments.test.js index dac54520f1..4c3c54707b 100644 --- a/ghost/core/test/e2e-api/members-comments/comments.test.js +++ b/ghost/core/test/e2e-api/members-comments/comments.test.js @@ -7,7 +7,6 @@ const moment = require('moment-timezone'); const settingsCache = require('../../../core/shared/settings-cache'); const sinon = require('sinon'); const DomainEvents = require('@tryghost/domain-events'); -const {forEach} = require('lodash'); let membersAgent, membersAgent2, postId, postAuthorEmail, postTitle; @@ -27,7 +26,6 @@ const dbFns = { * @typedef {Object} AddCommentReplyData * @property {string} member_id * @property {string} [html='This is a reply'] - * @property {date} [created_at] */ /** * @typedef {AddCommentData & {replies: AddCommentReplyData[]}} AddCommentWithRepliesData @@ -42,8 +40,7 @@ const dbFns = { post_id: data.post_id || postId, member_id: data.member_id, parent_id: data.parent_id, - html: data.html || '

This is a comment

', - created_at: data.created_at + html: data.html || '

This is a comment

' }); }, /** @@ -514,124 +511,20 @@ describe('Comments API', function () { }); it('can show most liked comment first when order param = best', async function () { - // await setupBrowseCommentsData(); - // add another comment - await dbFns.addComment({ - html: 'This is the newest comment', - member_id: fixtureManager.get('members', 2).id, - created_at: new Date('2024-08-18') - }); - - const secondBest = await dbFns.addComment({ - member_id: fixtureManager.get('members', 0).id, - html: 'This will be the second best comment', - created_at: new Date('2022-01-01') - }); - - await dbFns.addComment({ - member_id: fixtureManager.get('members', 1).id, - created_at: new Date('2023-01-01') - }); - - const bestComment = await dbFns.addComment({ - member_id: fixtureManager.get('members', 2).id, - html: 'This will be the best comment', - created_at: new Date('2021-01-01') - }); - - const oldestComment = await dbFns.addComment({ - member_id: fixtureManager.get('members', 1).id, - html: 'ancient comment', - created_at: new Date('2019-01-01') - }); - - await dbFns.addLike({ - comment_id: secondBest.id, - member_id: loggedInMember.id - }); - - await dbFns.addLike({ - comment_id: bestComment.id, - member_id: loggedInMember.id - }); - - await dbFns.addLike({ - comment_id: bestComment.id, - member_id: fixtureManager.get('members', 0).id - }); - - await dbFns.addLike({ - comment_id: bestComment.id, - member_id: fixtureManager.get('members', 1).id - }); - - const data2 = await membersAgent - .get(`/api/comments/post/${postId}/?page=1&order=best`) - .expectStatus(200); - - should(data2.body.comments[0].id).eql(bestComment.id); - - // check oldest comment - should(data2.body.comments[4].id).eql(oldestComment.id); - }); - - it('checks that pagination is working when order param = best', async function () { - // create 20 comments - const postId2 = fixtureManager.get('posts', 1).id; - forEach(new Array(12).fill(0), async (item, index) => { - await dbFns.addComment({ - member_id: fixtureManager.get('members', 1).id, - html: `This is comment ${index}`, - created_at: new Date(`2021-01-${index + 1}`) - }); - }); - - const comment = await dbFns.addComment({ - member_id: fixtureManager.get('members', 0).id, - html: 'This is the best comment', - created_at: new Date('2021-01-10') - }); - - await dbFns.addLike({ - comment_id: comment.id, - member_id: loggedInMember.id - }); - - await dbFns.addLike({ - comment_id: comment.id, - member_id: fixtureManager.get('members', 1).id - }); - + await setupBrowseCommentsData(); const data = await membersAgent - .get(`/api/comments/post/${postId}/?limit=5&page=1&order=best`) - .expectStatus(200); + .get(`/api/comments/post/${postId}`); - should(data.body.comments.length).eql(5); - should(data.body.meta.pagination.total).eql(13); - should(data.body.meta.pagination.pages).eql(3); - should(data.body.meta.pagination.next).eql(2); - should(data.body.meta.pagination.prev).eql(null); - should(data.body.meta.pagination.limit).eql(5); - should(data.body.comments[0].id).eql(comment.id); - - const data2 = await membersAgent - .get(`/api/comments/post/${postId}/?limit=5&page=2&order=best`) - .expectStatus(200); - - should(data2.body.meta.pagination.next).eql(3); - should(data2.body.meta.pagination.prev).eql(1); - - // ensure data2 does not contain any of the comments from data - const ids = data.body.comments.map(com => com.id); - data2.body.comments.forEach((com) => { - should(ids.includes(com.id)).eql(false); + await dbFns.addLike({ + comment_id: data.body.comments[1].id, + member_id: loggedInMember.id }); - const data3 = await membersAgent - .get(`/api/comments/post/${postId2}/?limit=5&page=1&order=best`) + const data2 = await membersAgent + .get(`/api/comments/post/${postId}/?order=best`) .expectStatus(200); - should(data3.body.comments.length).eql(0); + should(data2.body.comments[0].id).eql(data.body.comments[1].id); }); it('does not most liked comment first when order param and keeps normal order', async function () {