From 0d7f98f4d181344a67b3323d081292d73cde219d Mon Sep 17 00:00:00 2001 From: "Fabien \"egg\" O'Carroll" Date: Thu, 15 Jun 2023 15:46:48 +0200 Subject: [PATCH] Supported adding/removing post to collection via Posts API We've got some fairly simple diffing logic here to update the collections which a post is in, the bulk of the changes here are to support the return of a DTO rather than Bookshelf Model. This also helps improve the architecture because we are step closer to removing infrastructure concerns (HTTP Response Headers) from the business logic layer. For now there is a crappy EventString which can be passed back to the controller which can then handle any HTTP related concerns, although long term these should be actual events like PostPublished or PostUpdated. --- ghost/core/core/server/api/endpoints/posts.js | 26 +- .../admin/__snapshots__/posts.test.js.snap | 357 ++++++++++++++++++ ghost/core/test/e2e-api/admin/posts.test.js | 74 ++++ ghost/posts-service/lib/PostsService.js | 92 ++++- 4 files changed, 544 insertions(+), 5 deletions(-) diff --git a/ghost/core/core/server/api/endpoints/posts.js b/ghost/core/core/server/api/endpoints/posts.js index bb727f08a3..c019c38b3f 100644 --- a/ghost/core/core/server/api/endpoints/posts.js +++ b/ghost/core/core/server/api/endpoints/posts.js @@ -1,3 +1,4 @@ +const urlUtils = require('../../../shared/url-utils'); const models = require('../../models'); const getPostServiceInstance = require('../../services/posts/posts-service'); const allowedIncludes = [ @@ -21,6 +22,22 @@ const unsafeAttrs = ['status', 'authors', 'visibility']; const postsService = getPostServiceInstance(); +/** + * @param {string} event + */ +function getCacheHeaderFromEventString(event, dto) { + if (event === 'published_updated' || event === 'unpublished') { + return true; + } + if (event === 'scheduled_updated' || event === 'draft_updated') { + return { + value: urlUtils.urlFor({ + relativeUrl: urlUtils.urlJoin('/p', dto.uuid, '/') + }) + }; + } +} + module.exports = { docName: 'posts', browse: { @@ -162,6 +179,7 @@ module.exports = { edit: { headers: { + /** @type {boolean | {value: string}} */ cacheInvalidate: false }, options: [ @@ -194,9 +212,11 @@ module.exports = { unsafeAttrs: unsafeAttrs }, async query(frame) { - let model = await postsService.editPost(frame); - - this.headers.cacheInvalidate = postsService.handleCacheInvalidation(model); + let model = await postsService.editPost(frame, { + eventHandler: (event, dto) => { + this.headers.cacheInvalidate = getCacheHeaderFromEventString(event, dto); + } + }); return model; } diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap index 371d9c3533..095d70b1d9 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap @@ -974,6 +974,363 @@ Object { } `; +exports[`Posts API Update Can add and remove collections 1: [body] 1`] = ` +Object { + "posts": Array [ + Object { + "authors": Any, + "canonical_url": null, + "codeinjection_foot": null, + "codeinjection_head": null, + "comment_id": Any, + "count": Object { + "clicks": 0, + "negative_feedback": 0, + "positive_feedback": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "custom_excerpt": null, + "custom_template": null, + "email": null, + "email_only": false, + "email_segment": "all", + "email_subject": null, + "excerpt": null, + "feature_image": null, + "feature_image_alt": null, + "feature_image_caption": null, + "featured": false, + "frontmatter": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "meta_description": null, + "meta_title": null, + "mobiledoc": "{\\"version\\":\\"0.3.1\\",\\"ghostVersion\\":\\"4.0\\",\\"markups\\":[],\\"atoms\\":[],\\"cards\\":[],\\"sections\\":[[1,\\"p\\",[[0,[],0,\\"\\"]]]]}", + "newsletter": null, + "og_description": null, + "og_image": null, + "og_title": null, + "post_revisions": Any, + "primary_author": Any, + "primary_tag": Any, + "published_at": null, + "slug": "collection-update-test", + "status": "draft", + "tags": Any, + "tiers": Array [ + Object { + "active": true, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "currency": null, + "description": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "monthly_price": null, + "monthly_price_id": null, + "name": "Free", + "slug": "free", + "trial_days": 0, + "type": "free", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "public", + "welcome_page_url": null, + "yearly_price": null, + "yearly_price_id": null, + }, + Object { + "active": true, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "currency": "usd", + "description": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "monthly_price": 500, + "monthly_price_id": null, + "name": "Default Product", + "slug": "default-product", + "trial_days": 0, + "type": "paid", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "public", + "welcome_page_url": null, + "yearly_price": 5000, + "yearly_price_id": null, + }, + ], + "title": "Collection update test", + "twitter_description": null, + "twitter_image": null, + "twitter_title": null, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "visibility": "public", + }, + ], +} +`; + +exports[`Posts API Update Can add and remove collections 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": "3701", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/posts\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + +exports[`Posts API Update Can add and remove collections 3: [body] 1`] = ` +Object { + "posts": Array [ + Object { + "authors": Any, + "canonical_url": null, + "codeinjection_foot": null, + "codeinjection_head": null, + "collections": Array [ + Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.\\\\d\\{3\\}Z/, + "description": null, + "feature_image": null, + "filter": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "posts": Array [ + Object { + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "sort_order": 0, + }, + ], + "title": "Collection to remove.", + "type": "manual", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.\\\\d\\{3\\}Z/, + }, + ], + "comment_id": Any, + "count": Object { + "clicks": 0, + "negative_feedback": 0, + "positive_feedback": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "custom_excerpt": null, + "custom_template": null, + "email": null, + "email_only": false, + "email_segment": "all", + "email_subject": null, + "excerpt": null, + "feature_image": null, + "feature_image_alt": null, + "feature_image_caption": null, + "featured": false, + "frontmatter": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "meta_description": null, + "meta_title": null, + "mobiledoc": "{\\"version\\":\\"0.3.1\\",\\"ghostVersion\\":\\"4.0\\",\\"markups\\":[],\\"atoms\\":[],\\"cards\\":[],\\"sections\\":[[1,\\"p\\",[[0,[],0,\\"\\"]]]]}", + "newsletter": null, + "og_description": null, + "og_image": null, + "og_title": null, + "post_revisions": Any, + "primary_author": Any, + "primary_tag": Any, + "published_at": null, + "slug": "collection-update-test", + "status": "draft", + "tags": Any, + "tiers": Array [ + Object { + "active": true, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "currency": null, + "description": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "monthly_price": null, + "monthly_price_id": null, + "name": "Free", + "slug": "free", + "trial_days": 0, + "type": "free", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "public", + "welcome_page_url": null, + "yearly_price": null, + "yearly_price_id": null, + }, + Object { + "active": true, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "currency": "usd", + "description": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "monthly_price": 500, + "monthly_price_id": null, + "name": "Default Product", + "slug": "default-product", + "trial_days": 0, + "type": "paid", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "public", + "welcome_page_url": null, + "yearly_price": 5000, + "yearly_price_id": null, + }, + ], + "title": "Collection update test", + "twitter_description": null, + "twitter_image": null, + "twitter_title": null, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "visibility": "public", + }, + ], +} +`; + +exports[`Posts API Update Can add and remove collections 4: [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": "3992", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-cache-invalidate": StringMatching /\\\\/p\\\\/\\[0-9a-f\\]\\{8\\}-\\[0-9a-f\\]\\{4\\}-\\[0-9a-f\\]\\{4\\}-\\[0-9a-f\\]\\{4\\}-\\[0-9a-f\\]\\{12\\}/, + "x-powered-by": "Express", +} +`; + +exports[`Posts API Update Can add and remove collections 5: [body] 1`] = ` +Object { + "posts": Array [ + Object { + "authors": Any, + "canonical_url": null, + "codeinjection_foot": null, + "codeinjection_head": null, + "collections": Array [ + Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.\\\\d\\{3\\}Z/, + "description": null, + "feature_image": null, + "filter": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "posts": Array [ + Object { + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "sort_order": 0, + }, + ], + "title": "Collection to add.", + "type": "manual", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.\\\\d\\{3\\}Z/, + }, + ], + "comment_id": Any, + "count": Object { + "clicks": 0, + "negative_feedback": 0, + "positive_feedback": 0, + }, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "custom_excerpt": null, + "custom_template": null, + "email": null, + "email_only": false, + "email_segment": "all", + "email_subject": null, + "excerpt": null, + "feature_image": null, + "feature_image_alt": null, + "feature_image_caption": null, + "featured": false, + "frontmatter": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "meta_description": null, + "meta_title": null, + "mobiledoc": "{\\"version\\":\\"0.3.1\\",\\"ghostVersion\\":\\"4.0\\",\\"markups\\":[],\\"atoms\\":[],\\"cards\\":[],\\"sections\\":[[1,\\"p\\",[[0,[],0,\\"\\"]]]]}", + "newsletter": null, + "og_description": null, + "og_image": null, + "og_title": null, + "post_revisions": Any, + "primary_author": Any, + "primary_tag": Any, + "published_at": null, + "slug": "collection-update-test", + "status": "draft", + "tags": Any, + "tiers": Array [ + Object { + "active": true, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "currency": null, + "description": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "monthly_price": null, + "monthly_price_id": null, + "name": "Free", + "slug": "free", + "trial_days": 0, + "type": "free", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "public", + "welcome_page_url": null, + "yearly_price": null, + "yearly_price_id": null, + }, + Object { + "active": true, + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "currency": "usd", + "description": null, + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "monthly_price": 500, + "monthly_price_id": null, + "name": "Default Product", + "slug": "default-product", + "trial_days": 0, + "type": "paid", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "visibility": "public", + "welcome_page_url": null, + "yearly_price": 5000, + "yearly_price_id": null, + }, + ], + "title": "Collection update test", + "twitter_description": null, + "twitter_image": null, + "twitter_title": null, + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + "visibility": "public", + }, + ], +} +`; + +exports[`Posts API Update Can add and remove collections 6: [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": "3989", + "content-type": "application/json; charset=utf-8", + "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-cache-invalidate": StringMatching /\\\\/p\\\\/\\[0-9a-f\\]\\{8\\}-\\[0-9a-f\\]\\{4\\}-\\[0-9a-f\\]\\{4\\}-\\[0-9a-f\\]\\{4\\}-\\[0-9a-f\\]\\{12\\}/, + "x-powered-by": "Express", +} +`; + exports[`Posts API Update Can update a post with lexical 1: [body] 1`] = ` Object { "posts": Array [ diff --git a/ghost/core/test/e2e-api/admin/posts.test.js b/ghost/core/test/e2e-api/admin/posts.test.js index c3a76a5285..700d8346d9 100644 --- a/ghost/core/test/e2e-api/admin/posts.test.js +++ b/ghost/core/test/e2e-api/admin/posts.test.js @@ -419,6 +419,80 @@ describe('Posts API', function () { mobiledocRevisions.length.should.equal(0); }); + + it('Can add and remove collections', async function () { + const {body: postBody} = await agent + .post('/posts/') + .body({ + posts: [{ + title: 'Collection update test' + }] + }) + .expectStatus(201) + .matchBodySnapshot({ + posts: [Object.assign({}, matchPostShallowIncludes, {published_at: null})] + }) + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag, + location: anyLocationFor('posts') + }); + + const [postResponse] = postBody.posts; + + const {body: { + collections: [collectionToAdd] + }} = await agent + .post('/collections/') + .body({ + collections: [{ + title: 'Collection to add.' + }] + }); + + const {body: { + collections: [collectionToRemove] + }} = await agent + .post('/collections/') + .body({ + collections: [{ + title: 'Collection to remove.' + }] + }); + + const collectionMatcher = { + id: anyObjectId, + created_at: stringMatching(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/), + updated_at: stringMatching(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/), + posts: [{ + id: anyObjectId + }] + }; + + await agent.put(`/posts/${postResponse.id}/`) + .body({posts: [Object.assign({}, postResponse, {collections: [collectionToRemove.id]})]}) + .expectStatus(200) + .matchBodySnapshot({ + posts: [Object.assign({}, matchPostShallowIncludes, {published_at: null}, {collections: [collectionMatcher]})] + }) + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag, + 'x-cache-invalidate': stringMatching(/\/p\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/) + }); + + await agent.put(`/posts/${postResponse.id}/`) + .body({posts: [Object.assign({}, postResponse, {collections: [collectionToAdd.id]})]}) + .expectStatus(200) + .matchBodySnapshot({ + posts: [Object.assign({}, matchPostShallowIncludes, {published_at: null}, {collections: [collectionMatcher]})] + }) + .matchHeaderSnapshot({ + 'content-version': anyContentVersion, + etag: anyEtag, + 'x-cache-invalidate': stringMatching(/\/p\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/) + }); + }); }); describe('Delete', function () { diff --git a/ghost/posts-service/lib/PostsService.js b/ghost/posts-service/lib/PostsService.js index e201fda9be..a432477efc 100644 --- a/ghost/posts-service/lib/PostsService.js +++ b/ghost/posts-service/lib/PostsService.js @@ -23,6 +23,7 @@ class PostsService { this.stats = stats; this.emailService = emailService; this.postsExporter = postsExporter; + /** @type {import('@tryghost/collections').CollectionsService} */ this.collectionsService = collectionsService; } @@ -44,7 +45,18 @@ class PostsService { return dto; } - async editPost(frame) { + /** + * @typedef {'published_updated' | 'scheduled_updated' | 'draft_updated' | 'unpublished'} EventString + */ + + /** + * + * @param {any} frame + * @param {object} [options] + * @param {(event: EventString, dto: any) => Promise | void} [options.eventHandler] - Called before the editPost method resolves with an event string + * @returns + */ + async editPost(frame, options) { // Make sure the newsletter is matching an active newsletter // Note that this option is simply ignored if the post isn't published or scheduled if (frame.options.newsletter && frame.options.email_segment) { @@ -61,6 +73,49 @@ class PostsService { } } + if (this.isSet('collections') && frame.data.posts[0].collections) { + const existingCollections = await this.collectionsService.getCollectionsForPost(frame.options.id); + for (const collection of frame.data.posts[0].collections) { + let collectionId = null; + if (typeof collection === 'string') { + collectionId = collection; + } + if (typeof collection?.id === 'string') { + collectionId = collection.id; + } + if (!collectionId) { + continue; + } + const existingCollection = existingCollections.find(c => c.id === collectionId); + if (existingCollection) { + continue; + } + const found = await this.collectionsService.getById(collectionId); + if (!found) { + continue; + } + if (found.type !== 'manual') { + continue; + } + await this.collectionsService.addPostToCollection(collectionId, { + id: frame.options.id, + featured: frame.data.posts[0].featured, + published_at: frame.data.posts[0].published_at + }); + } + for (const existingCollection of existingCollections) { + if (frame.data.posts[0].collections.find((item) => { + if (typeof item === 'string') { + return item === existingCollection.id; + } + return item.id === existingCollection.id; + })) { + continue; + } + await this.collectionsService.removePostFromCollection(existingCollection.id, frame.options.id); + } + } + const model = await this.models.Post.edit(frame.data.posts[0], frame.options); /**Handle newsletter email */ @@ -82,7 +137,40 @@ class PostsService { } } - return model; + const dto = model.toJSON(frame.options); + + if (this.isSet('collections')) { + if (frame?.original?.query?.include?.includes('collections') || frame.data.posts[0].collections) { + dto.collections = await this.collectionsService.getCollectionsForPost(model.id); + } + } + + if (typeof options?.eventHandler === 'function') { + await options.eventHandler(this.getChanges(model), dto); + } + + return dto; + } + /** + * @param {any} model + * @returns {EventString} + */ + getChanges(model) { + if (model.get('status') === 'published' && model.wasChanged()) { + return 'published_updated'; + } + + if (model.get('status') === 'draft' && model.previous('status') === 'published') { + return 'unpublished'; + } + + if (model.get('status') === 'draft' && model.previous('status') !== 'published') { + return 'draft_updated'; + } + + if (model.get('status') === 'scheduled' && model.wasChanged()) { + return 'scheduled_updated'; + } } #mergeFilters(...filters) {