@lutzklai/pavo 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,54 @@
1
+ /* eslint-disable global-require */
2
+
3
+ const statusCodes = require('http-status-codes');
4
+
5
+ const app = require('./src/app');
6
+ const appEvents = require('./src/appEvents');
7
+ const appTest = require('./src/appTest');
8
+ const bootstrap = require('./src/bootstrap');
9
+ const moduleFinder = require('./src/moduleFinder');
10
+ const authStrategyHelper = require('./src/helperUtils/authStrategyHelper');
11
+ const diHelper = require('./src/helperUtils/diHelper');
12
+ const loggerHelper = require('./src/helperUtils/loggerHelper');
13
+ const moduleBootstrap = require('./src/module/moduleBootstrap');
14
+ const moduleBootstrapTest = require('./src/module/moduleBootstrapTest');
15
+ const modulePlugins = require('./src/module/modulePlugins');
16
+ const moduleTesting = require('./src/module/moduleTesting');
17
+
18
+ /**
19
+ * Injects items into the given dependency injector.
20
+ *
21
+ * @param {{register: function}} diContainer
22
+ */
23
+ function inject(diContainer) {
24
+ const previousSource = diContainer.setRegisterSource(__filename);
25
+
26
+ const pavoStatusCodes = Object.assign({}, statusCodes);
27
+ pavoStatusCodes.$filename = __filename;
28
+
29
+ diContainer.register('usingPavo', true, true, {filename: __filename});
30
+ diContainer.register('pavoAuthStrategyHelper', authStrategyHelper);
31
+ diContainer.register('statusCodes', pavoStatusCodes);
32
+
33
+ diContainer.setRegisterSource(previousSource);
34
+ }
35
+
36
+ module.exports = {
37
+ app,
38
+ appEvents,
39
+ appTest,
40
+ bootstrap,
41
+ inject,
42
+ module: {
43
+ bootstrap: moduleBootstrap,
44
+ testing: moduleTesting,
45
+ testingSetup: moduleBootstrapTest
46
+ },
47
+ moduleFinder,
48
+ plugins: modulePlugins,
49
+ utils: {
50
+ authStrategyHelper,
51
+ diHelper,
52
+ loggerHelper
53
+ }
54
+ };
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@lutzklai/pavo",
3
+ "version": "0.0.10",
4
+ "description": "The Clevyr Pavo API framework.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "Project Content <admin@projectcontent.com>",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "@lutzklai/functional-di": "^0.3.3",
13
+ "async-eventemitter": "^0.2.4",
14
+ "colors": "^1.1.2",
15
+ "dataloader": "^1.4.0",
16
+ "http-status-codes": "^1.3.0",
17
+ "lodash": "^4.17.4",
18
+ "winston": "^2.4.0",
19
+ "winston-rollbar2": "^2.1.4"
20
+ },
21
+ "publishConfig": {
22
+ "registry": "https://registry.npmjs.org/",
23
+ "access": "public"
24
+ }
25
+ }
package/src/app.js ADDED
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ const bootstrap = require('./bootstrap');
4
+
5
+ /**
6
+ * The Pavo app.
7
+ *
8
+ * @param {string} projectBaseDir
9
+ * @param {{inject: function}} appDiContainer
10
+ * @param {boolean} isTesting
11
+ * @returns {Promise}
12
+ */
13
+ function pavoApp(projectBaseDir, appDiContainer, isTesting) {
14
+ let pavoAppServer = null;
15
+
16
+ /**
17
+ * Allows the application to retrieve the server object after initialization.
18
+ * This must be registered in the application diContainer before the app is bootstrapped!
19
+ *
20
+ * @returns {{}}
21
+ */
22
+ function pavoAppServerFactory() {
23
+ return pavoAppServer;
24
+ }
25
+
26
+ pavoAppServerFactory.$singleton = true;
27
+ pavoAppServerFactory.$filename = __filename;
28
+ appDiContainer.register('pavoAppServer', pavoAppServerFactory);
29
+
30
+ // Bootstrap the application.
31
+ return bootstrap(projectBaseDir, appDiContainer, isTesting).then(function startServer() {
32
+ // By sending true, we tell the server to start up.
33
+ const resolvedServer = appDiContainer.resolve('server', {shouldStart: true});
34
+
35
+ if (resolvedServer && resolvedServer.then) {
36
+ resolvedServer.then((setup) => {
37
+ pavoAppServer = setup;
38
+ });
39
+ } else {
40
+ pavoAppServer = resolvedServer;
41
+ }
42
+ });
43
+ }
44
+
45
+ module.exports = pavoApp;
@@ -0,0 +1,131 @@
1
+ 'use strict';
2
+
3
+ const AsyncEventEmitter = require('async-eventemitter');
4
+
5
+ /**
6
+ * The PavoEvents object.
7
+ * Triggers and processes events during the setup process of the app server.
8
+ */
9
+ class PavoEvents extends AsyncEventEmitter {
10
+ /**
11
+ * Defines the class properties.
12
+ */
13
+ constructor() {
14
+ super();
15
+
16
+ /* The Pavo Events. */
17
+ this.EVENT_BEFORE_MIDDLEWARE = 'EVENT_BEFORE_MIDDLEWARE';
18
+ this.EVENT_PROCESS_MIDDLEWARE = 'EVENT_PROCESS_MIDDLEWARE';
19
+ this.EVENT_PROCESS_AUTH_STRATEGIES = 'EVENT_PROCESS_AUTH_STRATEGIES';
20
+ this.EVENT_AFTER_MIDDLEWARE = 'EVENT_AFTER_MIDDLEWARE';
21
+ this.EVENT_PROCESS_MODULES = 'EVENT_PROCESS_MODULES';
22
+ this.EVENT_BEFORE_START = 'EVENT_BEFORE_START';
23
+ this.EVENT_AFTER_START = 'EVENT_AFTER_START';
24
+ }
25
+
26
+ /**
27
+ * Emits an event and returns a promise after all listeners have finished.
28
+ *
29
+ * @param {string} event
30
+ * @param {*} data
31
+ * @returns {Promise}
32
+ */
33
+ emitPromise(event, data) {
34
+ if (!this[event] || String(this[event]) !== event) {
35
+ return Promise.reject(new Error(
36
+ `Invalid event '${event}' given to app event system. Check pavo.appEvents for valid events.`
37
+ ));
38
+ }
39
+
40
+ return new Promise((resolve, reject) => {
41
+ this.emit(event, data, (error, response) => {
42
+ if (error) {
43
+ reject(error);
44
+ } else {
45
+ resolve(response);
46
+ }
47
+ });
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Processes the given handler before the app middleware is processed.
53
+ *
54
+ * @param {function} handler
55
+ * @returns {Promise}
56
+ */
57
+ onBeforeMiddleware(handler) {
58
+ return this.on(this.EVENT_BEFORE_MIDDLEWARE, handler);
59
+ }
60
+
61
+ /**
62
+ * Processes the given handler when the middlewares are ready to be processed.
63
+ *
64
+ * @param {function} handler
65
+ * @returns {Promise}
66
+ */
67
+ onProcessMiddleware(handler) {
68
+ return this.on(this.EVENT_PROCESS_MIDDLEWARE, handler);
69
+ }
70
+
71
+ /**
72
+ * Processes the given handler when the authentication strategies are ready to be processed.
73
+ *
74
+ * @param {function} handler
75
+ * @returns {Promise}
76
+ */
77
+ onProcessAuthStrategies(handler) {
78
+ return this.on(this.EVENT_PROCESS_AUTH_STRATEGIES, handler);
79
+ }
80
+
81
+ /**
82
+ * Processes the given handler after the app middleware is processed.
83
+ *
84
+ * @param {function} handler
85
+ * @returns {Promise}
86
+ */
87
+ onAfterMiddleware(handler) {
88
+ return this.on(this.EVENT_AFTER_MIDDLEWARE, handler);
89
+ }
90
+
91
+ /**
92
+ * Processes the given handler when the modules are ready to be processed.
93
+ *
94
+ * @param {function} handler
95
+ * @returns {Promise}
96
+ */
97
+ onProcessModules(handler) {
98
+ return this.on(this.EVENT_PROCESS_MODULES, handler);
99
+ }
100
+
101
+ /**
102
+ * Processes the given handler before the app server is started.
103
+ *
104
+ * @param {function} handler
105
+ * @returns {Promise}
106
+ */
107
+ onBeforeStart(handler) {
108
+ return this.on(this.EVENT_BEFORE_START, handler);
109
+ }
110
+
111
+ /**
112
+ * Processes the given handler after the app server has been started.
113
+ *
114
+ * @param {function} handler
115
+ * @returns {Promise}
116
+ */
117
+ onAfterStart(handler) {
118
+ return this.on(this.EVENT_AFTER_START, handler);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Initializes a new PavoEvents object.
124
+ *
125
+ * @returns {PavoEvents}
126
+ */
127
+ function pavoEventsFactory() {
128
+ return new PavoEvents();
129
+ }
130
+
131
+ module.exports = pavoEventsFactory;
package/src/appTest.js ADDED
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const bootstrap = require('./bootstrap');
4
+
5
+ /**
6
+ * Initializes the app for testing purposes.
7
+ *
8
+ * @param {string} baseDir
9
+ * @param {function} appDiFactory
10
+ * @returns {function}
11
+ */
12
+ function initAppForTesting(baseDir, appDiFactory) {
13
+ /**
14
+ * Initializes the application for testing purposes.
15
+ *
16
+ * @param {string=} modName
17
+ * @param {Object=} testingConfig
18
+ * @returns {Promise<Object.<string, Object>>} Promise that returns the Hapi server.
19
+ */
20
+ return function testApplication(modName, testingConfig) {
21
+ // First setup the application dependency injector.
22
+ const appDiContainer = appDiFactory();
23
+
24
+ // Define any testing setup information
25
+ appDiContainer.register('testingConfig', testingConfig || {});
26
+
27
+ // Bootstrap the application.
28
+ return bootstrap(baseDir, appDiContainer, true).then(function getTestingServer(diContainers) {
29
+ if (!diContainers[modName]) {
30
+ throw new Error(`Bootstrap did not return DI container for module '${modName}'.`);
31
+ }
32
+
33
+ return {
34
+ [modName]: diContainers[modName],
35
+ app: diContainers.app,
36
+ mod: diContainers[modName]
37
+ }
38
+ });
39
+ }
40
+ }
41
+
42
+ module.exports = initAppForTesting;
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const lodash = require('lodash');
4
+
5
+ const moduleFinder = require('./moduleFinder.js');
6
+
7
+ /**
8
+ * Bootstraps the application by initializing the dependency injector, loading each module,
9
+ * and registering the module plugins with the DI.
10
+ *
11
+ * @param {string} baseDir
12
+ * @param {Object} appDiContainer
13
+ * @param {boolean} isTesting
14
+ * @returns {Promise<Object.<string, Object>>} A promise that returns the application DI container.
15
+ */
16
+ function bootstrapApp(baseDir, appDiContainer, isTesting) {
17
+ let allModDiContainers = {};
18
+ let allModServices = {};
19
+ let moduleLoaders = [];
20
+ let modulePlugins = {};
21
+
22
+ // Module finder will auto detect each module and return each of their loaders.
23
+ return moduleFinder(baseDir).then(function afterModulesFound(foundModuleFactories) {
24
+ // Up the event maxListeners cap to 2 per modules found.
25
+ // This prevents sequelize from throwing a warning.
26
+ process.setMaxListeners(foundModuleFactories.length * 2);
27
+
28
+ moduleLoaders = foundModuleFactories.map(function startEachModule(moduleFactory) {
29
+ return {
30
+ directory: moduleFactory.directory,
31
+ module: moduleFactory.module()
32
+ };
33
+ });
34
+
35
+ /**
36
+ * Order of module setup operation:
37
+ * 1. initModule (Sync)
38
+ * 2. loadModuleServices (Sync)
39
+ * 3. registerAppServices (Sync)
40
+ * 4. configureModuleTesting (Async)
41
+ * 5. resolveModulePlugins (Sync)
42
+ */
43
+
44
+ moduleLoaders = moduleLoaders.map((moduleLoader) => {
45
+ const modDiContainers = moduleLoader.module.initModule(appDiContainer, moduleLoader.directory);
46
+
47
+ if (modDiContainers) {
48
+ allModDiContainers = Object.assign({}, allModDiContainers, modDiContainers);
49
+ }
50
+
51
+ return moduleLoader.module;
52
+ });
53
+
54
+ moduleLoaders.forEach((moduleLoader) => {
55
+ const modServices = moduleLoader.loadModuleServices();
56
+
57
+ // Store each service into a collection of all module services.
58
+ if (modServices) {
59
+ allModServices = Object.assign({}, allModServices, modServices);
60
+ }
61
+ });
62
+
63
+ // Register the mod services with the app di container.
64
+ lodash.forEach(allModServices, function registerNewServices(service, serviceName) {
65
+ appDiContainer.register(serviceName, service);
66
+ });
67
+
68
+ moduleLoaders.forEach((moduleLoader) => {
69
+ moduleLoader.registerAppServices(allModServices);
70
+ });
71
+
72
+ if (isTesting) {
73
+ return Promise.all(moduleLoaders.map((moduleLoader) => {
74
+ return moduleLoader.configureModuleTesting();
75
+ }));
76
+ }
77
+
78
+ return true;
79
+ }).then(function afterModulesAreConfigured() {
80
+ moduleLoaders.forEach((moduleLoader) => {
81
+ const newPlugins = moduleLoader.resolveModulePlugins();
82
+
83
+ if (newPlugins) {
84
+ modulePlugins = Object.assign({}, modulePlugins, newPlugins);
85
+ }
86
+ });
87
+
88
+ const modulePluginsFactory = function modulePluginsFactory() {
89
+ return lodash.values(modulePlugins);
90
+ };
91
+ modulePluginsFactory.$filename = __filename;
92
+
93
+ appDiContainer.register('modulePlugins', modulePluginsFactory);
94
+
95
+ return Object.assign({}, allModDiContainers, {
96
+ app: appDiContainer
97
+ });
98
+ });
99
+ }
100
+
101
+ module.exports = bootstrapApp;
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Builds the authStrategyHelper.
5
+ *
6
+ * @returns {{}}
7
+ */
8
+ function authStrategyHelperFactory() {
9
+ /**
10
+ * Translates a promise strategy into a passport callback strategy.
11
+ *
12
+ * @param {function} strategy The promise based
13
+ * @returns {function}
14
+ */
15
+ function strategyWrapper(strategy) {
16
+ return function promiseStrategy() {
17
+ const args = Array.from(arguments);
18
+
19
+ let done = null;
20
+ while (args.length && (!done || typeof done !== 'function')) {
21
+ done = args.pop();
22
+ }
23
+
24
+ strategy.apply(null, args).then(
25
+ (strategyResponse) => {
26
+ if (strategyResponse.invalid) {
27
+ return done(null, false, strategyResponse.invalid);
28
+ }
29
+ return done(null, strategyResponse);
30
+ },
31
+ done
32
+ );
33
+ };
34
+ }
35
+
36
+ return {
37
+ strategyWrapper
38
+ };
39
+ }
40
+
41
+ authStrategyHelperFactory.$inject = true;
42
+ authStrategyHelperFactory.$filename = __filename;
43
+
44
+ module.exports = authStrategyHelperFactory;
@@ -0,0 +1,252 @@
1
+ 'use strict';
2
+
3
+ const diContainerFactory = require('@lutzklai/functional-di');
4
+ const lodash = require('lodash');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ // Requiring colors alters the string prototype to allow color mutations.
9
+ require('colors');
10
+
11
+ /**
12
+ * Sub folder glob pattern.
13
+ */
14
+ const SUB_FOLDER_GLOB = '/**/*';
15
+
16
+ /**
17
+ * The default file matching regexps for different file types.
18
+ * @type {Object.<string, RegExp>}
19
+ */
20
+ const FILE_REGEXPS = {
21
+ authStrategies: /Strategy\.js$/i,
22
+ config: /Config\.js$/i,
23
+ constants: /Constants\.js$/i,
24
+ helperUtils: /\.js$/i,
25
+ models: /Model\.js$/i,
26
+ policies: /Polic(y|ies)\.js$/i,
27
+ routes: /Route\.js$/i,
28
+ schemas: /Schema\.js$/i,
29
+ services: /Service\.js$/i
30
+ };
31
+
32
+ /**
33
+ * The default list of module folders.
34
+ * @type {Object.<string, string>}
35
+ */
36
+ const MODULE_FOLDERS = {
37
+ authStrategies: 'authStrategies',
38
+ config: 'config',
39
+ constants: 'constants',
40
+ helperUtils: 'helperUtils',
41
+ models: 'models',
42
+ policies: 'policies',
43
+ routes: 'routes' + SUB_FOLDER_GLOB,
44
+ schemas: 'schemas',
45
+ services: 'services'
46
+ };
47
+
48
+ /**
49
+ * The regexp used to match when a file is a test file.
50
+ * @const {RegExp}
51
+ */
52
+ const TEST_MATCH_REGEXP = /\.test\.js$/i;
53
+
54
+ /**
55
+ * File/directory not found error.
56
+ * @const {string}
57
+ */
58
+ const ENOENT = 'ENOENT';
59
+
60
+ /**
61
+ * Builds the diHelper.
62
+ *
63
+ * @param {Object.<string, RegExp>=} overrideFileRegexps
64
+ * @param {Object.<string, string>=} overrideModuleFolders
65
+ * @param {RegExp=} overrideTestRegexp
66
+ * @param {boolean=} logNotFound
67
+ * @returns {{}}
68
+ */
69
+ function diHelperFactory(overrideFileRegexps, overrideModuleFolders, overrideTestRegexp, logNotFound) {
70
+ const fileRegexps = Object.assign({}, FILE_REGEXPS, overrideFileRegexps || {});
71
+ const moduleFolders = Object.assign({}, MODULE_FOLDERS, overrideModuleFolders || {});
72
+ const testRegexp = overrideTestRegexp || TEST_MATCH_REGEXP;
73
+ const logNotFoundErrors = logNotFound || false;
74
+
75
+ /**
76
+ * Loads either a new Di Container or a clone of the given parent Di Container.
77
+ *
78
+ * @param {{}=} parentDiContainer
79
+ * @returns {{}}
80
+ */
81
+ function getDiContainer(parentDiContainer) {
82
+ let diContainer;
83
+ if (parentDiContainer) {
84
+ diContainer = parentDiContainer.clone();
85
+ } else {
86
+ diContainer = diContainerFactory();
87
+ }
88
+
89
+ return diContainer;
90
+ }
91
+
92
+ /**
93
+ * Injects all the files (or regexp matching files) from a folder into the dependency injector.
94
+ * Note: Files with no '.' or extension in the name will be skipped.
95
+ *
96
+ * @param {{register: function}} diContainer
97
+ * @param {string|string[]} folderPath
98
+ * @param {RegExp=} fileRegexp
99
+ * @param {Object.<string, string>=} injectionNameMap
100
+ * @param {{followSubFolders: boolean}=} folderOptions
101
+ * @returns {{}}
102
+ */
103
+ function injectFilesFromFolder(diContainer, folderPath, fileRegexp, injectionNameMap, folderOptions) {
104
+ const safeInjectionMap = injectionNameMap || {};
105
+ const safeFolderOptions = folderOptions || {};
106
+ const followSubFolders = Boolean(safeFolderOptions.followSubFolders);
107
+
108
+ let safeFolderPath = folderPath;
109
+ if (Array.isArray(folderPath)) {
110
+ safeFolderPath = path.join.apply(null, folderPath);
111
+ }
112
+
113
+ let files = [];
114
+ try {
115
+ files = fs.readdirSync(safeFolderPath); // eslint-disable-line no-sync
116
+ } catch (readDirError) {
117
+ if (readDirError.code && readDirError.code === ENOENT) {
118
+ if (logNotFoundErrors) {
119
+ console.log( // eslint-disable-line no-console
120
+ `Skipping injections from folder ${safeFolderPath}`.yellow
121
+ );
122
+ }
123
+ } else {
124
+ throw readDirError;
125
+ }
126
+ }
127
+
128
+ files.forEach((fileName) => {
129
+ if (fileName.indexOf('.') === -1) {
130
+ if (followSubFolders) {
131
+ const newFolderPath = [safeFolderPath, fileName];
132
+ injectFilesFromFolder(diContainer, newFolderPath, fileRegexp, injectionNameMap, safeFolderOptions);
133
+ }
134
+ return;
135
+ } else if (fileRegexp && !fileName.match(fileRegexp)) {
136
+ return;
137
+ } else if (fileName.match(testRegexp)) {
138
+ return;
139
+ }
140
+
141
+ let injectionName = null;
142
+ if (safeInjectionMap[fileName] === false) {
143
+ return;
144
+ } else if (safeInjectionMap[fileName]) {
145
+ injectionName = safeInjectionMap[fileName];
146
+ } else {
147
+ const fileNameParts = String(fileName).split('.');
148
+ injectionName = fileNameParts[0];
149
+ }
150
+
151
+ const fullFilePath = path.join(safeFolderPath, '/', fileName);
152
+ diContainer.register(injectionName, require(fullFilePath)); // eslint-disable-line global-require
153
+ });
154
+
155
+ return diContainer;
156
+ }
157
+
158
+ /**
159
+ * Automagically injects all the files for the module.
160
+ *
161
+ * @param {{register: function}} diContainer
162
+ * @param {string|string[]} moduleFolderPath
163
+ * @param {Object.<string, string>=} injectionNameMap
164
+ * @returns {{}}
165
+ */
166
+ function injectFilesForModule(diContainer, moduleFolderPath, injectionNameMap) {
167
+ let safeFolderPath = moduleFolderPath;
168
+ if (Array.isArray(moduleFolderPath)) {
169
+ safeFolderPath = path.join.apply(null, moduleFolderPath);
170
+ }
171
+
172
+ let files = [];
173
+ try {
174
+ files = fs.readdirSync(safeFolderPath); // eslint-disable-line no-sync
175
+ } catch (readDirError) {
176
+ if (readDirError.code && readDirError.code === ENOENT) {
177
+ if (logNotFoundErrors) {
178
+ console.log( // eslint-disable-line no-console
179
+ `Skipping injections from folder ${safeFolderPath}`.yellow
180
+ );
181
+ }
182
+ } else {
183
+ throw readDirError;
184
+ }
185
+ }
186
+
187
+ const injectFolderOptions = {followSubFolders: false};
188
+
189
+ files.forEach((fileName) => {
190
+ // Skip non-folder files.
191
+ if (fileName.indexOf('.') !== -1) {
192
+ return;
193
+ }
194
+
195
+ let matchedFolderName = null;
196
+ let followSubFolders = false;
197
+ lodash.values(moduleFolders).forEach((moduleFolderName) => {
198
+ // If the module folder name ends in '/**/*' then it will cause sub folders to be searched as well.
199
+ if (moduleFolderName.substr(0 - SUB_FOLDER_GLOB.length) === SUB_FOLDER_GLOB) {
200
+ const onlyFolderName = moduleFolderName.substring(0, moduleFolderName.length - SUB_FOLDER_GLOB.length);
201
+ if (fileName === onlyFolderName) {
202
+ matchedFolderName = fileName;
203
+ followSubFolders = true;
204
+ }
205
+ return;
206
+ }
207
+
208
+ if (fileName === moduleFolderName) {
209
+ matchedFolderName = fileName;
210
+ }
211
+ });
212
+
213
+ if (!matchedFolderName) {
214
+ return;
215
+ }
216
+
217
+ injectFilesFromFolder(
218
+ diContainer,
219
+ [moduleFolderPath, matchedFolderName],
220
+ fileRegexps[matchedFolderName],
221
+ injectionNameMap,
222
+ {...injectFolderOptions, followSubFolders}
223
+ );
224
+ });
225
+
226
+ return diContainer;
227
+ }
228
+
229
+ /**
230
+ * Automagically injects the top level files for the project.
231
+ * NOTE: This does not inject any module files.
232
+ *
233
+ * @param {{register: function}} diContainer
234
+ * @param {string|string[]} projectSrcFolderPath
235
+ * @param {Object.<string, string>=} injectionNameMap
236
+ * @returns {{}}
237
+ */
238
+ function injectFilesForProject(diContainer, projectSrcFolderPath, injectionNameMap) {
239
+ return injectFilesForModule(diContainer, projectSrcFolderPath, injectionNameMap);
240
+ }
241
+
242
+ return {
243
+ getDiContainer,
244
+ injectFilesFromFolder,
245
+ injectFilesForModule,
246
+ injectFilesForProject,
247
+ fileRegexp: Object.assign({}, fileRegexps),
248
+ moduleFolders: Object.assign({}, moduleFolders)
249
+ };
250
+ }
251
+
252
+ module.exports = diHelperFactory;
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ const winston = require('winston');
4
+
5
+ /**
6
+ * Exposes 'winston.transports.Rollbar'.
7
+ */
8
+ require('winston-rollbar2');
9
+
10
+ /**
11
+ * Gets a new logger instance.
12
+ *
13
+ * @param {{}} options
14
+ * @returns {{}}
15
+ */
16
+ function getNewLogger(options) {
17
+ const loggerOptions = options || {};
18
+
19
+ const defaultConsoleOptions = loggerOptions.console || {};
20
+
21
+ const transports = loggerOptions.transports || [
22
+ getTransportByName('console', defaultConsoleOptions)
23
+ ];
24
+
25
+ const newLogger = new (winston.Logger)({
26
+ transports: transports
27
+ });
28
+ newLogger.$filename = __filename;
29
+
30
+ return newLogger;
31
+ }
32
+
33
+ /**
34
+ * Gets a logging transport by the given name and options.
35
+ *
36
+ * @param {string} name
37
+ * @param {{}} options
38
+ * @returns {{}}
39
+ * @throws {Error} If the transport could not be found.
40
+ */
41
+ function getTransportByName(name, options) {
42
+ const transportOptions = options || {};
43
+
44
+ switch (name) {
45
+ case 'console':
46
+ return new (winston.transports.Console)(transportOptions);
47
+ case 'http':
48
+ return new (winston.transports.Http)(transportOptions);
49
+ case 'file':
50
+ return new (winston.transports.File)(transportOptions);
51
+ case 'rollbar':
52
+ return new (winston.transports.Rollbar)(transportOptions);
53
+ default:
54
+ throw new Error(`Could not find transport named '${name}'`);
55
+ }
56
+ }
57
+
58
+ module.exports = {
59
+ getNewLogger,
60
+ getTransportByName
61
+ };
@@ -0,0 +1,126 @@
1
+ 'use strict';
2
+
3
+ const lodash = require('lodash');
4
+
5
+ const MOD_KEY_MAX = Number.MAX_SAFE_INTEGER;
6
+
7
+ /**
8
+ * Provides the list of functions for bootstrapping a module.
9
+ *
10
+ * @param {{name: string}} modConfig
11
+ * @param {function} moduleDiFactory
12
+ * @param {function} testingSetup
13
+ * @param {string[]} serviceNames
14
+ * @returns {{
15
+ * initModule: function,
16
+ * loadModuleServices: function,
17
+ * registerAppServices: function,
18
+ * configureModuleTesting: function,
19
+ * resolveModulePlugins: function
20
+ * }}
21
+ */
22
+ function modBootstrapFactory(modConfig, moduleDiFactory, testingSetup, serviceNames) {
23
+ let modName = modConfig.name;
24
+ let modKey = (modName || '') + Math.floor((Math.random() * MOD_KEY_MAX));
25
+
26
+ const safeServiceNames = serviceNames || [];
27
+
28
+ let modDiContainer = null;
29
+ let dependencyArgs = {};
30
+ let modServices = {};
31
+
32
+ /**
33
+ * Initializes the module by loading its dependency injector.
34
+ *
35
+ * @param {Object} parentDiContainer
36
+ * @param {string} directoryName
37
+ * @returns {Promise.<{Object}>}
38
+ */
39
+ function initModule(parentDiContainer, directoryName) {
40
+ if (directoryName && !modName) {
41
+ modName = `${directoryName}Module`;
42
+ modKey = modName + modKey;
43
+ }
44
+
45
+ modDiContainer = moduleDiFactory(parentDiContainer);
46
+
47
+ return {
48
+ [modKey]: modDiContainer
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Loads the services for this module.
54
+ *
55
+ * @returns {Object.<string, function>}
56
+ */
57
+ function loadModuleServices() {
58
+ modServices = {};
59
+ safeServiceNames.forEach(function mapServices(serviceName) {
60
+ modServices[serviceName] = modDiContainer.resolveCurry(serviceName);
61
+ });
62
+
63
+ return modServices;
64
+ }
65
+
66
+ /**
67
+ * Registers other application services with this module's DI container.
68
+ *
69
+ * @param {Object.<string, function>} appServices
70
+ */
71
+ function registerAppServices(appServices) {
72
+ if (!appServices) {
73
+ return;
74
+ }
75
+
76
+ lodash.forEach(appServices, function registerNewServices(service, serviceName) {
77
+ if (modServices[serviceName]) {
78
+ // Do not register our own module services.
79
+ return;
80
+ }
81
+
82
+ modDiContainer.register(serviceName, service);
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Runs testing configuration for this module.
88
+ *
89
+ * @returns {Promise}
90
+ */
91
+ function configureModuleTesting() {
92
+ return testingSetup(
93
+ modDiContainer,
94
+ modDiContainer.resolve('testingConfig')
95
+ ).then((testingDependencyArgs) => {
96
+ dependencyArgs = Object.assign({}, dependencyArgs, testingDependencyArgs);
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Resolves and returns the module plugins.
102
+ *
103
+ * @returns {Object.<string, Object>}
104
+ */
105
+ function resolveModulePlugins() {
106
+ const moduleAsPlugin = modDiContainer.resolve('moduleAsPlugin', dependencyArgs || null)();
107
+
108
+ if (modName && !moduleAsPlugin.name) {
109
+ moduleAsPlugin.name = modName;
110
+ }
111
+
112
+ return {
113
+ [modKey]: moduleAsPlugin
114
+ };
115
+ }
116
+
117
+ return {
118
+ initModule,
119
+ loadModuleServices,
120
+ registerAppServices,
121
+ configureModuleTesting,
122
+ resolveModulePlugins
123
+ };
124
+ }
125
+
126
+ module.exports = modBootstrapFactory;
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Initializes the module testing bootstrap.
5
+ *
6
+ * @param {{createSeededDatabase: function}} dbSeed
7
+ * @returns {function}
8
+ */
9
+ function initModuleTestingBootstrap(dbSeed) {
10
+ /**
11
+ * Initializes the application for testing purposes.
12
+ *
13
+ * @param {Object} modDiContainer
14
+ * @param {{routeName: string, onDbSeeded: function}} config
15
+ * @returns {Promise<{moduleRoutes: string[]}>}
16
+ */
17
+ return function setupModuleTesting(modDiContainer, config) {
18
+ const safeConfig = config || {};
19
+ const routeName = safeConfig.routeName || '';
20
+ const onDbSeeded = safeConfig.onDbSeeded || null;
21
+
22
+ const returnArgs = {
23
+ moduleRoutes: [routeName]
24
+ };
25
+
26
+ if (!dbSeed) {
27
+ return Promise.resolve(returnArgs);
28
+ }
29
+
30
+ return dbSeed.createSeededDatabase(routeName + 'Test', modDiContainer).then(() => {
31
+ if (onDbSeeded && typeof onDbSeeded === 'function') {
32
+ onDbSeeded(modDiContainer);
33
+ }
34
+
35
+ return returnArgs;
36
+ });
37
+ }
38
+ }
39
+
40
+ module.exports = initModuleTestingBootstrap;
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ // Requiring colors alters the string prototype to allow color mutations.
4
+ require('colors');
5
+
6
+ /**
7
+ * Initializes the module plugins parser.
8
+ *
9
+ * @param {{}} app
10
+ * @param {boolean=} skipLogging
11
+ * @returns {{registerWithApp: function}}
12
+ */
13
+ function initModulePlugins(app, skipLogging) {
14
+ /**
15
+ * Registers each plugin with the server/app.
16
+ *
17
+ * @param {Array.<{register: function}>} modulePlugins
18
+ * @returns {Array.<{}>}
19
+ */
20
+ function registerWithApp(modulePlugins) {
21
+ const registerPromises = modulePlugins.map((modulePlugin) => {
22
+ if (modulePlugin.name && !skipLogging) {
23
+ console.log('Loading module', String(modulePlugin.name).cyan.bold); // eslint-disable-line no-console
24
+ }
25
+
26
+ return Promise.resolve(modulePlugin.register(app));
27
+ });
28
+
29
+ return Promise.all(registerPromises);
30
+ }
31
+
32
+ return {
33
+ registerWithApp
34
+ };
35
+ }
36
+
37
+ module.exports = initModulePlugins;
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Converts the given arguments to a testing config object.
5
+ *
6
+ * @param {string=} routeName
7
+ * @param {function=} onDbSeeded
8
+ * @returns {Object}
9
+ */
10
+ function convertArgumentsToConfig(routeName, onDbSeeded) {
11
+ return {
12
+ routeName: routeName ? String(routeName) : '',
13
+ onDbSeeded: onDbSeeded || null
14
+ };
15
+ }
16
+
17
+ /**
18
+ * Provides the list of functions for testing a module.
19
+ *
20
+ * @param {function} appTesting
21
+ * @param {{name: string}} modConfig
22
+ * @returns {{
23
+ * bootstrap: function,
24
+ * bootstrapServer: function,
25
+ * setup: function
26
+ * }}
27
+ */
28
+ function modTestingFactory(appTesting, modConfig) {
29
+ const modName = modConfig.name;
30
+
31
+ /**
32
+ * Initializes the application for testing purposes and returns the module's DI container.
33
+ *
34
+ * @param {string=} routeName
35
+ * @param {function=} onDbSeeded
36
+ * @returns {Promise<Object.<string, Object>>} Promise that returns the module's DI container.
37
+ */
38
+ function bootstrapTestApplication(routeName, onDbSeeded) {
39
+ return appTesting(
40
+ modName,
41
+ convertArgumentsToConfig(routeName, onDbSeeded)
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Initializes the application server for testing purposes.
47
+ *
48
+ * @param {string=} routeName
49
+ * @param {function=} onDbSeeded
50
+ * @returns {Promise<Object>} Promise that returns the Hapi server.
51
+ */
52
+ function bootstrapTestAppServer(routeName, onDbSeeded) {
53
+ const mockAdminUser = {
54
+ id: 1001,
55
+ isAdmin: true
56
+ };
57
+
58
+ return appTesting(
59
+ modName,
60
+ convertArgumentsToConfig(routeName, onDbSeeded)
61
+ ).then(function getServerFromModDiContainer(diContainers) {
62
+ // By sending false, we are preventing the server from starting.
63
+ return diContainers.app.resolve('server', {shouldStart: false});
64
+ }).then(function getMockServer(server) {
65
+ return {
66
+ inject: (call) => {
67
+ if (!call.credentials) {
68
+ call.credentials = {};
69
+ }
70
+ if (!call.credentials.user) {
71
+ call.credentials.user = mockAdminUser;
72
+ }
73
+
74
+ return server.inject(call);
75
+ }
76
+ };
77
+ });
78
+ }
79
+
80
+ return {
81
+ bootstrap: bootstrapTestApplication,
82
+ bootstrapServer: bootstrapTestAppServer
83
+ };
84
+ }
85
+
86
+ module.exports = modTestingFactory;
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ /* eslint-disable global-require */
4
+
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+
8
+ // Requiring colors alters the string prototype to allow color mutations.
9
+ require('colors');
10
+
11
+ /**
12
+ * Loads the main module loader factories for all modules in the modules directory.
13
+ *
14
+ * @param {string} baseDirectory
15
+ * @returns {Promise.<Array.<Object>>}
16
+ */
17
+ function moduleFinder(baseDirectory) {
18
+ const modulesPath = path.join(baseDirectory, 'modules');
19
+
20
+ return new Promise(function moduleFinderPromise(resolve, reject) {
21
+ fs.readdir(modulesPath, function afterReadModulesDir(err, files) {
22
+ if (err) {
23
+ if (err.code === 'ENOENT') {
24
+ console.log( // eslint-disable-line no-console
25
+ `Could not find required module folder at ${modulesPath}`.red.bold
26
+ );
27
+ }
28
+
29
+ reject(err);
30
+ return;
31
+ }
32
+
33
+ resolve(files);
34
+ });
35
+ }).then(function parseOutFiles(files) {
36
+ return files.filter(function filterOutFilesWithDots(fileName) {
37
+ return (fileName.indexOf('.') < 0);
38
+ });
39
+ }).then(function requireFoundModules(directoryNames) {
40
+ return directoryNames.map(function requireModule(directoryName) {
41
+ return {
42
+ directory: directoryName,
43
+ module: require(
44
+ path.join(modulesPath, directoryName, 'index.js')
45
+ )
46
+ };
47
+ });
48
+ });
49
+ }
50
+
51
+ module.exports = moduleFinder;