From 517c406e172b68f96e423eee7da76d9edec6b99b Mon Sep 17 00:00:00 2001 From: "Fabien \"egg\" O'Carroll" Date: Tue, 25 Jul 2023 15:54:22 +0200 Subject: [PATCH] Added Collections Content API The only usecases we need to support at the moment are reading individual collections by ID and by Slug. We can extend this API as we get more usescases in future. --- .../api/endpoints/collections-public.js | 53 +++++++++++++++++++ ghost/core/core/server/api/endpoints/index.js | 4 ++ .../web/api/endpoints/content/routes.js | 3 ++ .../__snapshots__/collections.test.js.snap | 37 +++++++++++++ .../test/e2e-api/content/collections.test.js | 34 ++++++++++++ 5 files changed, 131 insertions(+) create mode 100644 ghost/core/core/server/api/endpoints/collections-public.js create mode 100644 ghost/core/test/e2e-api/content/__snapshots__/collections.test.js.snap create mode 100644 ghost/core/test/e2e-api/content/collections.test.js diff --git a/ghost/core/core/server/api/endpoints/collections-public.js b/ghost/core/core/server/api/endpoints/collections-public.js new file mode 100644 index 0000000000..67a214540a --- /dev/null +++ b/ghost/core/core/server/api/endpoints/collections-public.js @@ -0,0 +1,53 @@ +const errors = require('@tryghost/errors'); +const tpl = require('@tryghost/tpl'); +const collectionsService = require('../../services/collections'); + +const messages = { + collectionNotFound: 'Collection not found.' +}; + +module.exports = { + docName: 'collections', + + readBySlug: { + headers: { + cacheInvalidate: false + }, + data: [ + 'slug' + ], + permissions: true, + async query(frame) { + const model = await collectionsService.api.getBySlug(frame.data.slug); + + if (!model) { + throw new errors.NotFoundError({ + message: tpl(messages.collectionNotFound) + }); + } + + return model; + } + }, + + readById: { + headers: { + cacheInvalidate: false + }, + data: [ + 'id' + ], + permissions: true, + async query(frame) { + const model = await collectionsService.api.getById(frame.data.id); + + if (!model) { + throw new errors.NotFoundError({ + message: tpl(messages.collectionNotFound) + }); + } + + return model; + } + } +}; diff --git a/ghost/core/core/server/api/endpoints/index.js b/ghost/core/core/server/api/endpoints/index.js index 7fe11c12da..e3bbd73888 100644 --- a/ghost/core/core/server/api/endpoints/index.js +++ b/ghost/core/core/server/api/endpoints/index.js @@ -217,6 +217,10 @@ module.exports = { return apiFramework.pipeline(require('./pages-public'), localUtils, 'content'); }, + get collectionsPublic() { + return apiFramework.pipeline(require('./collections-public'), localUtils); + }, + get tagsPublic() { return apiFramework.pipeline(require('./tags-public'), localUtils, 'content'); }, diff --git a/ghost/core/core/server/web/api/endpoints/content/routes.js b/ghost/core/core/server/web/api/endpoints/content/routes.js index de40d537c5..71bbdb705b 100644 --- a/ghost/core/core/server/web/api/endpoints/content/routes.js +++ b/ghost/core/core/server/web/api/endpoints/content/routes.js @@ -39,5 +39,8 @@ module.exports = function apiRoutes() { router.get('/tiers', mw.authenticatePublic, http(api.tiersPublic.browse)); router.get('/offers/:id', mw.authenticatePublic, http(api.offersPublic.read)); + router.get('/collections/:id', mw.authenticatePublic, http(api.collectionsPublic.readById)); + router.get('/collections/slug/:slug', mw.authenticatePublic, http(api.collectionsPublic.readBySlug)); + return router; }; diff --git a/ghost/core/test/e2e-api/content/__snapshots__/collections.test.js.snap b/ghost/core/test/e2e-api/content/__snapshots__/collections.test.js.snap new file mode 100644 index 0000000000..58709651a1 --- /dev/null +++ b/ghost/core/test/e2e-api/content/__snapshots__/collections.test.js.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collections Content API Can request a collection by slug and id 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": "Featured posts", + "feature_image": null, + "filter": "featured:true", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "slug": "featured", + "title": "Featured", + "type": "automatic", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + }, + ], +} +`; + +exports[`Collections Content API Can request a collection by slug and id 2: [body] 1`] = ` +Object { + "collections": Array [ + Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + "description": "Featured posts", + "feature_image": null, + "filter": "featured:true", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "slug": "featured", + "title": "Featured", + "type": "automatic", + "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/, + }, + ], +} +`; diff --git a/ghost/core/test/e2e-api/content/collections.test.js b/ghost/core/test/e2e-api/content/collections.test.js new file mode 100644 index 0000000000..972eb3744d --- /dev/null +++ b/ghost/core/test/e2e-api/content/collections.test.js @@ -0,0 +1,34 @@ +const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework'); +const {anyISODateTime, anyObjectId} = matchers; + +const collectionMatcher = { + id: anyObjectId, + created_at: anyISODateTime, + updated_at: anyISODateTime +}; + +describe('Collections Content API', function () { + let agent; + + before(async function () { + agent = await agentProvider.getContentAPIAgent(); + await fixtureManager.init('users', 'api_keys'); + await agent.authenticate(); + }); + + it('Can request a collection by slug and id', async function () { + const {body: {collections: [collection]}} = await agent + .get(`collections/slug/featured`) + .expectStatus(200) + .matchBodySnapshot({ + collections: [collectionMatcher] + }); + + await agent + .get(`collections/${collection.id}`) + .expectStatus(200) + .matchBodySnapshot({ + collections: [collectionMatcher] + }); + }); +});