mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
no-issue This is to give serializers access to the expected data properties so that can be used for filtering.
168 lines
5.5 KiB
JavaScript
168 lines
5.5 KiB
JavaScript
const debug = require('ghost-ignition').debug('api:shared:pipeline');
|
|
const Promise = require('bluebird');
|
|
const _ = require('lodash');
|
|
const shared = require('../shared');
|
|
const common = require('../../lib/common');
|
|
const sequence = require('../../lib/promise/sequence');
|
|
|
|
const STAGES = {
|
|
validation: {
|
|
input(apiUtils, apiConfig, apiImpl, frame) {
|
|
debug('stages: validation');
|
|
const tasks = [];
|
|
|
|
// CASE: do validation completely yourself
|
|
if (typeof apiImpl.validation === 'function') {
|
|
debug('validation function call');
|
|
return apiImpl.validation(frame);
|
|
}
|
|
|
|
tasks.push(function doValidation() {
|
|
return shared.validators.handle.input(
|
|
Object.assign({}, apiConfig, apiImpl.validation),
|
|
apiUtils.validators.input,
|
|
frame
|
|
);
|
|
});
|
|
|
|
return sequence(tasks);
|
|
}
|
|
},
|
|
|
|
serialisation: {
|
|
input(apiUtils, apiConfig, apiImpl, frame) {
|
|
debug('stages: input serialisation');
|
|
return shared.serializers.handle.input(
|
|
Object.assign({data: apiImpl.data}, apiConfig),
|
|
apiUtils.serializers.input,
|
|
frame
|
|
);
|
|
},
|
|
output(response, apiUtils, apiConfig, apiImpl, frame) {
|
|
debug('stages: output serialisation');
|
|
return shared.serializers.handle.output(response, apiConfig, apiUtils.serializers.output, frame);
|
|
}
|
|
},
|
|
|
|
permissions(apiUtils, apiConfig, apiImpl, frame) {
|
|
debug('stages: permissions');
|
|
const tasks = [];
|
|
|
|
// CASE: it's required to put the permission key to avoid security holes
|
|
if (!apiImpl.hasOwnProperty('permissions')) {
|
|
return Promise.reject(new common.errors.IncorrectUsageError());
|
|
}
|
|
|
|
// CASE: handle permissions completely yourself
|
|
if (typeof apiImpl.permissions === 'function') {
|
|
debug('permissions function call');
|
|
return apiImpl.permissions(frame);
|
|
}
|
|
|
|
// CASE: skip stage completely
|
|
if (apiImpl.permissions === false) {
|
|
debug('disabled permissions');
|
|
return Promise.resolve();
|
|
}
|
|
|
|
if (typeof apiImpl.permissions === 'object' && apiImpl.permissions.before) {
|
|
tasks.push(function beforePermissions() {
|
|
return apiImpl.permissions.before(frame);
|
|
});
|
|
}
|
|
|
|
tasks.push(function doPermissions() {
|
|
return apiUtils.permissions.handle(
|
|
Object.assign({}, apiConfig, apiImpl.permissions),
|
|
frame
|
|
);
|
|
});
|
|
|
|
return sequence(tasks);
|
|
},
|
|
|
|
query(apiUtils, apiConfig, apiImpl, frame) {
|
|
debug('stages: query');
|
|
|
|
if (!apiImpl.query) {
|
|
return Promise.reject(new common.errors.IncorrectUsageError());
|
|
}
|
|
|
|
return apiImpl.query(frame);
|
|
}
|
|
};
|
|
|
|
const pipeline = (apiController, apiUtils) => {
|
|
const keys = Object.keys(apiController);
|
|
|
|
// 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, key) => {
|
|
const docName = apiController.docName;
|
|
const method = key;
|
|
|
|
const apiImpl = _.cloneDeep(apiController)[key];
|
|
|
|
obj[key] = function wrapper() {
|
|
const apiConfig = {docName, method};
|
|
let options, data, frame;
|
|
|
|
if (arguments.length === 2) {
|
|
data = arguments[0];
|
|
options = arguments[1];
|
|
} else if (arguments.length === 1) {
|
|
options = arguments[0] || {};
|
|
} else {
|
|
options = {};
|
|
}
|
|
|
|
// CASE: http helper already creates it's own frame.
|
|
if (!(options instanceof shared.Frame)) {
|
|
frame = new shared.Frame({
|
|
body: data,
|
|
options: options,
|
|
context: {}
|
|
});
|
|
|
|
frame.configure({
|
|
options: apiImpl.options,
|
|
data: apiImpl.data
|
|
});
|
|
} else {
|
|
frame = options;
|
|
}
|
|
|
|
// CASE: api controller *can* be a single function, but it's not recommended to disable the framework.
|
|
if (typeof apiImpl === 'function') {
|
|
debug('ctrl function call');
|
|
return apiImpl(frame);
|
|
}
|
|
|
|
return Promise.resolve()
|
|
.then(() => {
|
|
return STAGES.validation.input(apiUtils, apiConfig, apiImpl, frame);
|
|
})
|
|
.then(() => {
|
|
return STAGES.serialisation.input(apiUtils, apiConfig, apiImpl, frame);
|
|
})
|
|
.then(() => {
|
|
return STAGES.permissions(apiUtils, apiConfig, apiImpl, frame);
|
|
})
|
|
.then(() => {
|
|
return STAGES.query(apiUtils, apiConfig, apiImpl, frame);
|
|
})
|
|
.then((response) => {
|
|
return STAGES.serialisation.output(response, apiUtils, apiConfig, apiImpl, frame);
|
|
})
|
|
.then(() => {
|
|
return frame.response;
|
|
});
|
|
};
|
|
|
|
Object.assign(obj[key], apiImpl);
|
|
return obj;
|
|
}, {});
|
|
};
|
|
|
|
module.exports = pipeline;
|
|
module.exports.STAGES = STAGES;
|