Skip to content

Commit

Permalink
Cached api controller pipelines (#19880)
Browse files Browse the repository at this point in the history
ref ENG-761
ref https://linear.app/tryghost/issue/ENG-761

Creating these pipelines is expensive, and we don't want to do it
repeatedly for the same controller. Adding caching should reduce the
amount of time spent setting up pipelines for each usage of the `get`
helper.
  • Loading branch information
allouis authored and royalfig committed Mar 25, 2024
1 parent a56b016 commit ddfdc95
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 12 deletions.
12 changes: 11 additions & 1 deletion ghost/api-framework/lib/pipeline.js
Expand Up @@ -160,6 +160,8 @@ const STAGES = {
}
};

const controllerMap = new Map();

/**
* @description The pipeline runs the request through all stages (validation, serialisation, permissions).
*
Expand All @@ -180,12 +182,16 @@ const STAGES = {
* @return {Object}
*/
const pipeline = (apiController, apiUtils, apiType) => {
if (controllerMap.has(apiController)) {
return controllerMap.get(apiController);
}

const keys = Object.keys(apiController);
const docName = apiController.docName;

// CASE: api controllers are objects with configuration.
// We have to ensure that we expose a functional interface e.g. `api.posts.add` has to be available.
return keys.reduce((obj, method) => {
const result = keys.reduce((obj, method) => {
const apiImpl = _.cloneDeep(apiController)[method];

Object.freeze(apiImpl.headers);
Expand Down Expand Up @@ -267,6 +273,10 @@ const pipeline = (apiController, apiUtils, apiType) => {
Object.assign(obj[method], apiImpl);
return obj;
}, {});

controllerMap.set(apiController, result);

return result;
};

module.exports = pipeline;
Expand Down
Expand Up @@ -33,8 +33,8 @@ describe('Frontend behavior tests', function () {

beforeEach(function () {
sinon.stub(themeEngine.getActive(), 'config').withArgs('posts_per_page').returns(2);
const postsAPI = require('../../../core/server/api/endpoints/posts-public');
postSpy = sinon.spy(postsAPI.browse, 'query');
const postsAPI = require('../../../core/server/api/endpoints').postsPublic;
postSpy = sinon.spy(postsAPI, 'browse');
});

afterEach(function () {
Expand Down Expand Up @@ -143,22 +143,20 @@ describe('Frontend behavior tests', function () {
});
});

it('serve tag', function () {
it('serve tag', async function () {
const req = {
method: 'GET',
url: '/tag/bacon/',
host: 'example.com'
};

return localUtils.mockExpress.invoke(app, req)
.then(function (response) {
response.statusCode.should.eql(200);
response.template.should.eql('tag');
const response = await localUtils.mockExpress.invoke(app, req);
response.statusCode.should.eql(200);
response.template.should.eql('tag');

postSpy.args[0][0].options.filter.should.eql('(tags:\'bacon\'+tags.visibility:public)+type:post');
postSpy.args[0][0].options.page.should.eql(1);
postSpy.args[0][0].options.limit.should.eql(2);
});
postSpy.args[0][0].filter.should.eql('tags:\'bacon\'+tags.visibility:public');
postSpy.args[0][0].page.should.eql(1);
postSpy.args[0][0].limit.should.eql(2);
});

it('serve tag rss', function () {
Expand Down

0 comments on commit ddfdc95

Please sign in to comment.