diff --git a/spec/features.spec.js b/spec/features.spec.js new file mode 100644 index 0000000000..b0a2745956 --- /dev/null +++ b/spec/features.spec.js @@ -0,0 +1,26 @@ +var features = require('../src/features') + +describe('features', () => { + it('set and get features', (done) => { + features.setFeature('users', { + testOption1: true, + testOption2: false + }); + + var _features = features.getFeatures(); + + var expected = { + testOption1: true, + testOption2: false + }; + + expect(_features.users).toEqual(expected); + done(); + }); + + it('get features that does not exist', (done) => { + var _features = features.getFeatures(); + expect(_features.test).toBeUndefined(); + done(); + }); +}); diff --git a/src/Adapters/Push/ParsePushAdapter.js b/src/Adapters/Push/ParsePushAdapter.js index 3f554054ed..c953d15763 100644 --- a/src/Adapters/Push/ParsePushAdapter.js +++ b/src/Adapters/Push/ParsePushAdapter.js @@ -14,6 +14,10 @@ export class ParsePushAdapter extends PushAdapter { super(pushConfig); this.validPushTypes = ['ios', 'android']; this.senderMap = {}; + // used in PushController for Dashboard Features + this.feature = { + immediatePush: true + }; let pushTypes = Object.keys(pushConfig); for (let pushType of pushTypes) { diff --git a/src/Controllers/AdaptableController.js b/src/Controllers/AdaptableController.js index 902a6eb349..ab7d715667 100644 --- a/src/Controllers/AdaptableController.js +++ b/src/Controllers/AdaptableController.js @@ -18,8 +18,12 @@ export class AdaptableController { this.options = options; this.appId = appId; this.adapter = adapter; + this.setFeature(); } + // sets features for Dashboard to consume from features router + setFeature() {} + set adapter(adapter) { this.validateAdapter(adapter); this[_adapter] = adapter; @@ -67,4 +71,4 @@ export class AdaptableController { } } -export default AdaptableController; \ No newline at end of file +export default AdaptableController; diff --git a/src/Controllers/PushController.js b/src/Controllers/PushController.js index 22d9fe1135..2e2134a6c4 100644 --- a/src/Controllers/PushController.js +++ b/src/Controllers/PushController.js @@ -3,9 +3,16 @@ import PromiseRouter from '../PromiseRouter'; import rest from '../rest'; import AdaptableController from './AdaptableController'; import { PushAdapter } from '../Adapters/Push/PushAdapter'; +import features from '../features'; + +const FEATURE_NAME = 'push'; export class PushController extends AdaptableController { + setFeature() { + features.setFeature(FEATURE_NAME, this.adapter.feature || {}); + } + /** * Check whether the deviceType parameter in qury condition is valid or not. * @param {Object} where A query condition diff --git a/src/Routers/FeaturesRouter.js b/src/Routers/FeaturesRouter.js new file mode 100644 index 0000000000..65ca1b71da --- /dev/null +++ b/src/Routers/FeaturesRouter.js @@ -0,0 +1,32 @@ +import PromiseRouter from '../PromiseRouter'; +import {getFeatures} from '../features'; + +let masterKeyRequiredResponse = () => { + return Promise.resolve({ + status: 401, + response: {error: 'master key not specified'}, + }) +} + +export class FeaturesRouter extends PromiseRouter { + + mountRoutes() { + this.route('GET','/features', (req) => { + return this.handleGET(req); + }); + } + + handleGET(req) { + if (!req.auth.isMaster) { + return masterKeyRequiredResponse(); + } + + return Promise.resolve({ + response: { + results: [getFeatures()] + } + }); + } +} + +export default FeaturesRouter; diff --git a/src/features.js b/src/features.js new file mode 100644 index 0000000000..1048b91cab --- /dev/null +++ b/src/features.js @@ -0,0 +1,107 @@ +/** + * features.js + * Feature config file that holds information on the features that are currently + * available on Parse Server. This is primarily created to work with an UI interface + * like the web dashboard. The list of features will change depending on the your + * app, choice of adapter as well as Parse Server version. This approach will enable + * the dashboard to be built independently and still support these use cases. + * + * + * Default features and feature options are listed in the features object. + * + * featureSwitch is a convenient way to turn on/off features without changing the config + * + * Features that use Adapters should specify the feature options through + * the setFeature method in your controller and feature + * Reference PushController and ParsePushAdapter as an example. + * + * NOTE: When adding new endpoints be sure to update this list both (features, featureSwitch) + * if you are planning to have a UI consume it. + */ + +// default features +let features = { + analytics: { + slowQueries: false, + performanceAnalysis: false, + retentionAnalysis: false, + }, + classes: {}, + files: {}, + functions: {}, + globalConfig: { + create: true, + read: true, + update: true, + delete: true, + }, + hooks: { + create: false, + read: false, + update: false, + delete: false, + }, + iapValidation: {}, + installations: {}, + logs: { + info: true, + error: true, + }, + publicAPI: {}, + push: {}, + roles: {}, + schemas: { + addField: true, + removeField: true, + addClass: true, + removeClass: true, + clearAllDataFromClass: false, + exportClass: false, + }, + sessions: {}, + users: {}, +}; + +// master switch for features +let featuresSwitch = { + analytics: true, + classes: true, + files: true, + functions: true, + globalConfig: true, + hooks: true, + iapValidation: true, + installations: true, + logs: true, + publicAPI: true, + push: true, + roles: true, + schemas: true, + sessions: true, + users: true, +}; + +/** + * set feature config options + */ +function setFeature(key, value) { + features[key] = value; +} + +/** + * get feature config options + */ +function getFeatures() { + let result = {}; + Object.keys(features).forEach((key) => { + if (featuresSwitch[key]) { + result[key] = features[key]; + } + }); + return result; +} + +module.exports = { + getFeatures, + setFeature, +}; diff --git a/src/index.js b/src/index.js index 13af8463fe..b521a26f03 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,7 @@ import ParsePushAdapter from './Adapters/Push/ParsePushAdapter'; import PromiseRouter from './PromiseRouter'; import { AnalyticsRouter } from './Routers/AnalyticsRouter'; import { ClassesRouter } from './Routers/ClassesRouter'; +import { FeaturesRouter } from './Routers/FeaturesRouter'; import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter'; import { FilesController } from './Controllers/FilesController'; import { FilesRouter } from './Routers/FilesRouter'; @@ -207,7 +208,8 @@ function ParseServer({ new SchemasRouter(), new PushRouter(), new LogsRouter(), - new IAPValidationRouter() + new IAPValidationRouter(), + new FeaturesRouter(), ]; if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED || process.env.TESTING) {