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)
This commit is contained in:
Simon Backx 2022-08-10 16:12:35 +02:00 committed by GitHub
parent 41d228a1ae
commit 82a3133ace
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1435 additions and 274 deletions

View File

@ -21,6 +21,6 @@
"sinon": "14.0.0"
},
"dependencies": {
"@tryghost/errors": "1.2.14"
"@tryghost/errors": "1.2.15"
}
}

View File

@ -21,6 +21,6 @@
"sinon": "14.0.0"
},
"dependencies": {
"@tryghost/logging": "2.2.3"
"@tryghost/logging": "2.2.4"
}
}

View File

@ -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'

View File

@ -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;
});
}
};

View File

@ -45,5 +45,9 @@ module.exports = {
get webhooks() {
return require('./webhooks');
},
get comments() {
return require('./comments');
}
};

View File

@ -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;
};

View File

@ -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;
}
});

View File

@ -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'];

View File

@ -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';
},

View File

@ -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'];

View File

@ -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
*

View File

@ -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
*/

View File

@ -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});
}
/**

View File

@ -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));

View File

@ -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"
}

View File

@ -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",

View File

@ -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
}]
});

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -20,7 +20,7 @@
"sinon": "14.0.0"
},
"dependencies": {
"@tryghost/debug": "0.1.17",
"@tryghost/debug": "0.1.18",
"lodash": "4.17.21"
}
}

View File

@ -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",

View File

@ -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"
}
}

View File

@ -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",

View File

@ -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"
}
}

View File

@ -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",

View File

@ -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"

View File

@ -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"
}

View File

@ -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"

View File

@ -22,6 +22,6 @@
"sinon": "14.0.0"
},
"dependencies": {
"@tryghost/errors": "1.2.14"
"@tryghost/errors": "1.2.15"
}
}

View File

@ -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"
}
}

View File

@ -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",

View File

@ -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"

View File

@ -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"
},

298
yarn.lock
View File

@ -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"