From 82a3133ace9b034f23da2db49d082dab333a34f4 Mon Sep 17 00:00:00 2001 From: Simon Backx Date: Wed, 10 Aug 2022 16:12:35 +0200 Subject: [PATCH] Added replies/likes count and limited replies to comments (#15206) refs https://github.com/TryGhost/Team/issues/1723 - Added count.replies to comments - Added replies endpoint - Limited returned replies to 3. - Replaced likes_count with count.likes in comments - Instead of fetching all the likes of a comment to determine the total count, we'll now use count.likes - Instead of fetching all the likes of a comment to determine whether a member liked a comment, we'll now use count.liked (which returns the amount of likes of the current member, being 0 or 1). This is mapped to `liked` to make it more natural to work with. The `members.test.snap` file changed because we no longer include `liked: false` if we didn't fetch the liked relation. And in the comments events of the activity feed the liked property is therefore removed. These changes requires an update to the `bookshelf-include-count` plugin: - Updated to also work for nested relations - This moves the count queries from the `bookshelf-include-count` plugin to the `countRelations` method of each model. - Updated to keep the counts after saving a model (crud.edit didn't return the counts before) --- ghost/adapter-manager/package.json | 2 +- ghost/bootstrap-socket/package.json | 2 +- .../server/api/endpoints/comments-members.js | 24 +- .../utils/serializers/input/comments.js | 18 + .../utils/serializers/input/index.js | 4 + .../serializers/output/mappers/comments.js | 24 +- ghost/core/core/server/models/comment.js | 91 +- ghost/core/core/server/models/label.js | 14 + ghost/core/core/server/models/newsletter.js | 21 + ghost/core/core/server/models/tag.js | 20 + ghost/core/core/server/models/user.js | 20 + .../server/services/comments/controller.js | 9 + .../core/server/services/comments/service.js | 19 +- ghost/core/core/server/web/comments/routes.js | 1 + ghost/core/package.json | 36 +- .../admin/__snapshots__/members.test.js.snap | 2 +- .../__snapshots__/comments.test.js.snap | 846 +++++++++++++++++- .../e2e-api/members-comments/comments.test.js | 188 +++- .../package.json | 6 +- .../package.json | 2 +- ghost/email-analytics-service/package.json | 2 +- ghost/job-manager/package.json | 2 +- ghost/member-analytics-service/package.json | 4 +- ghost/members-api/package.json | 8 +- ghost/members-importer/package.json | 4 +- ghost/members-ssr/package.json | 4 +- ghost/minifier/package.json | 6 +- ghost/mw-error-handler/package.json | 6 +- ghost/package-json/package.json | 4 +- ghost/session-service/package.json | 2 +- ghost/settings-path-manager/package.json | 4 +- ghost/stripe/package.json | 6 +- ghost/update-check-service/package.json | 8 +- package.json | 2 +- yarn.lock | 298 +++--- 35 files changed, 1435 insertions(+), 274 deletions(-) create mode 100644 ghost/core/core/server/api/endpoints/utils/serializers/input/comments.js diff --git a/ghost/adapter-manager/package.json b/ghost/adapter-manager/package.json index e1fdcd88f0..404c94c476 100644 --- a/ghost/adapter-manager/package.json +++ b/ghost/adapter-manager/package.json @@ -21,6 +21,6 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/errors": "1.2.14" + "@tryghost/errors": "1.2.15" } } diff --git a/ghost/bootstrap-socket/package.json b/ghost/bootstrap-socket/package.json index 37e29c6def..e5880777ef 100644 --- a/ghost/bootstrap-socket/package.json +++ b/ghost/bootstrap-socket/package.json @@ -21,6 +21,6 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/logging": "2.2.3" + "@tryghost/logging": "2.2.4" } } diff --git a/ghost/core/core/server/api/endpoints/comments-members.js b/ghost/core/core/server/api/endpoints/comments-members.js index 3caa843275..582ef3a8eb 100644 --- a/ghost/core/core/server/api/endpoints/comments-members.js +++ b/ghost/core/core/server/api/endpoints/comments-members.js @@ -3,7 +3,7 @@ const tpl = require('@tryghost/tpl'); const errors = require('@tryghost/errors'); const models = require('../../models'); const commentsService = require('../../services/comments'); -const ALLOWED_INCLUDES = ['post', 'member', 'likes', 'replies', 'parent']; +const ALLOWED_INCLUDES = ['member', 'replies', 'replies.member', 'replies.count.likes', 'replies.liked', 'count.replies', 'count.likes', 'liked', 'post', 'parent']; const UNSAFE_ATTRS = ['status']; const messages = { @@ -37,6 +37,28 @@ module.exports = { } }, + replies: { + options: [ + 'include', + 'page', + 'limit', + 'fields', + 'filter', + 'order', + 'debug', + 'id' + ], + validation: { + options: { + include: ALLOWED_INCLUDES + } + }, + permissions: 'browse', + query(frame) { + return commentsService.controller.replies(frame); + } + }, + read: { options: [ 'include' diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/input/comments.js b/ghost/core/core/server/api/endpoints/utils/serializers/input/comments.js new file mode 100644 index 0000000000..ed60b58901 --- /dev/null +++ b/ghost/core/core/server/api/endpoints/utils/serializers/input/comments.js @@ -0,0 +1,18 @@ +module.exports = { + all(_apiConfig, frame) { + if (!frame.options.withRelated || frame.options.withRelated.length === 0) { + return; + } + + // Map the 'liked' relation to 'count.liked' + frame.options.withRelated = frame.options.withRelated.map((relation) => { + if (relation === 'liked') { + return 'count.liked'; + } + if (relation === 'replies.liked') { + return 'replies.count.liked'; + } + return relation; + }); + } +}; diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/input/index.js b/ghost/core/core/server/api/endpoints/utils/serializers/input/index.js index 5a47526ac0..adc4af5cc7 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/input/index.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/input/index.js @@ -45,5 +45,9 @@ module.exports = { get webhooks() { return require('./webhooks'); + }, + + get comments() { + return require('./comments'); } }; diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js index fd10b1eb3d..343d64cf30 100644 --- a/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +++ b/ghost/core/core/server/api/endpoints/utils/serializers/output/mappers/comments.js @@ -25,6 +25,11 @@ const postFields = [ 'url' ]; +const countFields = [ + 'replies', + 'likes' +]; + const commentMapper = (model, frame) => { const jsonModel = model.toJSON ? model.toJSON(frame.options) : model; @@ -36,12 +41,6 @@ const commentMapper = (model, frame) => { response.member = null; } - if (jsonModel.likes) { - response.likes_count = jsonModel.likes.length; - } else { - response.likes_count = 0; - } - if (jsonModel.replies) { response.replies = jsonModel.replies.map(reply => commentMapper(reply, frame)); } @@ -56,11 +55,12 @@ const commentMapper = (model, frame) => { response.post = _.pick(jsonModel.post, postFields); } - // todo - response.liked = false; - if (jsonModel.likes && frame.original.context.member && frame.original.context.member.id) { - const id = frame.original.context.member.id; - response.liked = !!jsonModel.likes.find(l => l.member_id === id); + if (jsonModel.count && jsonModel.count.liked !== undefined) { + response.liked = jsonModel.count.liked > 0; + } + + if (jsonModel.count) { + response.count = _.pick(jsonModel.count, countFields); } if (utils.isMembersAPI(frame)) { @@ -68,7 +68,7 @@ const commentMapper = (model, frame) => { response.html = null; } } - + return response; }; diff --git a/ghost/core/core/server/models/comment.js b/ghost/core/core/server/models/comment.js index 5febb806ce..d4a8655b3d 100644 --- a/ghost/core/core/server/models/comment.js +++ b/ghost/core/core/server/models/comment.js @@ -8,8 +8,7 @@ const messages = { emptyComment: 'The body of a comment cannot be empty', commentNotFound: 'Comment could not be found', notYourCommentToEdit: 'You may only edit your own comments', - notYourCommentToDestroy: 'You may only delete your own comments', - cannotEditDeletedComment: 'You may only edit published comments' + notYourCommentToDestroy: 'You may only delete your own comments' }; /** @@ -52,7 +51,9 @@ const Comment = ghostBookshelf.Model.extend({ }, replies() { - return this.hasMany('Comment', 'parent_id'); + return this.hasMany('Comment', 'parent_id', 'id') + .query('orderBy', 'id', 'ASC') + .query('limit', 3); }, emitChange: function emitChange(event, options) { @@ -63,12 +64,6 @@ const Comment = ghostBookshelf.Model.extend({ onSaving() { ghostBookshelf.Model.prototype.onSaving.apply(this, arguments); - if (this.hasChanged('html') && this.get('status') !== 'published') { - throw new ValidationError({ - message: tpl(messages.cannotEditDeletedComment) - }); - } - if (this.hasChanged('html')) { const sanitizeHtml = require('sanitize-html'); @@ -105,13 +100,16 @@ const Comment = ghostBookshelf.Model.extend({ }, enforcedFilters: function enforcedFilters(options) { - if (options.context && options.context.user) { - return null; + // Convenicence option to merge all filters with parent_id:null filter + if (options.parentId !== undefined) { + if (options.parentId === null) { + return 'parent_id:null'; + } + return 'parent_id:' + options.parentId; } - return 'parent_id:null'; + return null; } - }, { destroy: function destroy(unfilteredOptions) { let options = this.filterOptions(unfilteredOptions, 'destroy', {extraAllowedProperties: ['id']}); @@ -181,12 +179,75 @@ const Comment = ghostBookshelf.Model.extend({ */ defaultRelations: function defaultRelations(methodName, options) { // @todo: the default relations are not working for 'add' when we add it below - if (['findAll', 'findPage', 'edit', 'findOne'].indexOf(methodName) !== -1) { + if (['findAll', 'findPage', 'edit', 'findOne', 'destroy'].indexOf(methodName) !== -1) { if (!options.withRelated || options.withRelated.length === 0) { - options.withRelated = ['member', 'likes', 'replies', 'replies.member', 'replies.likes']; + if (options.parentId) { + // Do not include replies for replies + options.withRelated = [ + // Relations + 'member', 'count.likes', 'count.liked' + ]; + } else { + options.withRelated = [ + // Relations + 'member', 'count.replies', 'count.likes', 'count.liked', + // Replies (limited to 3) + 'replies', 'replies.member' , 'replies.count.likes', 'replies.count.liked' + ]; + } } } + return options; + }, + + countRelations() { + return { + replies(modelOrCollection) { + modelOrCollection.query('columns', 'comments.*', (qb) => { + qb.count('replies.id') + .from('comments AS replies') + .whereRaw('replies.parent_id = comments.id') + .as('count__replies'); + }); + }, + likes(modelOrCollection) { + modelOrCollection.query('columns', 'comments.*', (qb) => { + qb.count('comment_likes.id') + .from('comment_likes') + .whereRaw('comment_likes.comment_id = comments.id') + .as('count__likes'); + }); + }, + liked(modelOrCollection, options) { + modelOrCollection.query('columns', 'comments.*', (qb) => { + if (options.context && options.context.member && options.context.member.id) { + qb.count('comment_likes.id') + .from('comment_likes') + .whereRaw('comment_likes.comment_id = comments.id') + .where('comment_likes.member_id', options.context.member.id) + .as('count__liked'); + return; + } + + // Return zero + qb.select(ghostBookshelf.knex.raw('0')).as('count__liked'); + }); + } + }; + }, + + /** + * Returns an array of keys permitted in a method's `options` hash, depending on the current method. + * @param {String} methodName The name of the method to check valid options for. + * @return {Array} Keys allowed in the `options` hash of the model's method. + */ + 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/models/label.js b/ghost/core/core/server/models/label.js index c6cde4316f..0e6c44c820 100644 --- a/ghost/core/core/server/models/label.js +++ b/ghost/core/core/server/models/label.js @@ -107,6 +107,20 @@ Label = ghostBookshelf.Model.extend({ return options; }, + countRelations() { + return { + members(modelOrCollection) { + modelOrCollection.query('columns', 'labels.*', (qb) => { + qb.count('members.id') + .from('members') + .leftOuterJoin('members_labels', 'members.id', 'members_labels.member_id') + .whereRaw('members_labels.label_id = labels.id') + .as('count__members'); + }); + } + }; + }, + destroy: function destroy(unfilteredOptions) { const options = this.filterOptions(unfilteredOptions, 'destroy', {extraAllowedProperties: ['id']}); options.withRelated = ['members']; diff --git a/ghost/core/core/server/models/newsletter.js b/ghost/core/core/server/models/newsletter.js index 6608d45f4a..d4d43979fa 100644 --- a/ghost/core/core/server/models/newsletter.js +++ b/ghost/core/core/server/models/newsletter.js @@ -126,6 +126,27 @@ const Newsletter = ghostBookshelf.Model.extend({ return options; }, + countRelations() { + return { + posts(modelOrCollection) { + modelOrCollection.query('columns', 'newsletters.*', (qb) => { + qb.count('posts.id') + .from('posts') + .whereRaw('posts.newsletter_id = newsletters.id') + .as('count__posts'); + }); + }, + members(modelOrCollection) { + modelOrCollection.query('columns', 'newsletters.*', (qb) => { + qb.count('members_newsletters.id') + .from('members_newsletters') + .whereRaw('members_newsletters.newsletter_id = newsletters.id') + .as('count__members'); + }); + } + }; + }, + orderDefaultRaw: function () { return 'sort_order ASC, created_at ASC, id ASC'; }, diff --git a/ghost/core/core/server/models/tag.js b/ghost/core/core/server/models/tag.js index 674c6380d0..97378b16ee 100644 --- a/ghost/core/core/server/models/tag.js +++ b/ghost/core/core/server/models/tag.js @@ -182,6 +182,26 @@ Tag = ghostBookshelf.Model.extend({ return options; }, + countRelations() { + return { + posts(modelOrCollection, options) { + modelOrCollection.query('columns', 'tags.*', (qb) => { + qb.count('posts.id') + .from('posts') + .leftOuterJoin('posts_tags', 'posts.id', 'posts_tags.post_id') + .whereRaw('posts_tags.tag_id = tags.id') + .as('count__posts'); + + if (options.context && options.context.public) { + // @TODO use the filter behavior for posts + qb.andWhere('posts.type', '=', 'post'); + qb.andWhere('posts.status', '=', 'published'); + } + }); + } + }; + }, + destroy: function destroy(unfilteredOptions) { const options = this.filterOptions(unfilteredOptions, 'destroy', {extraAllowedProperties: ['id']}); options.withRelated = ['posts']; diff --git a/ghost/core/core/server/models/user.js b/ghost/core/core/server/models/user.js index ef86e68ee2..c39659ea98 100644 --- a/ghost/core/core/server/models/user.js +++ b/ghost/core/core/server/models/user.js @@ -426,6 +426,26 @@ User = ghostBookshelf.Model.extend({ return permittedOptionsToReturn; }, + countRelations() { + return { + posts(modelOrCollection, options) { + modelOrCollection.query('columns', 'users.*', (qb) => { + qb.count('posts.id') + .from('posts') + .join('posts_authors', 'posts.id', 'posts_authors.post_id') + .whereRaw('posts_authors.author_id = users.id') + .as('count__posts'); + + if (options.context && options.context.public) { + // @TODO use the filter behavior for posts + qb.andWhere('posts.type', '=', 'post'); + qb.andWhere('posts.status', '=', 'published'); + } + }); + } + }; + }, + /** * ### Find One * diff --git a/ghost/core/core/server/services/comments/controller.js b/ghost/core/core/server/services/comments/controller.js index aa0d6b0286..30e14d2372 100644 --- a/ghost/core/core/server/services/comments/controller.js +++ b/ghost/core/core/server/services/comments/controller.js @@ -1,3 +1,5 @@ +const _ = require('lodash'); + /** * @typedef {import('../../api/shared/frame')} Frame */ @@ -26,6 +28,13 @@ module.exports = class CommentsController { return this.service.getComments(frame.options); } + /** + * @param {Frame} frame + */ + async replies(frame) { + return this.service.getReplies(frame.options.id, _.omit(frame.options, 'id')); + } + /** * @param {Frame} frame */ diff --git a/ghost/core/core/server/services/comments/service.js b/ghost/core/core/server/services/comments/service.js index 0ffabd8c9c..c8ef5c14ee 100644 --- a/ghost/core/core/server/services/comments/service.js +++ b/ghost/core/core/server/services/comments/service.js @@ -115,7 +115,18 @@ class CommentsService { */ async getComments(options) { this.checkEnabled(); - const page = await this.models.Comment.findPage(options); + const page = await this.models.Comment.findPage({...options, parentId: null}); + + return page; + } + + /** + * @param {string} id - The ID of the Comment to get replies from + * @param {any} options + */ + async getReplies(id, options) { + this.checkEnabled(); + const page = await this.models.Comment.findPage({...options, parentId: id}); return page; } @@ -181,7 +192,8 @@ class CommentsService { commentId: model.id })); - return model; + // Instead of returning the model, fetch it again, so we have all the relations properly fetched + return await this.models.Comment.findOne({id: model.id}, {...options, require: true}); } /** @@ -240,7 +252,8 @@ class CommentsService { commentId: model.id })); - return model; + // Instead of returning the model, fetch it again, so we have all the relations properly fetched + return await this.models.Comment.findOne({id: model.id}, {...options, require: true}); } /** diff --git a/ghost/core/core/server/web/comments/routes.js b/ghost/core/core/server/web/comments/routes.js index cffa639e62..f06ff79875 100644 --- a/ghost/core/core/server/web/comments/routes.js +++ b/ghost/core/core/server/web/comments/routes.js @@ -23,6 +23,7 @@ module.exports = function apiRoutes() { router.post('/:id/like', http(api.commentsMembers.like)); router.delete('/:id/like', http(api.commentsMembers.unlike)); + router.get('/:id/replies', http(api.commentsMembers.replies)); router.post('/:id/report', http(api.commentsMembers.report)); diff --git a/ghost/core/package.json b/ghost/core/package.json index e7e497b01c..004cafe8c3 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -56,19 +56,19 @@ "@tryghost/adapter-manager": "0.0.0", "@tryghost/admin-api-schema": "4.1.1", "@tryghost/api-version-compatibility-service": "0.0.0", - "@tryghost/bookshelf-plugins": "0.4.3", + "@tryghost/bookshelf-plugins": "0.5.0", "@tryghost/bootstrap-socket": "0.0.0", "@tryghost/color-utils": "0.1.20", "@tryghost/config-url-helpers": "1.0.3", "@tryghost/constants": "0.0.0", "@tryghost/custom-theme-settings-service": "0.0.0", - "@tryghost/database-info": "0.3.8", - "@tryghost/debug": "0.1.17", + "@tryghost/database-info": "0.3.9", + "@tryghost/debug": "0.1.18", "@tryghost/domain-events": "0.0.0", "@tryghost/email-analytics-provider-mailgun": "0.0.0", "@tryghost/email-analytics-service": "0.0.0", "@tryghost/email-content-generator": "0.0.0", - "@tryghost/errors": "1.2.14", + "@tryghost/errors": "1.2.15", "@tryghost/express-dynamic-redirects": "0.0.0", "@tryghost/helpers": "1.1.72", "@tryghost/html-to-plaintext": "0.0.0", @@ -80,7 +80,7 @@ "@tryghost/kg-markdown-html-renderer": "5.1.6", "@tryghost/kg-mobiledoc-html-renderer": "5.3.6", "@tryghost/limit-service": "1.2.3", - "@tryghost/logging": "2.2.3", + "@tryghost/logging": "2.2.4", "@tryghost/magic-link": "0.0.0", "@tryghost/member-events": "0.0.0", "@tryghost/members-api": "0.0.0", @@ -89,32 +89,32 @@ "@tryghost/members-offers": "0.0.0", "@tryghost/members-ssr": "0.0.0", "@tryghost/members-stripe-service": "0.0.0", - "@tryghost/metrics": "1.0.14", + "@tryghost/metrics": "1.0.15", "@tryghost/minifier": "0.0.0", "@tryghost/mw-api-version-mismatch": "0.0.0", "@tryghost/mw-error-handler": "0.0.0", "@tryghost/mw-session-from-token": "0.0.0", "@tryghost/mw-vhost": "0.0.0", - "@tryghost/nodemailer": "0.3.24", + "@tryghost/nodemailer": "0.3.25", "@tryghost/nql": "0.9.2", "@tryghost/package-json": "0.0.0", - "@tryghost/pretty-cli": "1.2.29", - "@tryghost/promise": "0.1.20", - "@tryghost/request": "0.1.28", - "@tryghost/root-utils": "0.3.15", + "@tryghost/pretty-cli": "1.2.30", + "@tryghost/promise": "0.1.21", + "@tryghost/request": "0.1.29", + "@tryghost/root-utils": "0.3.16", "@tryghost/security": "0.0.0", "@tryghost/session-service": "0.0.0", "@tryghost/settings-path-manager": "0.0.0", "@tryghost/social-urls": "0.1.33", "@tryghost/stats-service": "0.3.0", "@tryghost/string": "0.1.27", - "@tryghost/tpl": "0.1.17", + "@tryghost/tpl": "0.1.18", "@tryghost/update-check-service": "0.0.0", "@tryghost/url-utils": "4.0.3", - "@tryghost/validator": "0.1.26", + "@tryghost/validator": "0.1.27", "@tryghost/verification-trigger": "0.0.0", - "@tryghost/version": "0.1.15", - "@tryghost/zip": "1.1.27", + "@tryghost/version": "0.1.16", + "@tryghost/zip": "1.1.28", "amperize": "0.6.1", "analytics-node": "6.2.0", "bluebird": "3.7.2", @@ -189,8 +189,8 @@ }, "devDependencies": { "@playwright/test": "1.24.2", - "@tryghost/express-test": "0.11.1", - "@tryghost/webhook-mock-receiver": "0.1.1", + "@tryghost/express-test": "0.11.2", + "@tryghost/webhook-mock-receiver": "0.1.2", "@types/common-tags": "1.8.1", "c8": "7.12.0", "cli-progress": "3.11.2", @@ -215,7 +215,7 @@ "tmp": "0.2.1" }, "resolutions": { - "@tryghost/logging": "2.2.3", + "@tryghost/logging": "2.2.4", "moment": "2.24.0", "moment-timezone": "0.5.23" } diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap index abc72a5098..9c2e6d4835 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap @@ -3316,7 +3316,7 @@ exports[`Members API Returns comments in activity feed 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "3484", + "content-length": "3394", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", diff --git a/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap b/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap index bae94211f8..e0b2f5d2e9 100644 --- a/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap +++ b/ghost/core/test/e2e-api/members-comments/__snapshots__/comments.test.js.snap @@ -4,12 +4,15 @@ exports[`Comments API when authenticated Can browse all comments of a post 1: [b Object { "comments": Array [ Object { + "count": Object { + "likes": Any, + "replies": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "

First.

", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -19,12 +22,14 @@ Object { }, "replies": Array [ Object { + "count": Object { + "likes": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "

Really original

", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -38,12 +43,15 @@ Object { "status": "published", }, Object { + "count": Object { + "likes": Any, + "replies": 0, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "

This is a message

New line

", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -72,7 +80,7 @@ exports[`Comments API when authenticated Can browse all comments of a post 2: [h Object { "access-control-allow-origin": "*", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "1064", + "content-length": "1100", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Accept-Encoding", @@ -84,13 +92,23 @@ exports[`Comments API when authenticated Can comment on a post 1: [body] 1`] = ` Object { "comments": Array [ Object { + "count": Object { + "likes": Any, + "replies": 0, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "

This is a message

New line

", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, - "liked": false, - "likes_count": 0, - "member": null, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], "status": "published", }, ], @@ -101,7 +119,7 @@ exports[`Comments API when authenticated Can comment on a post 2: [headers] 1`] Object { "access-control-allow-origin": "*", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "226", + "content-length": "373", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -114,12 +132,15 @@ exports[`Comments API when authenticated Can edit a comment on a post 1: [body] Object { "comments": Array [ Object { + "count": Object { + "likes": Any, + "replies": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "html": "Updated comment", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -129,12 +150,14 @@ Object { }, "replies": Array [ Object { + "count": Object { + "likes": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "This is a reply", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -155,7 +178,7 @@ exports[`Comments API when authenticated Can edit a comment on a post 2: [header Object { "access-control-allow-origin": "*", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "646", + "content-length": "666", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Accept-Encoding", @@ -165,7 +188,7 @@ Object { exports[`Comments API when authenticated Can fetch counts 1: [body] 1`] = ` Object { - "618ba1ffbe2896088840a6df": 10, + "618ba1ffbe2896088840a6df": 13, "618ba1ffbe2896088840a6e1": 0, "618ba1ffbe2896088840a6e3": 0, } @@ -187,12 +210,15 @@ exports[`Comments API when authenticated Can like a comment 1: [body] 1`] = ` Object { "comments": Array [ Object { + "count": Object { + "likes": Any, + "replies": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "

This is a message

New line

", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -202,12 +228,14 @@ Object { }, "replies": Array [ Object { + "count": Object { + "likes": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "This is a reply", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -259,7 +287,7 @@ exports[`Comments API when authenticated Can like a comment 2: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "655", + "content-length": "675", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Accept-Encoding", @@ -280,12 +308,15 @@ exports[`Comments API when authenticated Can like a comment 4: [body] 1`] = ` Object { "comments": Array [ Object { + "count": Object { + "likes": Any, + "replies": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "

This is a message

New line

", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -295,12 +326,14 @@ Object { }, "replies": Array [ Object { + "count": Object { + "likes": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "This is a reply", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -321,7 +354,55 @@ exports[`Comments API when authenticated Can like a comment 5: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "654", + "content-length": "674", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when authenticated Can like a reply 1: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "x-powered-by": "Express", +} +`; + +exports[`Comments API when authenticated Can like a reply 2: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 0", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when authenticated Can like a reply 3: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "343", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Accept-Encoding", @@ -333,12 +414,15 @@ exports[`Comments API when authenticated Can not edit a comment as a member who Object { "comments": Array [ Object { + "count": Object { + "likes": Any, + "replies": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "html": "Illegal comment update", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -348,12 +432,14 @@ Object { }, "replies": Array [ Object { + "count": Object { + "likes": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "This is a reply", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -374,7 +460,7 @@ exports[`Comments API when authenticated Can not edit a comment as a member who Object { "access-control-allow-origin": "*", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "653", + "content-length": "673", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Accept-Encoding", @@ -491,12 +577,15 @@ exports[`Comments API when authenticated Can remove a like 2: [body] 1`] = ` Object { "comments": Array [ Object { + "count": Object { + "likes": Any, + "replies": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "

This is a message

New line

", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -506,12 +595,14 @@ Object { }, "replies": Array [ Object { + "count": Object { + "likes": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "This is a reply", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -532,7 +623,7 @@ exports[`Comments API when authenticated Can remove a like 3: [headers] 1`] = ` Object { "access-control-allow-origin": "*", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "655", + "content-length": "675", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Accept-Encoding", @@ -544,13 +635,23 @@ exports[`Comments API when authenticated Can reply to a comment 1: [body] 1`] = Object { "comments": Array [ Object { + "count": Object { + "likes": Any, + "replies": 0, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "This is a reply", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, - "liked": false, - "likes_count": 0, - "member": null, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], "status": "published", }, ], @@ -561,7 +662,7 @@ exports[`Comments API when authenticated Can reply to a comment 2: [headers] 1`] Object { "access-control-allow-origin": "*", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "195", + "content-length": "342", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -574,13 +675,23 @@ exports[`Comments API when authenticated Can reply to your own comment 1: [body] Object { "comments": Array [ Object { + "count": Object { + "likes": Any, + "replies": 0, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "This is a reply", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, - "liked": false, - "likes_count": 0, - "member": null, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], "status": "published", }, ], @@ -591,7 +702,7 @@ exports[`Comments API when authenticated Can reply to your own comment 2: [heade Object { "access-control-allow-origin": "*", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "195", + "content-length": "342", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -609,6 +720,190 @@ Object { } `; +exports[`Comments API when authenticated Can request second page of replies 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 1", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 2", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "meta": Object { + "pagination": Object { + "limit": 3, + "next": null, + "page": 2, + "pages": 2, + "prev": 1, + "total": 5, + }, + }, +} +`; + +exports[`Comments API when authenticated Can request second page of replies 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "708", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when authenticated Can return replies 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

Really original

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 0", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 1", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 2", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 5, + }, + }, +} +`; + +exports[`Comments API when authenticated Can return replies 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1629", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + exports[`Comments API when authenticated Cannot like a comment multiple times 1: [body] 1`] = ` Object { "errors": Array [ @@ -657,16 +952,19 @@ Object { } `; -exports[`Comments API when not authenticated but enabled Can browse all comments of a post 1: [body] 1`] = ` +exports[`Comments API when authenticated Limits returned replies to 3 1: [body] 1`] = ` Object { "comments": Array [ Object { + "count": Object { + "likes": Any, + "replies": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "

First.

", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -676,12 +974,488 @@ Object { }, "replies": Array [ Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

Really original

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when authenticated Limits returned replies to 3 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "956", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when authenticated Limits returned replies to 3 3: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 0", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when authenticated Limits returned replies to 3 4: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "344", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when authenticated Limits returned replies to 3 5: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 1", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when authenticated Limits returned replies to 3 6: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "344", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when authenticated Limits returned replies to 3 7: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 2", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when authenticated Limits returned replies to 3 8: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "344", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when authenticated Limits returned replies to 3 9: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

First.

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Mr Egg", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

Really original

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 0", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "status": "published", + }, + ], +} +`; + +exports[`Comments API when authenticated Limits returned replies to 3 10: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1261", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when authenticated can paginate replies 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 1", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 2", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "meta": Object { + "pagination": Object { + "limit": 3, + "next": null, + "page": 2, + "pages": 2, + "prev": 1, + "total": 5, + }, + }, +} +`; + +exports[`Comments API when authenticated can paginate replies 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "708", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when authenticated can return replies 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

Really original

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 0", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 1", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + Object { + "count": Object { + "likes": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "This is a reply 2", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "status": "published", + }, + ], + "meta": Object { + "pagination": Object { + "limit": 15, + "next": null, + "page": 1, + "pages": 1, + "prev": null, + "total": 5, + }, + }, +} +`; + +exports[`Comments API when authenticated can return replies 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "*", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "1630", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Comments API when not authenticated but enabled Can browse all comments of a post 1: [body] 1`] = ` +Object { + "comments": Array [ + Object { + "count": Object { + "likes": Any, + "replies": Any, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "edited_at": null, + "html": "

First.

", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "liked": Any, + "member": Object { + "avatar_image": null, + "bio": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Mr Egg", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "replies": Array [ + Object { + "count": Object { + "likes": Any, + }, "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, "edited_at": null, "html": "

Really original

", "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, "liked": Any, - "likes_count": Any, "member": Object { "avatar_image": null, "bio": null, @@ -712,7 +1486,7 @@ exports[`Comments API when not authenticated but enabled Can browse all comments Object { "access-control-allow-origin": "*", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "721", + "content-length": "741", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Accept-Encoding", 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 ecfaeca3b5..1c8f0efaed 100644 --- a/ghost/core/test/e2e-api/members-comments/comments.test.js +++ b/ghost/core/test/e2e-api/members-comments/comments.test.js @@ -9,11 +9,6 @@ const sinon = require('sinon'); let membersAgent, membersAgent2, member, postId, postTitle, commentId; -const commentMatcherNoMember = { - id: anyObjectId, - created_at: anyISODateTime -}; - const commentMatcher = { id: anyObjectId, created_at: anyISODateTime, @@ -21,21 +16,33 @@ const commentMatcher = { id: anyObjectId, uuid: anyUuid }, - likes_count: anyNumber, + count: { + likes: anyNumber + }, liked: anyBoolean }; -const commentMatcherWithReply = { - id: anyObjectId, - created_at: anyISODateTime, - member: { +/** + * @param {Object} [options] + * @param {number} [options.replies] + * @returns + */ +function commentMatcherWithReplies(options = {replies: 0}) { + return { id: anyObjectId, - uuid: anyUuid - }, - likes_count: anyNumber, - liked: anyBoolean, - replies: [commentMatcher] -}; + created_at: anyISODateTime, + member: { + id: anyObjectId, + uuid: anyUuid + }, + replies: new Array(options?.replies ?? 0).fill(commentMatcher), + count: { + likes: anyNumber, + replies: anyNumber + }, + liked: anyBoolean + }; +} async function sleep(ms) { return new Promise((resolve) => { @@ -89,7 +96,7 @@ describe('Comments API', function () { etag: anyEtag }) .matchBodySnapshot({ - comments: [commentMatcherWithReply] + comments: [commentMatcherWithReplies({replies: 1})] }); }); @@ -173,7 +180,7 @@ describe('Comments API', function () { location: anyLocationFor('comments') }) .matchBodySnapshot({ - comments: [commentMatcherNoMember] + comments: [commentMatcher] }); // Save for other tests commentId = body.comments[0].id; @@ -206,7 +213,7 @@ describe('Comments API', function () { etag: anyEtag }) .matchBodySnapshot({ - comments: [commentMatcherWithReply, commentMatcher] + comments: [commentMatcherWithReplies({replies: 1}), commentMatcher] }); }); @@ -229,7 +236,7 @@ describe('Comments API', function () { location: anyLocationFor('comments') }) .matchBodySnapshot({ - comments: [commentMatcherNoMember] + comments: [commentMatcher] }); // Check only the author got an email (because we are the author of this parent comment) @@ -267,7 +274,7 @@ describe('Comments API', function () { location: anyLocationFor('comments') }) .matchBodySnapshot({ - comments: [commentMatcherNoMember] + comments: [commentMatcher] }); mockManager.assert.sentEmailCount(2); @@ -292,6 +299,61 @@ describe('Comments API', function () { should.notEqual(member.get('last_commented_at').getTime(), date.getTime(), 'Should update `last_commented_at` property after posting a comment.'); }); + let testReplyId; + it('Limits returned replies to 3', async function () { + const parentId = fixtureManager.get('comments', 0).id; + + // Check initial status: two replies before test + await membersAgent + .get(`/api/comments/${parentId}/`) + .expectStatus(200) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + comments: [commentMatcherWithReplies({replies: 2})] + }) + .expect(({body}) => { + body.comments[0].count.replies.should.eql(2); + }); + + // Add some replies + for (let index = 0; index < 3; index++) { + const {body: reply} = await membersAgent + .post(`/api/comments/`) + .body({comments: [{ + post_id: postId, + parent_id: parentId, + html: 'This is a reply ' + index + }]}) + .expectStatus(201) + .matchHeaderSnapshot({ + etag: anyEtag, + location: anyLocationFor('comments') + }) + .matchBodySnapshot({ + comments: [commentMatcher] + }); + if (index === 0) { + testReplyId = reply.comments[0].id; + } + } + + // Check if we have count.replies = 4, and replies.length == 3 + await membersAgent + .get(`/api/comments/${parentId}/`) + .expectStatus(200) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + comments: [commentMatcherWithReplies({replies: 3})] + }) + .expect(({body}) => { + body.comments[0].count.replies.should.eql(5); + }); + }); + it('Can like a comment', async function () { // Check not liked await membersAgent @@ -301,10 +363,11 @@ describe('Comments API', function () { etag: anyEtag }) .matchBodySnapshot({ - comments: new Array(1).fill(commentMatcherWithReply) + comments: new Array(1).fill(commentMatcherWithReplies({replies: 1})) }) .expect(({body}) => { body.comments[0].liked.should.eql(false); + body.comments[0].count.likes.should.eql(0); }); // Create a temporary comment @@ -324,10 +387,11 @@ describe('Comments API', function () { etag: anyEtag }) .matchBodySnapshot({ - comments: new Array(1).fill(commentMatcherWithReply) + comments: new Array(1).fill(commentMatcherWithReplies({replies: 1})) }) .expect(({body}) => { body.comments[0].liked.should.eql(true); + body.comments[0].count.likes.should.eql(1); }); }); @@ -346,6 +410,77 @@ describe('Comments API', function () { }); }); + it('Can like a reply', async function () { + // Check initial status: two replies before test + await membersAgent + .post(`/api/comments/${testReplyId}/like/`) + .expectStatus(204) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .expectEmptyBody(); + + // Check liked + await membersAgent + .get(`/api/comments/${testReplyId}/`) + .expectStatus(200) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + comments: new Array(1).fill(commentMatcherWithReplies({replies: 0})) + }) + .expect(({body}) => { + body.comments[0].liked.should.eql(true); + body.comments[0].count.likes.should.eql(1); + }); + }); + + it('Can return replies', async function () { + const parentId = fixtureManager.get('comments', 0).id; + + // Check initial status: two replies before test + await membersAgent + .get(`/api/comments/${parentId}/replies/`) + .expectStatus(200) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + comments: new Array(5).fill(commentMatcher) + }) + .expect(({body}) => { + should(body.comments[0].count.replies).be.undefined(); + should(body.meta.pagination.total).eql(5); + should(body.meta.pagination.next).eql(null); + + // Check liked + likes working for replies too + should(body.comments[2].id).eql(testReplyId); + should(body.comments[2].count.likes).eql(1); + should(body.comments[2].liked).eql(true); + }); + }); + + it('Can request second page of replies', async function () { + const parentId = fixtureManager.get('comments', 0).id; + + // Check initial status: two replies before test + await membersAgent + .get(`/api/comments/${parentId}/replies/?page=2&limit=3`) + .expectStatus(200) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + comments: new Array(2).fill(commentMatcher) + }) + .expect(({body}) => { + should(body.comments[0].count.replies).be.undefined(); + should(body.meta.pagination.total).eql(5); + should(body.meta.pagination.next).eql(null); + }); + }); + it('Can remove a like', async function () { // Create a temporary comment await membersAgent @@ -364,10 +499,11 @@ describe('Comments API', function () { etag: anyEtag }) .matchBodySnapshot({ - comments: new Array(1).fill(commentMatcherWithReply) + comments: new Array(1).fill(commentMatcherWithReplies({replies: 1})) }) .expect(({body}) => { body.comments[0].liked.should.eql(false); + body.comments[0].count.likes.should.eql(0); }); }); @@ -428,7 +564,7 @@ describe('Comments API', function () { }) .matchBodySnapshot({ comments: [{ - ...commentMatcherWithReply, + ...commentMatcherWithReplies({replies: 1}), edited_at: anyISODateTime }] }); @@ -490,7 +626,7 @@ describe('Comments API', function () { }) .matchBodySnapshot({ comments: [{ - ...commentMatcherWithReply, + ...commentMatcherWithReplies({replies: 1}), edited_at: anyISODateTime }] }); diff --git a/ghost/custom-theme-settings-service/package.json b/ghost/custom-theme-settings-service/package.json index 016136aff5..75286f3b85 100644 --- a/ghost/custom-theme-settings-service/package.json +++ b/ghost/custom-theme-settings-service/package.json @@ -22,9 +22,9 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/debug": "0.1.17", - "@tryghost/errors": "1.2.14", - "@tryghost/tpl": "0.1.17", + "@tryghost/debug": "0.1.18", + "@tryghost/errors": "1.2.15", + "@tryghost/tpl": "0.1.18", "lodash": "4.17.21" } } diff --git a/ghost/email-analytics-provider-mailgun/package.json b/ghost/email-analytics-provider-mailgun/package.json index adae657fd9..51103324f3 100644 --- a/ghost/email-analytics-provider-mailgun/package.json +++ b/ghost/email-analytics-provider-mailgun/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@tryghost/email-analytics-service": "0.0.0", - "@tryghost/logging": "2.2.3", + "@tryghost/logging": "2.2.4", "mailgun-js": "0.22.0", "moment": "2.29.1" } diff --git a/ghost/email-analytics-service/package.json b/ghost/email-analytics-service/package.json index dee687e828..5d24c54ec6 100644 --- a/ghost/email-analytics-service/package.json +++ b/ghost/email-analytics-service/package.json @@ -20,7 +20,7 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/debug": "0.1.17", + "@tryghost/debug": "0.1.18", "lodash": "4.17.21" } } diff --git a/ghost/job-manager/package.json b/ghost/job-manager/package.json index bc18f498f1..b5e8f3273e 100644 --- a/ghost/job-manager/package.json +++ b/ghost/job-manager/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "@breejs/later": "4.1.0", - "@tryghost/logging": "2.2.3", + "@tryghost/logging": "2.2.4", "bree": "6.5.0", "cron-validate": "1.4.3", "fastq": "1.13.0", diff --git a/ghost/member-analytics-service/package.json b/ghost/member-analytics-service/package.json index 014c8049d5..6a9ab2423b 100644 --- a/ghost/member-analytics-service/package.json +++ b/ghost/member-analytics-service/package.json @@ -23,9 +23,9 @@ }, "dependencies": { "@tryghost/domain-events": "0.0.0", - "@tryghost/errors": "1.2.14", + "@tryghost/errors": "1.2.15", "@tryghost/member-events": "0.0.0", - "@tryghost/tpl": "0.1.17", + "@tryghost/tpl": "0.1.18", "bson-objectid": "2.0.3" } } diff --git a/ghost/members-api/package.json b/ghost/members-api/package.json index 1268e25115..b4fd090928 100644 --- a/ghost/members-api/package.json +++ b/ghost/members-api/package.json @@ -26,10 +26,10 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/debug": "0.1.17", + "@tryghost/debug": "0.1.18", "@tryghost/domain-events": "0.0.0", - "@tryghost/errors": "1.2.14", - "@tryghost/logging": "2.2.3", + "@tryghost/errors": "1.2.15", + "@tryghost/logging": "2.2.4", "@tryghost/magic-link": "0.0.0", "@tryghost/member-analytics-service": "0.0.0", "@tryghost/member-events": "0.0.0", @@ -37,7 +37,7 @@ "@tryghost/members-payments": "0.0.0", "@tryghost/members-stripe-service": "0.0.0", "@tryghost/nql": "0.9.2", - "@tryghost/tpl": "0.1.17", + "@tryghost/tpl": "0.1.18", "@types/jsonwebtoken": "8.5.8", "bluebird": "3.7.2", "body-parser": "1.20.0", diff --git a/ghost/members-importer/package.json b/ghost/members-importer/package.json index 06cd2d4943..2c3f2f3c5b 100644 --- a/ghost/members-importer/package.json +++ b/ghost/members-importer/package.json @@ -21,9 +21,9 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/errors": "1.2.14", + "@tryghost/errors": "1.2.15", "@tryghost/members-csv": "0.0.0", - "@tryghost/tpl": "0.1.17", + "@tryghost/tpl": "0.1.18", "moment-timezone": "0.5.23" } } diff --git a/ghost/members-ssr/package.json b/ghost/members-ssr/package.json index 0eeca7bd3c..224cd46ac6 100644 --- a/ghost/members-ssr/package.json +++ b/ghost/members-ssr/package.json @@ -23,8 +23,8 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/debug": "0.1.17", - "@tryghost/errors": "1.2.14", + "@tryghost/debug": "0.1.18", + "@tryghost/errors": "1.2.15", "bluebird": "3.7.2", "concat-stream": "2.0.0", "cookies": "0.8.0", diff --git a/ghost/minifier/package.json b/ghost/minifier/package.json index f70d9b14ef..fe51a5c7a9 100644 --- a/ghost/minifier/package.json +++ b/ghost/minifier/package.json @@ -21,9 +21,9 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/debug": "0.1.17", - "@tryghost/errors": "1.2.14", - "@tryghost/tpl": "0.1.17", + "@tryghost/debug": "0.1.18", + "@tryghost/errors": "1.2.15", + "@tryghost/tpl": "0.1.18", "csso": "5.0.4", "terser": "5.14.2", "tiny-glob": "0.2.9" diff --git a/ghost/mw-error-handler/package.json b/ghost/mw-error-handler/package.json index eb506d8df8..1b078178cc 100644 --- a/ghost/mw-error-handler/package.json +++ b/ghost/mw-error-handler/package.json @@ -21,9 +21,9 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/debug": "0.1.17", - "@tryghost/errors": "1.2.14", - "@tryghost/tpl": "0.1.17", + "@tryghost/debug": "0.1.18", + "@tryghost/errors": "1.2.15", + "@tryghost/tpl": "0.1.18", "lodash": "4.17.21", "semver": "7.3.7" } diff --git a/ghost/package-json/package.json b/ghost/package-json/package.json index 82b345c85c..ca2d2f75cb 100644 --- a/ghost/package-json/package.json +++ b/ghost/package-json/package.json @@ -22,8 +22,8 @@ "tmp": "0.2.1" }, "dependencies": { - "@tryghost/errors": "1.2.14", - "@tryghost/tpl": "0.1.17", + "@tryghost/errors": "1.2.15", + "@tryghost/tpl": "0.1.18", "bluebird": "3.7.2", "fs-extra": "10.1.0", "lodash": "4.17.21" diff --git a/ghost/session-service/package.json b/ghost/session-service/package.json index 7e4a965cff..bc40a0c209 100644 --- a/ghost/session-service/package.json +++ b/ghost/session-service/package.json @@ -22,6 +22,6 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/errors": "1.2.14" + "@tryghost/errors": "1.2.15" } } diff --git a/ghost/settings-path-manager/package.json b/ghost/settings-path-manager/package.json index c4b040068e..bdc885233c 100644 --- a/ghost/settings-path-manager/package.json +++ b/ghost/settings-path-manager/package.json @@ -21,8 +21,8 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/errors": "1.2.14", - "@tryghost/tpl": "0.1.17", + "@tryghost/errors": "1.2.15", + "@tryghost/tpl": "0.1.18", "date-fns": "2.29.1" } } diff --git a/ghost/stripe/package.json b/ghost/stripe/package.json index 2238cd2fbf..0dcdfe98cb 100644 --- a/ghost/stripe/package.json +++ b/ghost/stripe/package.json @@ -22,10 +22,10 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/debug": "0.1.17", + "@tryghost/debug": "0.1.18", "@tryghost/domain-events": "0.0.0", - "@tryghost/errors": "1.2.14", - "@tryghost/logging": "2.2.3", + "@tryghost/errors": "1.2.15", + "@tryghost/logging": "2.2.4", "@tryghost/member-events": "0.0.0", "leaky-bucket": "2.2.0", "lodash": "4.17.21", diff --git a/ghost/update-check-service/package.json b/ghost/update-check-service/package.json index 32e7c8a78f..078a1de218 100644 --- a/ghost/update-check-service/package.json +++ b/ghost/update-check-service/package.json @@ -21,10 +21,10 @@ "sinon": "14.0.0" }, "dependencies": { - "@tryghost/debug": "0.1.17", - "@tryghost/errors": "1.2.14", - "@tryghost/logging": "2.2.3", - "@tryghost/tpl": "0.1.17", + "@tryghost/debug": "0.1.18", + "@tryghost/errors": "1.2.15", + "@tryghost/logging": "2.2.4", + "@tryghost/tpl": "0.1.18", "bluebird": "3.7.2", "lodash": "4.17.21", "moment": "2.24.0" diff --git a/package.json b/package.json index 1bcda8255a..f06a7420e7 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "main:submodules": "git submodule sync && git submodule update && git submodule foreach \"git checkout main && git pull ${GHOST_UPSTREAM:-origin} main && yarn\"" }, "resolutions": { - "@tryghost/logging": "2.2.3", + "@tryghost/logging": "2.2.4", "moment": "2.24.0", "moment-timezone": "0.5.23" }, diff --git a/yarn.lock b/yarn.lock index 09d9dd0fac..2f6624a4f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3094,12 +3094,12 @@ ajv "^6.12.6" lodash "^4.17.11" -"@tryghost/bookshelf-collision@^0.1.24": - version "0.1.24" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-collision/-/bookshelf-collision-0.1.24.tgz#fe3f29e730fd9c97219d32ca24f34497830c342e" - integrity sha512-owfQrkXDlwk+HYpANaS3CpKxLwJZOJ6By0W/N1UA4Tp4zEmW05rnuG+yLZJQtRgfIDTUclIb2BTCzFs6HOiJug== +"@tryghost/bookshelf-collision@^0.1.25": + version "0.1.25" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-collision/-/bookshelf-collision-0.1.25.tgz#27ebb1eb617bcc018a84a9025c77928b124e9b6e" + integrity sha512-P3AJpL3WCEif2QHBysLiIP7CAcpc5KwIqiZ07ogQrhBs/qLlBsbjQsYey63vO7LrhmI79mXlidni3N7GMFBO8A== dependencies: - "@tryghost/errors" "^1.2.14" + "@tryghost/errors" "^1.2.15" lodash "^4.17.21" moment-timezone "^0.5.33" @@ -3108,38 +3108,38 @@ resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-custom-query/-/bookshelf-custom-query-0.1.14.tgz#789d113c0fd6b157307c2b58210b1c43a33cf062" integrity sha512-/+H8BaOrfSstY4BFCXyJt1g/Mv8A21A4rGnr7lzpyXBiKueS1dkHq8mDlopggLgSlCqbXLmndD/x0mFLvZISUA== -"@tryghost/bookshelf-eager-load@^0.1.16": - version "0.1.16" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-eager-load/-/bookshelf-eager-load-0.1.16.tgz#dc7d9afd01cda02f16a2304d7210fa34a1c0ca67" - integrity sha512-iFsT8DinsZw2NzF+U1b4LPSy0hA8vWULXfGoz/sS/EKv3P6OIaVs17ZSTxoJtnRKGwGAFE02qGMItRP/B3n52g== - dependencies: - "@tryghost/debug" "^0.1.17" - lodash "^4.17.21" - -"@tryghost/bookshelf-filter@^0.4.11": - version "0.4.11" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-filter/-/bookshelf-filter-0.4.11.tgz#ce97f70691e7d5a56496be0de28b43e66c6a47a6" - integrity sha512-at5qAFeas8gdrMYIXjHtMTLYFXbQpDJCDRJAdF01kihs11dbkMksHiQq7esk6xtMf0ASLh/TqFJmI2h24eeCFw== - dependencies: - "@tryghost/debug" "^0.1.17" - "@tryghost/errors" "^1.2.14" - "@tryghost/nql" "^0.9.0" - "@tryghost/tpl" "^0.1.17" - -"@tryghost/bookshelf-has-posts@^0.1.17": +"@tryghost/bookshelf-eager-load@^0.1.17": version "0.1.17" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-has-posts/-/bookshelf-has-posts-0.1.17.tgz#07ec5d6875d41d7c2fe9baa5997c60f0139052ad" - integrity sha512-5rav7YgbLorWMXPVm2iD8z8W6DoXvKHHr1QEebToC9HH2vX5bOpsT0rMdz7N9t+fKG1MwNfzBwVv5oUKImtZTg== + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-eager-load/-/bookshelf-eager-load-0.1.17.tgz#abf64fbcc8a0b41db21402db16a448461f51a675" + integrity sha512-477M1a/yycl4sh3bIMiHIeTpqcvohM/AA2HG1RSNbZfu3L12YpTRyki786Rc9aMW5+sSmb+Zv5OUlXPrWyN3Xg== dependencies: - "@tryghost/debug" "^0.1.17" + "@tryghost/debug" "^0.1.18" lodash "^4.17.21" -"@tryghost/bookshelf-include-count@^0.2.2": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-include-count/-/bookshelf-include-count-0.2.2.tgz#c99d59a3ebadeaac22e1211ae0e843ca2723b713" - integrity sha512-Wp9rea2T/RpmWU4fRJ8/1pqh1HKlGE8mkGDIzy56E2kGa5XLr6HnkHwEmlsm5vjtOSKgHXAbbpc/73cJ6rmbXg== +"@tryghost/bookshelf-filter@^0.4.12": + version "0.4.12" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-filter/-/bookshelf-filter-0.4.12.tgz#a2323df684fd06501c7a17a9dc71d1ff7e171841" + integrity sha512-dY8d5C3WEamGp+s+e4au0IkTm/Nj8FYtobmB1npDkzsbOa7HvIpYJQKQHD6k/OOtv5uCh3eCJpLB5G1ULgN21w== dependencies: - "@tryghost/debug" "^0.1.17" + "@tryghost/debug" "^0.1.18" + "@tryghost/errors" "^1.2.15" + "@tryghost/nql" "^0.9.0" + "@tryghost/tpl" "^0.1.18" + +"@tryghost/bookshelf-has-posts@^0.1.18": + version "0.1.18" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-has-posts/-/bookshelf-has-posts-0.1.18.tgz#d5350e1ddd7db3b596ca263aa137c2c72646d1da" + integrity sha512-hc7Dj21MP+zAwzePCKJokDbqTGtvYeR15kvhf8XacGE53qL6PSVgpni3QmU/d0FWGUtADa9O6/LHTHyos8yMDg== + dependencies: + "@tryghost/debug" "^0.1.18" + lodash "^4.17.21" + +"@tryghost/bookshelf-include-count@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-include-count/-/bookshelf-include-count-0.3.0.tgz#182fd7810e1cadb666cf27d60b1fb925b1db7e98" + integrity sha512-sa1kPxFmHqUcgBSGo0Zvrl4o6KOdm8lNsfwCOWrFRi6Ez0HDafKjO4r15NkhGzBJcSTNdOP+lD7l0mBSz6Rs5A== + dependencies: + "@tryghost/debug" "^0.1.18" lodash "^4.17.21" "@tryghost/bookshelf-order@^0.1.14": @@ -3149,28 +3149,28 @@ dependencies: lodash "^4.17.21" -"@tryghost/bookshelf-pagination@^0.1.26": - version "0.1.26" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-pagination/-/bookshelf-pagination-0.1.26.tgz#1408b7fbab2471769c295670dde114155437dbd7" - integrity sha512-jjnE2OzaUlkdzR3YzqKMNZHlaD5IWpCQ0wombMOGPv3K8Z1JDffdO0Iu6mAB8vFPgZopZSPD4lrm4WahCzmC3g== +"@tryghost/bookshelf-pagination@^0.1.27": + version "0.1.27" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-pagination/-/bookshelf-pagination-0.1.27.tgz#f21c34e2c9837c333f3b1df791a8d74b496f762b" + integrity sha512-oH447PdKm8zNWwzCQX23fIPS/g/MXOVXcknWHMTpk4ATvhjTmxnykX5Ms4lmBzd7ZxzneRk5XjD+9Qo0MkTjlQ== dependencies: - "@tryghost/errors" "^1.2.14" - "@tryghost/tpl" "^0.1.17" + "@tryghost/errors" "^1.2.15" + "@tryghost/tpl" "^0.1.18" lodash "^4.17.21" -"@tryghost/bookshelf-plugins@0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-plugins/-/bookshelf-plugins-0.4.3.tgz#a558a369b4c4c0f326ef4555d784de732c80be94" - integrity sha512-HCBE3xAokvIrXlRIicgExtMIWAqU/tEJL9oKI4ZmWjNVOY3NUyc/LxXn8CWug9KNbTn1eextyGPrFkRAxKOjMw== +"@tryghost/bookshelf-plugins@0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@tryghost/bookshelf-plugins/-/bookshelf-plugins-0.5.0.tgz#494107183b86ed31061b9125936db6e3ed3f3258" + integrity sha512-6cQkDYgxOnsjGJ9BPQ9KycHjmx7mxiWkykcAEKAKDDqUCspdQhl7II0VmdhGOr9dJjzyAd+nTWtbkLS5LBjoZw== dependencies: - "@tryghost/bookshelf-collision" "^0.1.24" + "@tryghost/bookshelf-collision" "^0.1.25" "@tryghost/bookshelf-custom-query" "^0.1.14" - "@tryghost/bookshelf-eager-load" "^0.1.16" - "@tryghost/bookshelf-filter" "^0.4.11" - "@tryghost/bookshelf-has-posts" "^0.1.17" - "@tryghost/bookshelf-include-count" "^0.2.2" + "@tryghost/bookshelf-eager-load" "^0.1.17" + "@tryghost/bookshelf-filter" "^0.4.12" + "@tryghost/bookshelf-has-posts" "^0.1.18" + "@tryghost/bookshelf-include-count" "^0.3.0" "@tryghost/bookshelf-order" "^0.1.14" - "@tryghost/bookshelf-pagination" "^0.1.26" + "@tryghost/bookshelf-pagination" "^0.1.27" "@tryghost/bookshelf-search" "^0.1.14" "@tryghost/bookshelf-transaction-events" "^0.1.14" @@ -3216,6 +3216,11 @@ resolved "https://registry.yarnpkg.com/@tryghost/database-info/-/database-info-0.3.8.tgz#77af1a8a930986d3b3cef14e4dd5928be4c4bdcc" integrity sha512-fs034LgcOlyvVwQtaaRPuZW1AF1ac6/UMb5UxdO0/sreMiO30Y5mVuOBqET6CbWKLKOK6WVvVgmGIgyddH0Qrg== +"@tryghost/database-info@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@tryghost/database-info/-/database-info-0.3.9.tgz#76a995a5ec489e0adf55699223cd91e372584552" + integrity sha512-aWeWk1bJVpBBZ5sX3+66z/pEyYG3nselSSua3VOxC3Ii8pbppDkcJJlm2Z6TSLNEEHtrhHtXJJH4+eBfLA24kQ== + "@tryghost/debug@0.1.17", "@tryghost/debug@^0.1.13", "@tryghost/debug@^0.1.17": version "0.1.17" resolved "https://registry.yarnpkg.com/@tryghost/debug/-/debug-0.1.17.tgz#2655fa1c71b2608e71de7f166f5f7b1a1c086915" @@ -3224,13 +3229,21 @@ "@tryghost/root-utils" "^0.3.15" debug "^4.3.1" -"@tryghost/elasticsearch@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@tryghost/elasticsearch/-/elasticsearch-3.0.2.tgz#3a781b3c8069ac8753c5a15c8dd9efcb5f534f46" - integrity sha512-sIKNikP/v97sdCAiOAVpwQIWiqDAuCCjy6Ty8mulKWn1KpmTEJAcLyDaCQtrP8P7ZCu4tIR3W8NXzhORSbttKw== +"@tryghost/debug@0.1.18", "@tryghost/debug@^0.1.18": + version "0.1.18" + resolved "https://registry.yarnpkg.com/@tryghost/debug/-/debug-0.1.18.tgz#b126fedb8f409f44c3082a2c469171940cd6986e" + integrity sha512-vG9dHuvYn8LlRrWyFUbt4zmsWmPLlZZJn1Qv9ko8uML36nILrpFehTnfda5663cMlwQQWPJ+DL6SCylIbtLWDg== + dependencies: + "@tryghost/root-utils" "^0.3.16" + debug "^4.3.1" + +"@tryghost/elasticsearch@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@tryghost/elasticsearch/-/elasticsearch-3.0.3.tgz#6651298989f38bbe30777ab122d56a43f719d2c2" + integrity sha512-P5uH0xfpjNh0aanGjc3yXDy0oJ0rEiCmYpgAzCBBDihLbf6IU1krD3MazrpB9n360Dhgli23aNbebYPUnvAKTg== dependencies: "@elastic/elasticsearch" "8.2.1" - "@tryghost/debug" "^0.1.17" + "@tryghost/debug" "^0.1.18" split2 "4.1.0" "@tryghost/ember-promise-modals@2.0.1": @@ -3249,7 +3262,7 @@ focus-trap "^6.7.2" postcss-preset-env "^7.3.1" -"@tryghost/errors@1.2.14", "@tryghost/errors@^1.0.0", "@tryghost/errors@^1.2.1", "@tryghost/errors@^1.2.14", "@tryghost/errors@^1.2.3": +"@tryghost/errors@1.2.14", "@tryghost/errors@^1.0.0", "@tryghost/errors@^1.2.1", "@tryghost/errors@^1.2.3": version "1.2.14" resolved "https://registry.yarnpkg.com/@tryghost/errors/-/errors-1.2.14.tgz#af5e0ea1450b6fac7bde94585177943f85f0e41f" integrity sha512-ycXhblMBlbwXo+PfmVJZtT26/B1wu6Ae/8SBjXlzHAp6qlkho/Z5hZPKMRo0frfBt6CDGyX/abKTeVQzSkTPYA== @@ -3258,12 +3271,21 @@ utils-copy-error "^1.0.1" uuid "^8.3.2" -"@tryghost/express-test@0.11.1": - version "0.11.1" - resolved "https://registry.yarnpkg.com/@tryghost/express-test/-/express-test-0.11.1.tgz#bab0bbffbdd11b72bfdcdb0aa98d8ee33f0cec1f" - integrity sha512-hiUf8Jbr6LKR1ZFUzQiN7WmOcanVYfiQbiRLGMlbXHvqQKgEBBVRuZqtsxs6rErsAZvD8Q223dsAc3Q/mNfLDA== +"@tryghost/errors@1.2.15", "@tryghost/errors@^1.2.15": + version "1.2.15" + resolved "https://registry.yarnpkg.com/@tryghost/errors/-/errors-1.2.15.tgz#0f9e63f3c8d1024fe53fd5e6eb600657f366a46f" + integrity sha512-3T0KA5qpxM3JU2ZI3s5w2qUztA2CG3h+G86zKxbZc9ZnOP+nzsJ2fn9nkkqVwFUvxOr76mjWMgVETdH6yXiu1g== dependencies: - "@tryghost/jest-snapshot" "^0.4.1" + lodash "^4.17.21" + utils-copy-error "^1.0.1" + uuid "^8.3.2" + +"@tryghost/express-test@0.11.2": + version "0.11.2" + resolved "https://registry.yarnpkg.com/@tryghost/express-test/-/express-test-0.11.2.tgz#4e4c83879dce176a066966d3714a2ce526dccd66" + integrity sha512-m4pmDbiwIUAvdPJEvaWWFiH4xItYyRrugXgFjvMSG5j7Q/aZxtNk2JcpJzpMWHJVSwjUorzV6uq22tLaIfTq0A== + dependencies: + "@tryghost/jest-snapshot" "^0.4.2" cookiejar "^2.1.3" reqresnext "^1.7.0" @@ -3283,13 +3305,13 @@ "@tryghost/mobiledoc-kit" "^0.12.4-ghost.1" jsdom "^18.0.0" -"@tryghost/http-stream@^0.1.10": - version "0.1.10" - resolved "https://registry.yarnpkg.com/@tryghost/http-stream/-/http-stream-0.1.10.tgz#30efbcd251675a38e11190bb14a788723b3148a2" - integrity sha512-ixiAo3ZsnUzoKVKhyIrFepq8kdef1PVbN17WtVqy+r18PY1K3N5ViQWhK/CGvBWdX/Ge4feVwx9LD0BgNRT5/w== +"@tryghost/http-stream@^0.1.11": + version "0.1.11" + resolved "https://registry.yarnpkg.com/@tryghost/http-stream/-/http-stream-0.1.11.tgz#ca658c3e09b0584cb013b4a56c2eb18884d5c801" + integrity sha512-R/cz7ojry7GqfMwe4EYwK/6LInqaiFfi7VWoFz8REimbNR1qYDwTaZORvlfzvuNVbhwv/I5Gvhee52Xdod4vBA== dependencies: - "@tryghost/errors" "^1.2.14" - "@tryghost/request" "^0.1.28" + "@tryghost/errors" "^1.2.15" + "@tryghost/request" "^0.1.29" "@tryghost/image-transform@1.2.2": version "1.2.2" @@ -3302,14 +3324,14 @@ optionalDependencies: sharp "^0.30.0" -"@tryghost/jest-snapshot@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@tryghost/jest-snapshot/-/jest-snapshot-0.4.1.tgz#c082d56a76a9b31b03aad5ccaa91ed9105637f49" - integrity sha512-/5UnvdOUGOI6ujd7/Ws7rRooAiuVcb1GxxWbPSMpRSkb7uj31gcmJm5sPHFahArPhXmDbmQFTyJdt/HhDk+Arg== +"@tryghost/jest-snapshot@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@tryghost/jest-snapshot/-/jest-snapshot-0.4.2.tgz#3f4a4119cb91e7f8cb7322ec4cc8e97acb21978c" + integrity sha512-7CFksY5fDb3yup04K5Vk/uDY9sQZjFKARXLkbTwRgLm9IAQeI8xTFBCpP/Ok2VWTzxWjzMEXmS2Yj72YUB8/Fw== dependencies: "@jest/expect" "^28.0.1" "@jest/expect-utils" "^28.0.1" - "@tryghost/errors" "^1.2.14" + "@tryghost/errors" "^1.2.15" jest-snapshot "^28.0.0" "@tryghost/kg-card-factory@3.1.4": @@ -3384,16 +3406,16 @@ lodash "^4.17.21" luxon "^1.26.0" -"@tryghost/logging@2.2.3", "@tryghost/logging@^2.2.3": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@tryghost/logging/-/logging-2.2.3.tgz#40575a42e18b907a49cee5dfdfa62deb820954aa" - integrity sha512-ACCm84U4HITt3mQhDSpyDLZetxzjYo4ux2MoSVGL3zxPfQBPFoI6hWEiSzYWX/4RGq2l2tR4di+5LWjIe8Ow6A== +"@tryghost/logging@2.2.3", "@tryghost/logging@2.2.4", "@tryghost/logging@^2.2.3": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@tryghost/logging/-/logging-2.2.4.tgz#3e2ad6745fdc4d98ad80c632ef69e8ea8177e41c" + integrity sha512-r0WhWkmEScr/jL5wF9W/tMZI8hF8EOq8eVzDlDbk7qPczRyIHQcLTOxb/iWgkqeGEOP4RwCrqtMmKSlqIO8ZVQ== dependencies: "@tryghost/bunyan-rotating-filestream" "^0.0.7" - "@tryghost/elasticsearch" "^3.0.2" - "@tryghost/http-stream" "^0.1.10" - "@tryghost/pretty-stream" "^0.1.11" - "@tryghost/root-utils" "^0.3.15" + "@tryghost/elasticsearch" "^3.0.3" + "@tryghost/http-stream" "^0.1.11" + "@tryghost/pretty-stream" "^0.1.12" + "@tryghost/root-utils" "^0.3.16" bunyan "^1.8.15" bunyan-loggly "^1.4.2" fs-extra "^10.0.0" @@ -3412,14 +3434,14 @@ papaparse "^5.3.2" pump "^3.0.0" -"@tryghost/metrics@1.0.14": - version "1.0.14" - resolved "https://registry.yarnpkg.com/@tryghost/metrics/-/metrics-1.0.14.tgz#03e489c0e68f75a4308c6024cdf30573bf1dfef4" - integrity sha512-Y7ZGbR1m56kj/IqCftJflZ8WQY7npNGXbwxCiE+shuQr9Y79nMkIArrFek0TqCQihn5qEyHtaKKUFd3Dg9FFxw== +"@tryghost/metrics@1.0.15": + version "1.0.15" + resolved "https://registry.yarnpkg.com/@tryghost/metrics/-/metrics-1.0.15.tgz#d65fc570a9deb9cc0f3821b66975be258b58a37f" + integrity sha512-XK5A70z9J7IU5hi4mSaGOvfmTKEOWdKKBeso4mmOa/rehkyfO3rRb4a0dl+KC3KDWD3J1sRrKduDZd/vqZK2qw== dependencies: - "@tryghost/elasticsearch" "^3.0.2" - "@tryghost/pretty-stream" "^0.1.11" - "@tryghost/root-utils" "^0.3.15" + "@tryghost/elasticsearch" "^3.0.3" + "@tryghost/pretty-stream" "^0.1.12" + "@tryghost/root-utils" "^0.3.16" json-stringify-safe "^5.0.1" optionalDependencies: promise.allsettled "^1.0.5" @@ -3455,13 +3477,13 @@ dependencies: lodash "^4.17.11" -"@tryghost/nodemailer@0.3.24": - version "0.3.24" - resolved "https://registry.yarnpkg.com/@tryghost/nodemailer/-/nodemailer-0.3.24.tgz#b9cd59fadf20b327002a88ac4b2f5597a507e576" - integrity sha512-PR8Z1YbUU2h5EH82AJCP8qQ1Ra5/7Y9Z0YdMYfx0mLOYON4REI2pHnN5awc0lF3TnwgBv8SGMAUuV0XZX4YOsg== +"@tryghost/nodemailer@0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@tryghost/nodemailer/-/nodemailer-0.3.25.tgz#4766dfe8dfa37cb38f3211e9f7bc3c7ff7d084f2" + integrity sha512-H2AzPAE9g4Kzy7/dsih83nnOvhHj3iZI2NwuvJvgHnUWXdPrZw94YyCj2Mof4CASsYvudEh7lnQ32niBjbD9Aw== dependencies: "@aws-sdk/client-ses" "^3.31.0" - "@tryghost/errors" "^1.2.14" + "@tryghost/errors" "^1.2.15" nodemailer "^6.6.3" nodemailer-direct-transport "^3.3.2" nodemailer-stub-transport "^1.1.0" @@ -3491,34 +3513,50 @@ chalk "^4.1.0" sywac "^1.3.0" -"@tryghost/pretty-stream@^0.1.11": - version "0.1.11" - resolved "https://registry.yarnpkg.com/@tryghost/pretty-stream/-/pretty-stream-0.1.11.tgz#5e0bcb4a0041147cc43a95057ac4a957d69f0757" - integrity sha512-lLuXNwCsbShUTcZ+A/UZ5YvHlMHbbfA019rGcFSNFHIy6An2PGJ8o8Q/NQeX2x94hqa5IKC2jZyUmB/NMvu+FA== +"@tryghost/pretty-cli@1.2.30": + version "1.2.30" + resolved "https://registry.yarnpkg.com/@tryghost/pretty-cli/-/pretty-cli-1.2.30.tgz#1e14ba29cc30bf40fedd3ec6ce6e80a2f99c8abb" + integrity sha512-mREyW+u2U4HVihGUvrlr1U+K2WctImm9trhqi3IxvHaLFODvFuIaC3d10X64quD3vX76SVaWu4XLoJ1z0WbOIA== + dependencies: + chalk "^4.1.0" + sywac "^1.3.0" + +"@tryghost/pretty-stream@^0.1.12": + version "0.1.12" + resolved "https://registry.yarnpkg.com/@tryghost/pretty-stream/-/pretty-stream-0.1.12.tgz#e1991415610142d9895d3e5cb84ac838c58d8c8b" + integrity sha512-0FZxJG5K6Nz9nK347jkMwb0x+Gr988z1IRAY/Fi9OIebBxq8ZLnFDZvFIW1zlALoBwX2QW6X6K/tABzlI7ofoA== dependencies: lodash "^4.17.21" moment "^2.29.1" prettyjson "^1.2.5" -"@tryghost/promise@0.1.20": - version "0.1.20" - resolved "https://registry.yarnpkg.com/@tryghost/promise/-/promise-0.1.20.tgz#cf588998300e02ad277ce6a01f2957001f88333d" - integrity sha512-5+/ObL7Nxshj89ywF6CR/JzWtfh1KNHJRSFhVkq0TmK9RYehMj051AD+YFFfUDwNVYdLaxCwFMTzRPWN4f7txA== +"@tryghost/promise@0.1.21": + version "0.1.21" + resolved "https://registry.yarnpkg.com/@tryghost/promise/-/promise-0.1.21.tgz#117b09ec5bfd21919a643ea933e2e5d707cef329" + integrity sha512-PVyRL+AP0VgO0rUxscibUbZkZkAAmej2O/pQOOOj2fyklNTGflukrTyfZGbpbRTqO1LCOqKCgCuUGAKKN+5w8A== dependencies: bluebird "^3.7.2" -"@tryghost/request@0.1.28", "@tryghost/request@^0.1.28": - version "0.1.28" - resolved "https://registry.yarnpkg.com/@tryghost/request/-/request-0.1.28.tgz#dc2423478116cbdaa60b5e750a357b588e154025" - integrity sha512-qjocJI/W5CRfAptPPrlkVsgOLS78t1VwAdyDZhdqhYP8bp+S5jO6IxmvJtB6DynQREeZIH2UVJQ/0DPE51Ij4w== +"@tryghost/request@0.1.29", "@tryghost/request@^0.1.29": + version "0.1.29" + resolved "https://registry.yarnpkg.com/@tryghost/request/-/request-0.1.29.tgz#1d12c21e25bfcf227bc39f0e3b0e76ba7caf822a" + integrity sha512-lt/7vif/fdQPeIc32PDOmxH/gs5/AqlmsY8zjLRaZMTqzVC0B7VazUKKYFFpXjt3FcqeKbgbqrQjbJ5jx6dABw== dependencies: - "@tryghost/errors" "^1.2.14" - "@tryghost/validator" "^0.1.26" - "@tryghost/version" "^0.1.15" + "@tryghost/errors" "^1.2.15" + "@tryghost/validator" "^0.1.27" + "@tryghost/version" "^0.1.16" got "9.6.0" lodash "^4.17.21" -"@tryghost/root-utils@0.3.15", "@tryghost/root-utils@^0.3.15": +"@tryghost/root-utils@0.3.16", "@tryghost/root-utils@^0.3.16": + version "0.3.16" + resolved "https://registry.yarnpkg.com/@tryghost/root-utils/-/root-utils-0.3.16.tgz#a3dca3c9edccab494ca5968e460af2d49da4ce2b" + integrity sha512-+X79NBxHwJW9tiO/B+uJbCmcgv/MotY+g3p/+bwIWjGfuC7VUXBDHNswERtJrx6b374b1N1zXXl1iWJNtSavdg== + dependencies: + caller "^1.0.1" + find-root "^1.1.0" + +"@tryghost/root-utils@^0.3.15": version "0.3.15" resolved "https://registry.yarnpkg.com/@tryghost/root-utils/-/root-utils-0.3.15.tgz#0b7fd9e02360b81cf46d07b1b87d1e94e9bf6585" integrity sha512-HgSdyvg1CVXZBIdrJh5RMp14VQhJWFBozs1SCjomWew400ERDsUIHamwlzKxqfGZgc1P4Sp1jinkOb+dVanruw== @@ -3558,10 +3596,10 @@ resolved "https://registry.yarnpkg.com/@tryghost/timezone-data/-/timezone-data-0.2.71.tgz#0ac10c3c16ca8153bce8ecf0d166e194efed6225" integrity sha512-2WiXgunY97uyRw0hQ9qu57L6152th09OtfMzIlw/N7Ycf+W9lbHJIMUhVeanqgZ3o3wMzpSfkm9eoxOngdVCqw== -"@tryghost/tpl@0.1.17", "@tryghost/tpl@^0.1.17": - version "0.1.17" - resolved "https://registry.yarnpkg.com/@tryghost/tpl/-/tpl-0.1.17.tgz#3663f732346f0759a1ce4a241f5158dbca1ebb5c" - integrity sha512-Lfo/ITYoFofvfee2Wgh/LjRoXfut1J7ETcPZppvxyavwjuNAk0Z3kOHstjVkRllFnMtox10gwDlwlqe/QS4o2g== +"@tryghost/tpl@0.1.18", "@tryghost/tpl@^0.1.18": + version "0.1.18" + resolved "https://registry.yarnpkg.com/@tryghost/tpl/-/tpl-0.1.18.tgz#a4c87e3a82cd75aba72b2325c91d87677cc615f2" + integrity sha512-frhPu9ScllYof5Xrhn4FrOluBSOEQiAQP67EOiFV6XE8UKJtAYTB48Xf9XtAWm9IfznpOGuy/2w84nK1SN7lvg== dependencies: lodash.template "^4.5.0" @@ -3589,29 +3627,29 @@ remark-footnotes "^1.0.0" unist-util-visit "^2.0.0" -"@tryghost/validator@0.1.26", "@tryghost/validator@^0.1.26": - version "0.1.26" - resolved "https://registry.yarnpkg.com/@tryghost/validator/-/validator-0.1.26.tgz#b938840acddd575735e78d773ec3c2c77bdcdde5" - integrity sha512-lVixU7sapNMenGIQhM7MjAky5kFnfeV1KBxMpYHRhRxUrrXQHJ/R8bHzE5DGy4oYz7twwdP8ubknVIEPFjNSQA== +"@tryghost/validator@0.1.27", "@tryghost/validator@^0.1.27": + version "0.1.27" + resolved "https://registry.yarnpkg.com/@tryghost/validator/-/validator-0.1.27.tgz#35cc29e66eb861b03460f1e801d894efbabd6e36" + integrity sha512-0GFCSt4S5DmL1U1InSP9M5egqsqHh7Y1+GP62nLP9ZK9UhZoksAcYUv/VpvBh3slx4tIvp8k+0395B1OLnUhHg== dependencies: - "@tryghost/errors" "^1.2.14" - "@tryghost/tpl" "^0.1.17" + "@tryghost/errors" "^1.2.15" + "@tryghost/tpl" "^0.1.18" lodash "^4.17.21" moment-timezone "^0.5.23" validator "7.2.0" -"@tryghost/version@0.1.15", "@tryghost/version@^0.1.15": - version "0.1.15" - resolved "https://registry.yarnpkg.com/@tryghost/version/-/version-0.1.15.tgz#8e6138f9ce9f49e0f3f20b0fc4e20d83c948f2b4" - integrity sha512-mdhm3Se0HtUdZgNVgYNhuywDKFfSIrt3eiD3yXWBmb/XzQNU31lW5RESt+7ZulUMHZVTpEQi1SE+muCVnVfn1g== +"@tryghost/version@0.1.16", "@tryghost/version@^0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@tryghost/version/-/version-0.1.16.tgz#0d28acd180bcc1461bfaf6b45fbdbb66318d518f" + integrity sha512-17CTizTr7zYY0zwkf7v0wiJ6XqlDFT1yNrrIDvC15WEQD8NTADX1y/cjMLS77+loCFq19P7CeMq68xPRPtkPqg== dependencies: - "@tryghost/root-utils" "^0.3.15" + "@tryghost/root-utils" "^0.3.16" semver "^7.3.5" -"@tryghost/webhook-mock-receiver@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@tryghost/webhook-mock-receiver/-/webhook-mock-receiver-0.1.1.tgz#9a6aab1c215121eef486bddf3f90b7fe49367166" - integrity sha512-XlA6JcoINptCYz7yy4clAK9FfkCZHwKAqSZbRRXywKRA8KAocjheHq44HUVTWVGpZB1myBAiA77Iyhs8wla1/w== +"@tryghost/webhook-mock-receiver@0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@tryghost/webhook-mock-receiver/-/webhook-mock-receiver-0.1.2.tgz#feeaad6298cbcda16447804e0b7829f2aa32584c" + integrity sha512-urg+UERP6CPUaCYoZBg1ANe1DCu1+QbJL8V2hDrkJlRCAjokJpXwy9Wwc/VdHXbVSTeH6JVAK1LNH3gjJVGhVw== "@tryghost/zip@1.1.27": version "1.1.27" @@ -3623,6 +3661,16 @@ extract-zip "^2.0.1" fs-extra "^10.0.0" +"@tryghost/zip@1.1.28": + version "1.1.28" + resolved "https://registry.yarnpkg.com/@tryghost/zip/-/zip-1.1.28.tgz#2274498721b0b46ce5f96bbab8927f3c3bc0f8b5" + integrity sha512-2to9/P7yaSYd0HF525MAnYJsScegV7SENK9sqSw3uzXwmSnpJEgwt39rCVXNhh6JGp7fl8Z0sWhOI9ZQJ1eNlg== + dependencies: + archiver "^5.0.0" + bluebird "^3.7.2" + extract-zip "^2.0.1" + fs-extra "^10.0.0" + "@trysound/sax@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"