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:
parent
db1c4e6381
commit
ddc23b3467
3 changed files with 128 additions and 4 deletions
|
@ -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}`
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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(',')}]`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue