From e02c67dd3a9f33cab0e416542fb35a5a3ba75301 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Thu, 2 Feb 2023 13:25:09 +0800 Subject: [PATCH] Mentions ordering (#16215) closes https://github.com/TryGhost/Team/issues/2498 - can now order mentions via the API via params eg `api/admin/mentions/?order=created_at%20desc`. --- .../services/mentions/MentionController.js | 8 ++ .../lib/InMemoryMentionRepository.js | 11 ++- ghost/webmentions/lib/MentionsAPI.js | 8 +- ghost/webmentions/test/MentionsAPI.test.js | 84 ++++++++++++++++++- 4 files changed, 107 insertions(+), 4 deletions(-) diff --git a/ghost/core/core/server/services/mentions/MentionController.js b/ghost/core/core/server/services/mentions/MentionController.js index 2fec519021..ad02b880bb 100644 --- a/ghost/core/core/server/services/mentions/MentionController.js +++ b/ghost/core/core/server/services/mentions/MentionController.js @@ -51,8 +51,16 @@ module.exports = class MentionController { page = 1; } + let order; + if (frame.options.order && frame.options.order === 'created_at desc') { + order = 'created_at desc'; + } else { + order = 'created_at asc'; + } + const results = await this.#api.listMentions({ filter: frame.options.filter, + order, limit, page }); diff --git a/ghost/webmentions/lib/InMemoryMentionRepository.js b/ghost/webmentions/lib/InMemoryMentionRepository.js index d87e0b4535..b9551b2a76 100644 --- a/ghost/webmentions/lib/InMemoryMentionRepository.js +++ b/ghost/webmentions/lib/InMemoryMentionRepository.js @@ -62,16 +62,25 @@ module.exports = class InMemoryMentionRepository { /** * @param {object} options * @param {string} [options.filter] + * @param {string} [options.order] * @param {number | null} options.page * @param {number | 'all'} options.limit * @returns {Promise>} */ async getPage(options) { const filter = nql(options.filter || '', {}); - const results = this.#store.slice().filter((item) => { + const data = this.#store.slice(); + + const results = data.slice().filter((item) => { return filter.queryJSON(this.toPrimitive(item)); }); + if (options.order === 'created_at desc') { + results.sort((a, b) => { + return Number(b.timestamp) - Number(a.timestamp); + }); + } + if (options.limit === 'all') { return { data: results, diff --git a/ghost/webmentions/lib/MentionsAPI.js b/ghost/webmentions/lib/MentionsAPI.js index cedbff8062..9f52cd2d29 100644 --- a/ghost/webmentions/lib/MentionsAPI.js +++ b/ghost/webmentions/lib/MentionsAPI.js @@ -18,6 +18,7 @@ const Mention = require('./Mention'); /** * @typedef {object} PaginatedOptions * @prop {string} [filter] A valid NQL string + * @prop {string} [order] * @prop {number} page * @prop {number} limit */ @@ -25,6 +26,7 @@ const Mention = require('./Mention'); /** * @typedef {object} NonPaginatedOptions * @prop {string} [filter] A valid NQL string + * @prop {string} [order] * @prop {'all'} limit */ @@ -105,13 +107,15 @@ module.exports = class MentionsAPI { if (options.limit === 'all') { pageOptions = { filter: options.filter, - limit: options.limit + limit: options.limit, + order: options.order }; } else { pageOptions = { filter: options.filter, limit: options.limit, - page: options.page + page: options.page, + order: options.order }; } diff --git a/ghost/webmentions/test/MentionsAPI.test.js b/ghost/webmentions/test/MentionsAPI.test.js index 294cf75ace..2417dedac4 100644 --- a/ghost/webmentions/test/MentionsAPI.test.js +++ b/ghost/webmentions/test/MentionsAPI.test.js @@ -3,6 +3,7 @@ const ObjectID = require('bson-objectid'); const Mention = require('../lib/Mention'); const MentionsAPI = require('../lib/MentionsAPI'); const InMemoryMentionRepository = require('../lib/InMemoryMentionRepository'); +const sinon = require('sinon'); const mockRoutingService = { async pageExists() { @@ -30,7 +31,17 @@ const mockWebmentionMetadata = { } }; +function addMinutes(date, minutes) { + date.setMinutes(date.getMinutes() + minutes); + + return date; +} + describe('MentionsAPI', function () { + beforeEach(function () { + sinon.restore(); + }); + it('Can list paginated mentions', async function () { const repository = new InMemoryMentionRepository(); const api = new MentionsAPI({ @@ -94,7 +105,6 @@ describe('MentionsAPI', function () { target: new URL('https://target.com'), payload: {} }); - const mentionTwo = await api.processWebmention({ source: new URL('https://source.com'), target: new URL('https://target.com'), @@ -113,6 +123,78 @@ describe('MentionsAPI', function () { assert(page.data[0].id === mentionTwo.id); }); + it('Can list mentions in descending order', async function () { + const repository = new InMemoryMentionRepository(); + const api = new MentionsAPI({ + repository, + routingService: mockRoutingService, + resourceService: mockResourceService, + webmentionMetadata: mockWebmentionMetadata + }); + + const mentionOne = await api.processWebmention({ + source: new URL('https://source.com'), + target: new URL('https://target.com'), + payload: {} + }); + + sinon.useFakeTimers(addMinutes(new Date(), 10).getTime()); + + const mentionTwo = await api.processWebmention({ + source: new URL('https://source2.com'), + target: new URL('https://target.com'), + payload: {} + }); + + assert(mentionOne instanceof Mention); + assert(mentionTwo instanceof Mention); + + const page = await api.listMentions({ + limit: 'all', + order: 'created_at desc' + }); + + assert(page.meta.pagination.total === 2); + assert(page.data[0].id === mentionTwo.id, 'First mention should be the second one in descending order'); + assert(page.data[1].id === mentionOne.id, 'Second mention should be the first one in descending order'); + }); + + it('Can list mentions in ascending order', async function () { + const repository = new InMemoryMentionRepository(); + const api = new MentionsAPI({ + repository, + routingService: mockRoutingService, + resourceService: mockResourceService, + webmentionMetadata: mockWebmentionMetadata + }); + + const mentionOne = await api.processWebmention({ + source: new URL('https://source.com'), + target: new URL('https://target.com'), + payload: {} + }); + + sinon.useFakeTimers(addMinutes(new Date(), 10).getTime()); + + const mentionTwo = await api.processWebmention({ + source: new URL('https://source2.com'), + target: new URL('https://target.com'), + payload: {} + }); + + assert(mentionOne instanceof Mention); + assert(mentionTwo instanceof Mention); + + const page = await api.listMentions({ + limit: 'all', + order: 'created_at asc' + }); + + assert(page.meta.pagination.total === 2); + assert(page.data[0].id === mentionOne.id, 'First mention should be the first one in ascending order'); + assert(page.data[1].id === mentionTwo.id, 'Second mention should be the second one in ascending order'); + }); + it('Can handle updating mentions', async function () { const repository = new InMemoryMentionRepository(); const api = new MentionsAPI({