0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Added collection's posts fetching to collections lib

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

- This is a building block to allow paging, querying etc. of the posts belonging to a collection
This commit is contained in:
Naz 2023-06-13 13:50:27 +07:00 committed by naz
parent db1c4e6381
commit ddc23b3467
3 changed files with 128 additions and 4 deletions

View file

@ -1,12 +1,16 @@
import {Collection} from './Collection';
import {CollectionRepository} from './CollectionRepository';
import tpl from '@tryghost/tpl';
import {MethodNotAllowedError} from '@tryghost/errors';
import {MethodNotAllowedError, NotFoundError} from '@tryghost/errors';
const messages = {
cannotDeleteBuiltInCollectionError: {
message: 'Cannot delete builtin collection',
context: 'The collection {id} is a builtin collection and cannot be deleted'
},
collectionNotFound: {
message: 'Collection not found',
context: 'Collection with id: {id} does not exist'
}
};
@ -20,6 +24,14 @@ type CollectionPostDTO = {
sort_order: number;
};
type CollectionPostListItemDTO = {
id: string;
slug: string;
title: string;
featured: boolean;
featured_image?: string;
}
type ManualCollection = {
title: string;
type: 'manual';
@ -61,8 +73,16 @@ type CollectionPostInputDTO = {
published_at: Date;
};
type QueryOptions = {
filter?: string;
include?: string;
page?: number;
limit?: number;
}
interface PostsRepository {
getAll(options: {filter?: string}): Promise<any[]>;
getAll(options: QueryOptions): Promise<any[]>;
getBulk(ids: string[]): Promise<any[]>;
}
export class CollectionsService {
@ -92,6 +112,16 @@ export class CollectionsService {
};
}
private toCollectionPostListItemDTO(post: any): CollectionPostListItemDTO {
return {
id: post.id,
slug: post.slug,
title: post.title,
featured: post.featured,
featured_image: post.featureImage
};
}
private fromDTO(data: any): any {
const mappedDTO: {[index: string]:any} = {
title: data.title,
@ -208,11 +238,17 @@ export class CollectionsService {
return await this.collectionsRepository.getById(id);
}
async getAll(options?: any): Promise<{data: Collection[], meta: any}> {
async getAll(options?: QueryOptions): Promise<{data: CollectionDTO[], meta: any}> {
const collections = await this.collectionsRepository.getAll(options);
const collectionsDTOs: CollectionDTO[] = [];
for (const collection of collections) {
collectionsDTOs.push(this.toDTO(collection));
}
return {
data: collections,
data: collectionsDTOs,
meta: {
pagination: {
page: 1,
@ -226,6 +262,38 @@ export class CollectionsService {
};
}
async getAllPosts(id: string, {limit = 15, page = 1}: QueryOptions): Promise<{data: CollectionPostListItemDTO[], meta: any}> {
const collection = await this.getById(id);
if (!collection) {
throw new NotFoundError({
message: tpl(messages.collectionNotFound.message),
context: tpl(messages.collectionNotFound.context, {id})
});
}
const startIdx = limit * (page - 1);
const endIdx = limit * page;
const postIds = collection.posts.slice(startIdx, endIdx);
const posts = await this.postsRepository.getBulk(postIds);
const postsDTOs = posts.map(this.toCollectionPostListItemDTO);
return {
data: postsDTOs,
meta: {
pagination: {
page: page,
pages: Math.ceil(collection.posts.length / limit),
limit: limit,
total: posts.length,
prev: null,
next: null
}
}
};
}
async getCollectionsForPost(postId: string): Promise<CollectionDTO[]> {
const collections = await this.collectionsRepository.getAll({
filter: `posts:${postId}`

View file

@ -96,6 +96,56 @@ describe('CollectionsService', function () {
});
});
describe('getAllPosts', function () {
it('Can get paged posts of a collection', async function () {
const collection = await collectionsService.createCollection({
title: 'testing paging',
type: 'manual'
});
for (const post of posts) {
await collectionsService.addPostToCollection(collection.id, post);
}
const postsPage1 = await collectionsService.getAllPosts(collection.id, {page: 1, limit: 2});
assert.ok(postsPage1, 'Posts should be returned');
assert.equal(postsPage1.meta.pagination.page, 1, 'Page should be 1');
assert.equal(postsPage1.meta.pagination.limit, 2, 'Limit should be 2');
assert.equal(postsPage1.meta.pagination.pages, 2, 'Pages should be 2');
assert.equal(postsPage1.data.length, 2, 'There should be 2 posts');
assert.equal(postsPage1.data[0].id, posts[0].id, 'First post should be the correct one');
assert.equal(postsPage1.data[1].id, posts[1].id, 'Second post should be the correct one');
assert.deepEqual(Object.keys(postsPage1.data[0]), [
'id',
'slug',
'title',
'featured',
'featured_image'
], 'Posts should have only specific attributes');
const postsPage2 = await collectionsService.getAllPosts(collection.id, {page: 2, limit: 2});
assert.ok(postsPage2, 'Posts should be returned');
assert.equal(postsPage2.meta.pagination.page, 2, 'Page should be 2');
assert.equal(postsPage2.meta.pagination.limit, 2, 'Limit should be 2');
assert.equal(postsPage2.meta.pagination.pages, 2, 'Pages should be 2');
assert.equal(postsPage2.data.length, 2, 'There should be 2 posts');
assert.equal(postsPage2.data[0].id, posts[2].id, 'First post should be the correct one');
assert.equal(postsPage2.data[1].id, posts[3].id, 'Second post should be the correct one');
});
it('Throws when trying to get posts of a collection that does not exist', async function () {
await assert.rejects(async () => {
await collectionsService.getAllPosts('fake id', {});
}, (err: any) => {
assert.equal(err.message, 'Collection not found', 'Error message should match');
assert.equal(err.context, 'Collection with id: fake id does not exist', 'Error context should match');
return true;
});
});
});
describe('addPostToCollection', function () {
it('Can add a Post to a Collection', async function () {
const collection = await collectionsService.createCollection({

View file

@ -17,4 +17,10 @@ export class PostsRepositoryInMemory extends InMemoryRepository<string, Collecti
published_at: entity.published_at
};
}
getBulk(ids: string[]): Promise<CollectionPost[]> {
return this.getAll({
filter: `id:[${ids.join(',')}]`
});
}
}