0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-04 02:01:58 -05:00

Added DEL /collections/:id/posts/:post_id to Admin API

refs https://github.com/TryGhost/Team/issues/3260

- We need a way to remove posts form collections without fetching the whole collection's content. This API method allows to remove posts from manual collections by collection id and post id.
- As a response it returns up to date collection state without the removed post.
This commit is contained in:
Naz 2023-06-01 14:57:47 +07:00
parent 2edaf2c42c
commit e8220b1387
No known key found for this signature in database
9 changed files with 238 additions and 1 deletions

View file

@ -45,6 +45,12 @@ export class Collection {
this.posts.splice(index, 0, post.id);
}
removePost(id: string) {
if (this.posts.includes(id)) {
this._posts = this.posts.filter(postId => postId !== id);
}
}
private constructor(data: any) {
this.id = data.id;
this.title = data.title;

View file

@ -175,4 +175,19 @@ export class CollectionsService {
return collection;
}
async removePostFromCollection(id: string, postId: string): Promise<CollectionDTO | null> {
const collection = await this.getById(id);
if (!collection) {
return null;
}
if (collection) {
collection.removePost(postId);
await this.collectionsRepository.save(collection);
}
return this.toDTO(collection);
}
}

View file

@ -138,4 +138,22 @@ describe('Collection', function () {
assert(collection.posts.length as number === 4);
assert(collection.posts[collection.posts.length - 2] === '3');
});
it('Removes a post by id', async function () {
const collection = await Collection.create({
title: 'Testing adding posts'
});
assert.equal(collection.posts.length, 0);
collection.addPost({
id: '0'
});
assert.equal(collection.posts.length, 1);
collection.removePost('0');
assert.equal(collection.posts.length, 0);
});
});

View file

@ -117,5 +117,34 @@ describe('CollectionsService', function () {
assert.equal(editedCollection?.posts[0].id, posts[0].id, 'Collection should have the correct post');
assert.equal(editedCollection?.posts[0].sort_order, 0, 'Collection should have the correct post sort order');
});
it('Removes a Post from a Collection', async function () {
const collection = await collectionsService.createCollection({
title: 'testing collections',
description: 'testing collections description',
type: 'manual'
});
let editedCollection = await collectionsService.edit({
id: collection.id,
posts: [{
id: posts[0].id
}, {
id: posts[1].id
}]
});
assert.equal(editedCollection?.posts.length, 2, 'Collection should have two posts');
editedCollection = await collectionsService.removePostFromCollection(collection.id, posts[0].id);
assert.equal(editedCollection?.posts.length, 1, 'Collection should have one posts');
});
it('Returns null when removing post from non existing collection', async function () {
const collection = await collectionsService.removePostFromCollection('i-do-not-exist', posts[0].id);
assert.equal(collection, null, 'Collection should be null');
});
});
});

View file

@ -150,5 +150,40 @@ module.exports = {
async query(frame) {
return await collectionsService.api.destroy(frame.options.id);
}
},
destroyPost: {
docName: 'collection_posts',
statusCode: 200,
headers: {
cacheInvalidate: true
},
options: [
'id',
'post_id'
],
validation: {
options: {
id: {
required: true
},
post_id: {
required: true
}
}
},
// @NOTE: should have permissions when moving out of Alpha
permissions: false,
async query(frame) {
const collection = await collectionsService.api.destroyCollectionPost(frame.options.id, frame.options.post_id);
if (!collection) {
throw new errors.NotFoundError({
message: tpl(messages.collectionNotFound)
});
}
return collection;
}
}
};

View file

@ -19,7 +19,8 @@ class CollectionsServiceWrapper {
add: collectionsService.createCollection.bind(collectionsService),
edit: collectionsService.edit.bind(collectionsService),
addPost: collectionsService.addPostToCollection.bind(collectionsService),
destroy: collectionsService.destroy.bind(collectionsService)
destroy: collectionsService.destroy.bind(collectionsService),
destroyCollectionPost: collectionsService.removePostFromCollection.bind(collectionsService)
};
}
}

View file

@ -25,6 +25,7 @@ module.exports = function apiRoutes() {
router.put('/collections/:id', mw.authAdminApi, labs.enabledMiddleware('collections'), http(api.collections.edit));
router.del('/collections/:id', mw.authAdminApi, labs.enabledMiddleware('collections'), http(api.collections.destroy));
router.post('/collections/:id/posts', mw.authAdminApi, labs.enabledMiddleware('collections'), http(api.collections.addPost));
router.del('/collections/:id/posts/:post_id', mw.authAdminApi, labs.enabledMiddleware('collections'), http(api.collections.destroyPost));
// ## Configuration
router.get('/config', mw.authAdminApi, http(api.config.read));

View file

@ -610,6 +610,111 @@ Object {
}
`;
exports[`Collections API edit Can remove a Post from a Collection 1: [body] 1`] = `
Object {
"collections": Array [
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"feature_image": null,
"filter": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"posts": Array [
Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"sort_order": Any<Number>,
},
Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"sort_order": Any<Number>,
},
Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"sort_order": Any<Number>,
},
Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"sort_order": Any<Number>,
},
],
"title": "Test Collection Edited",
"type": "manual",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
},
],
}
`;
exports[`Collections API edit Can remove a Post from a Collection 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": "440",
"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-powered-by": "Express",
}
`;
exports[`Collections API edit Can remove a Post from a Collection 3: [body] 1`] = `
Object {
"collections": Array [
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"feature_image": null,
"filter": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"posts": Array [
Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"sort_order": Any<Number>,
},
Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"sort_order": Any<Number>,
},
Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"sort_order": Any<Number>,
},
],
"title": "Test Collection Edited",
"type": "manual",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
},
],
}
`;
exports[`Collections API edit Can remove a Post from a Collection 3: [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-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Version, Origin",
"x-cache-invalidate": "/*",
"x-powered-by": "Express",
}
`;
exports[`Collections API edit Can remove a Post from a Collection 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": "391",
"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": "/*",
"x-powered-by": "Express",
}
`;
exports[`Collections API edit Fails to edit unexistent Collection 1: [body] 1`] = `
Object {
"errors": Array [

View file

@ -256,6 +256,33 @@ describe('Collections API', function () {
assert.equal(readResponse.body.collections[0].posts.length, 4, 'Post should have been added to a Collection');
});
it('Can remove a Post from a Collection', async function () {
const collectionId = collectionToEdit.id;
const readResponse = await agent
.get(`/collections/${collectionId}/`)
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
collections: [buildMatcher(4, {withSortOrder: true})]
});
const postIdToRemove = readResponse.body.collections[0].posts[0]?.id;
await agent
.delete(`/collections/${collectionId}/posts/${postIdToRemove}`)
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
collections: [buildMatcher(3, {withSortOrder: true})]
});
});
});
it('Can delete a Collection', async function () {