diff --git a/ghost/collections/src/CollectionsService.ts b/ghost/collections/src/CollectionsService.ts index 1242356d33..9f7e8b9928 100644 --- a/ghost/collections/src/CollectionsService.ts +++ b/ghost/collections/src/CollectionsService.ts @@ -108,6 +108,7 @@ type QueryOptions = { interface PostsRepository { getAll(options: QueryOptions): Promise; + getAllIds(): Promise; } export class CollectionsService { @@ -128,8 +129,8 @@ export class CollectionsService { this.slugService = deps.slugService; } - private toDTO(collection: Collection): CollectionDTO { - return { + private async toDTO(collection: Collection): Promise { + const dto = { id: collection.id, title: collection.title, slug: collection.slug, @@ -144,6 +145,14 @@ export class CollectionsService { sort_order: index })) }; + if (collection.slug === 'latest') { + const allPostIds = await this.postsRepository.getAllIds(); + dto.posts = allPostIds.map((id, index) => ({ + id, + sort_order: index + })); + } + return dto; } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -580,11 +589,9 @@ export class CollectionsService { 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)); - } + const collectionsDTOs: CollectionDTO[] = await Promise.all( + collections.map(collection => this.toDTO(collection)) + ); return { data: collectionsDTOs, @@ -602,14 +609,13 @@ export class CollectionsService { } async getCollectionsForPost(postId: string): Promise { const collections = await this.collectionsRepository.getAll({ - filter: `posts:${postId}` + filter: `posts:${postId},slug:latest` }); - return collections.map(collection => this.toDTO(collection)) - .sort((a, b) => { - // NOTE: sorting is here to keep DB engine ordering consistent - return a.slug.localeCompare(b.slug); - }); + return Promise.all(collections.sort((a, b) => { + // NOTE: sorting is here to keep DB engine ordering consistent + return a.slug.localeCompare(b.slug); + }).map(collection => this.toDTO(collection))); } async destroy(id: string): Promise { diff --git a/ghost/collections/test/collections.test.ts b/ghost/collections/test/collections.test.ts index de029b85f9..f466058087 100644 --- a/ghost/collections/test/collections.test.ts +++ b/ghost/collections/test/collections.test.ts @@ -166,6 +166,19 @@ describe('CollectionsService', function () { }); }); + describe('latest collection', function () { + it('Includes all posts when fetched directly', async function () { + await collectionsService.createCollection({ + title: 'Latest', + slug: 'latest', + type: 'automatic', + filter: '' + }); + const collection = await collectionsService.getBySlug('latest'); + assert(collection?.posts.length === 4); + }); + }); + describe('edit', function () { it('Can edit existing collection', async function () { const savedCollection = await collectionsService.createCollection({ diff --git a/ghost/collections/test/fixtures/PostsRepositoryInMemory.ts b/ghost/collections/test/fixtures/PostsRepositoryInMemory.ts index 944ff415e4..06936390a5 100644 --- a/ghost/collections/test/fixtures/PostsRepositoryInMemory.ts +++ b/ghost/collections/test/fixtures/PostsRepositoryInMemory.ts @@ -10,4 +10,9 @@ export class PostsRepositoryInMemory extends InMemoryRepository tag.slug) }; } + + async getAllIds() { + const posts = await this.getAll(); + return posts.map(post => post.id); + } } diff --git a/ghost/core/core/server/services/collections/PostsRepository.js b/ghost/core/core/server/services/collections/PostsRepository.js index 2371c60059..84460b49dd 100644 --- a/ghost/core/core/server/services/collections/PostsRepository.js +++ b/ghost/core/core/server/services/collections/PostsRepository.js @@ -4,6 +4,11 @@ class PostsRepository { this.moment = moment; } + async getAllIds() { + const rows = await this.models.Post.query().select('id').where('type', 'post'); + + return rows.map(row => row.id); + } async getAll({filter, transaction}) { const {data: models} = await this.models.Post.findPage({ filter: `(${filter})+type:post`, diff --git a/ghost/core/test/e2e-api/admin/collections.test.js b/ghost/core/test/e2e-api/admin/collections.test.js index 8486a22509..1bb315c808 100644 --- a/ghost/core/test/e2e-api/admin/collections.test.js +++ b/ghost/core/test/e2e-api/admin/collections.test.js @@ -572,7 +572,7 @@ describe('Collections API', function () { }, this.skip.bind(this)); const collectionRelatedQueries = queries.filter(query => query.sql.includes('collection')); - assert.equal(collectionRelatedQueries.length, 12); + assert.equal(collectionRelatedQueries.length, 7); } await agent