diff --git a/src/auth/validate.js b/src/auth/validate.js index 2b38b9be46..43dad23f27 100644 --- a/src/auth/validate.js +++ b/src/auth/validate.js @@ -1,5 +1,5 @@ -const validate = require('express-validation'); -const Joi = require('joi'); +import validate from 'express-validation'; +import Joi from 'joi'; module.exports = { login: validate({ diff --git a/src/errors/APIError.js b/src/errors/APIError.js index 2b4aef1122..9a4705eccf 100644 --- a/src/errors/APIError.js +++ b/src/errors/APIError.js @@ -1,34 +1,34 @@ -const httpStatus = require('http-status'); - -/** - * @extends Error - */ -class ExtendableError extends Error { - constructor(message, status, isPublic) { - super(message); - this.name = this.constructor.name; - this.message = message; - this.status = status; - this.isPublic = isPublic; - this.isOperational = true; // This is required since bluebird 4 doesn't append it anymore. - Error.captureStackTrace(this, this.constructor.name); - } -} - -/** - * Class representing an API error. - * @extends ExtendableError - */ -class APIError extends ExtendableError { - /** - * Creates an API error. - * @param {string} message - Error message. - * @param {number} status - HTTP status code of error. - * @param {boolean} isPublic - Whether the message should be visible to user or not. - */ - constructor(message, status = httpStatus.INTERNAL_SERVER_ERROR, isPublic = false) { - super(message, status, isPublic); - } -} - -module.exports = APIError; +import httpStatus from 'http-status'; + +/** + * @extends Error + */ +class ExtendableError extends Error { + constructor(message, status, isPublic) { + super(message); + this.name = this.constructor.name; + this.message = message; + this.status = status; + this.isPublic = isPublic; + this.isOperational = true; // This is required since bluebird 4 doesn't append it anymore. + Error.captureStackTrace(this, this.constructor.name); + } +} + +/** + * Class representing an API error. + * @extends ExtendableError + */ +class APIError extends ExtendableError { + /** + * Creates an API error. + * @param {string} message - Error message. + * @param {number} status - HTTP status code of error. + * @param {boolean} isPublic - Whether the message should be visible to user or not. + */ + constructor(message, status = httpStatus.INTERNAL_SERVER_ERROR, isPublic = false) { + super(message, status, isPublic); + } +} + +module.exports = APIError; diff --git a/src/index.js b/src/index.js index cdaf5b4b0c..f8fc0c8cc0 100644 --- a/src/index.js +++ b/src/index.js @@ -21,13 +21,17 @@ import authValidate from './auth/validate'; import authRequestHandlers from './auth/requestHandlers'; import passwordResetConfig from './auth/passwordResets/passwordReset.config'; import validateConfig from './utilities/validateConfig'; +import setModelLocaleMiddleware from './mongoose/setModelLocale.middleware'; class Payload { models = {}; constructor(options) { - mongoose.connect(options.config.mongoURL, { useNewUrlParser: true }, (err) => { + mongoose.connect(options.config.mongoURL, { + useNewUrlParser: true, + useUnifiedTopology: true, + }, (err) => { if (err) { console.log('Unable to connect to the Mongo server. Please start the server. Error:', err); } else { @@ -130,13 +134,16 @@ class Payload { if (config.auth.registration) { options.router - .route('/' + config.slug + '/register') // TODO: not sure how to incorporate url params like `:pageId` + .route(`${config.slug}/register`) // TODO: not sure how to incorporate url params like `:pageId` .post(config.auth.registrationValidation, auth.register); } } this.models[config.labels.singular] = model; - options.router.all(`/${config.slug}*`, bindModelMiddleware(model)); + options.router.all(`/${config.slug}*`, + bindModelMiddleware(model), + setModelLocaleMiddleware() + ); options.router.route(`/${config.slug}`) .get(config.policies.read, query) diff --git a/src/localization/localization.plugin.js b/src/localization/localization.plugin.js index 84ae9ddf85..e2910f93c3 100644 --- a/src/localization/localization.plugin.js +++ b/src/localization/localization.plugin.js @@ -6,8 +6,8 @@ export default function localizationPlugin(schema, options) { } // plugin options to be set under schema options - schema.options.mongooseIntl = {}; - const pluginOptions = schema.options.mongooseIntl; + schema.options.localization = {}; + const pluginOptions = schema.options.localization; pluginOptions.locales = options.locales.slice(0); @@ -86,7 +86,7 @@ export default function localizationPlugin(schema, options) { .set(function (value) { // multiple locales are set as an object if (typeof value === 'object') { - let locales = this.schema.options.mongooseIntl.locales; + let locales = this.schema.options.localization.locales; locales.forEach(locale => { if (!value[locale]) { return; @@ -137,10 +137,10 @@ export default function localizationPlugin(schema, options) { // document methods to set the locale for each model instance (document) schema.method({ getLocales: function () { - return this.schema.options.mongooseIntl.locales; + return this.schema.options.localization.locales; }, getLocale: function () { - return this.docLocale || this.schema.options.mongooseIntl.defaultLocale; + return this.docLocale || this.schema.options.localization.defaultLocale; }, setLocale: function (locale, fallbackLocale) { const locales = this.getLocales(); @@ -171,15 +171,15 @@ export default function localizationPlugin(schema, options) { // model methods to set the locale for the current schema schema.static({ getLocales: function () { - return this.schema.options.mongooseIntl.locales; + return this.schema.options.localization.locales; }, getDefaultLocale: function () { - return this.schema.options.mongooseIntl.defaultLocale; + return this.schema.options.localization.defaultLocale; }, setDefaultLocale: function (locale) { let updateLocale = function (schema, locale) { - schema.options.mongooseIntl.defaultLocale = locale.slice(0); + schema.options.localization.defaultLocale = locale.slice(0); // default locale change for sub-documents schemas schema.eachPath((path, schemaType) => { diff --git a/src/media/imageResizer.js b/src/media/imageResizer.js index 7f71125161..edb6c247e6 100644 --- a/src/media/imageResizer.js +++ b/src/media/imageResizer.js @@ -1,34 +1,35 @@ -import sharp from 'sharp'; -const { promisify } = require('util'); -const sizeOf = promisify(require('image-size')); - -function getOutputImageName(sourceImage, size) { - let extension = sourceImage.split('.').pop(); - let filenameWithoutExtension = sourceImage.substr(0, sourceImage.lastIndexOf('.')) || sourceImage; - return `${filenameWithoutExtension}-${size.width}x${size.height}.${extension}`; -} - -export async function resizeAndSave(config, file) { - let sourceImage = `${config.staticDir}/${file.name}`; - - let outputSizes = []; - try { - const dimensions = await sizeOf(sourceImage); - for (let desiredSize of config.imageSizes) { - if (desiredSize.width > dimensions.width) { - continue; - } - let outputImageName = getOutputImageName(sourceImage, desiredSize); - await sharp(sourceImage) - .resize(desiredSize.width, desiredSize.height, { - position: desiredSize.crop || 'centre' - }) - .toFile(outputImageName); - outputSizes.push({ height: desiredSize.height, width: desiredSize.width }); - } - } catch (e) { - console.log('error in resize and save', e.message); - } - - return outputSizes; -} +import sharp from 'sharp'; +import { promisify } from 'util'; +import imageSize from 'image-size'; +const sizeOf = promisify(imageSize); + +function getOutputImageName(sourceImage, size) { + let extension = sourceImage.split('.').pop(); + let filenameWithoutExtension = sourceImage.substr(0, sourceImage.lastIndexOf('.')) || sourceImage; + return `${filenameWithoutExtension}-${size.width}x${size.height}.${extension}`; +} + +export async function resizeAndSave(config, file) { + let sourceImage = `${config.staticDir}/${file.name}`; + + let outputSizes = []; + try { + const dimensions = await sizeOf(sourceImage); + for (let desiredSize of config.imageSizes) { + if (desiredSize.width > dimensions.width) { + continue; + } + let outputImageName = getOutputImageName(sourceImage, desiredSize); + await sharp(sourceImage) + .resize(desiredSize.width, desiredSize.height, { + position: desiredSize.crop || 'centre' + }) + .toFile(outputImageName); + outputSizes.push({ height: desiredSize.height, width: desiredSize.width }); + } + } catch (e) { + console.log('error in resize and save', e.message); + } + + return outputSizes; +} diff --git a/src/mongoose/autopopulate.plugin.js b/src/mongoose/autopopulate.plugin.js index f3d41368a2..a441b28733 100644 --- a/src/mongoose/autopopulate.plugin.js +++ b/src/mongoose/autopopulate.plugin.js @@ -1,153 +1,149 @@ -'use strict'; - -const mongoose = require('mongoose'); - -module.exports = function (schema) { - const pathsToPopulate = []; - - eachPathRecursive(schema, (pathname, schemaType) => { - let option; - if (schemaType.options && schemaType.options.autopopulate) { - option = schemaType.options.autopopulate; - pathsToPopulate.push({ - options: defaultOptions(pathname, schemaType.options), - autopopulate: option - }); - } else if (schemaType.options && - schemaType.options.type && - schemaType.options.type[0] && - schemaType.options.type[0].autopopulate) { - option = schemaType.options.type[0].autopopulate; - pathsToPopulate.push({ - options: defaultOptions(pathname, schemaType.options.type[0]), - autopopulate: option - }); - } - }); - - if (schema.virtuals) { - Object.keys(schema.virtuals).forEach((pathname) => { - if (schema.virtuals[pathname].options.autopopulate) { - pathsToPopulate.push({ - options: defaultOptions(pathname, schema.virtuals[pathname].options), - autopopulate: schema.virtuals[pathname].options.autopopulate, - }); - } - }); - } - - var autopopulateHandler = function () { - if (this._mongooseOptions && - this._mongooseOptions.lean && - // If lean and user didn't explicitly do `lean({ autopulate: true })`, - // skip it. See gh-27, gh-14, gh-48 - !this._mongooseOptions.lean.autopopulate) { - return; - } - - const options = this.options || {}; - if (options.autopopulate === false) { - return; - } - - let maxDepth = options.maxDepth; - - if (options.autopopulate && options.autopopulate.maxDepth) { - maxDepth = options.autopopulate.maxDepth; - } - - const depth = options._depth != null ? options._depth : 0; - - if (options.maxDepth > 0 && depth >= maxDepth) { - return; - } - - const numPaths = pathsToPopulate.length; - for (let i = 0; i < numPaths; ++i) { - pathsToPopulate[i].options = pathsToPopulate[i].options || {}; - pathsToPopulate[i].options.options = pathsToPopulate[i].options.options || {}; - Object.assign(pathsToPopulate[i].options.options, { - ...options, - _depth: depth + 1 - }); - processOption.call(this, - pathsToPopulate[i].autopopulate, pathsToPopulate[i].options); - } - }; - - schema. - pre('find', autopopulateHandler). - pre('findOne', autopopulateHandler). - pre('findOneAndUpdate', autopopulateHandler); -}; - -function defaultOptions(pathname, v) { - const ret = { path: pathname, options: { maxDepth: 10 } }; - if (v.ref != null) { - ret.model = v.ref; - ret.ref = v.ref; - } - if (v.refPath != null) { - ret.refPath = v.refPath; - } - return ret; -} - -function processOption(value, options) { - switch (typeof value) { - case 'function': - handleFunction.call(this, value, options); - break; - case 'object': - handleObject.call(this, value, options); - break; - default: - handlePrimitive.call(this, value, options); - break; - } -} - -function handlePrimitive(value, options) { - if (value) { - this.populate(options); - } -} - -function handleObject(value, optionsToUse) { - // Special case: support top-level `maxDepth` - if (value.maxDepth != null) { - optionsToUse.options = optionsToUse.options || {}; - optionsToUse.options.maxDepth = value.maxDepth; - delete value.maxDepth; - } - optionsToUse = Object.assign({}, optionsToUse, value); - this.populate(optionsToUse); -} - -function handleFunction(fn, options) { - const val = fn.call(this, options); - processOption.call(this, val, options); -} - -function mergeOptions(destination, source) { - const keys = Object.keys(source); - const numKeys = keys.length; - for (let i = 0; i < numKeys; ++i) { - destination[keys[i]] = source[keys[i]]; - } -} - -function eachPathRecursive(schema, handler, path) { - if (!path) { - path = []; - } - schema.eachPath((pathname, schemaType) => { - path.push(pathname); - if (schemaType.schema) { - eachPathRecursive(schemaType.schema, handler, path); - } else { - handler(path.join('.'), schemaType); - } - path.pop(); - }); -} +module.exports = function (schema) { + const pathsToPopulate = []; + + eachPathRecursive(schema, (pathname, schemaType) => { + let option; + if (schemaType.options && schemaType.options.autopopulate) { + option = schemaType.options.autopopulate; + pathsToPopulate.push({ + options: defaultOptions(pathname, schemaType.options), + autopopulate: option + }); + } else if (schemaType.options && + schemaType.options.type && + schemaType.options.type[0] && + schemaType.options.type[0].autopopulate) { + option = schemaType.options.type[0].autopopulate; + pathsToPopulate.push({ + options: defaultOptions(pathname, schemaType.options.type[0]), + autopopulate: option + }); + } + }); + + if (schema.virtuals) { + Object.keys(schema.virtuals).forEach((pathname) => { + if (schema.virtuals[pathname].options.autopopulate) { + pathsToPopulate.push({ + options: defaultOptions(pathname, schema.virtuals[pathname].options), + autopopulate: schema.virtuals[pathname].options.autopopulate, + }); + } + }); + } + + var autopopulateHandler = function () { + if (this._mongooseOptions && + this._mongooseOptions.lean && + // If lean and user didn't explicitly do `lean({ autopulate: true })`, + // skip it. See gh-27, gh-14, gh-48 + !this._mongooseOptions.lean.autopopulate) { + return; + } + + const options = this.options || {}; + if (options.autopopulate === false) { + return; + } + + let maxDepth = options.maxDepth; + + if (options.autopopulate && options.autopopulate.maxDepth) { + maxDepth = options.autopopulate.maxDepth; + } + + const depth = options._depth != null ? options._depth : 0; + + if (options.maxDepth > 0 && depth >= maxDepth) { + return; + } + + const numPaths = pathsToPopulate.length; + for (let i = 0; i < numPaths; ++i) { + pathsToPopulate[i].options = pathsToPopulate[i].options || {}; + pathsToPopulate[i].options.options = pathsToPopulate[i].options.options || {}; + Object.assign(pathsToPopulate[i].options.options, { + ...options, + _depth: depth + 1 + }); + processOption.call(this, + pathsToPopulate[i].autopopulate, pathsToPopulate[i].options); + } + }; + + schema. + pre('find', autopopulateHandler). + pre('findOne', autopopulateHandler). + pre('findOneAndUpdate', autopopulateHandler); +}; + +function defaultOptions(pathname, v) { + const ret = { path: pathname, options: { maxDepth: 10 } }; + if (v.ref != null) { + ret.model = v.ref; + ret.ref = v.ref; + } + if (v.refPath != null) { + ret.refPath = v.refPath; + } + return ret; +} + +function processOption(value, options) { + switch (typeof value) { + case 'function': + handleFunction.call(this, value, options); + break; + case 'object': + handleObject.call(this, value, options); + break; + default: + handlePrimitive.call(this, value, options); + break; + } +} + +function handlePrimitive(value, options) { + if (value) { + this.populate(options); + } +} + +function handleObject(value, optionsToUse) { + // Special case: support top-level `maxDepth` + if (value.maxDepth != null) { + optionsToUse.options = optionsToUse.options || {}; + optionsToUse.options.maxDepth = value.maxDepth; + delete value.maxDepth; + } + optionsToUse = Object.assign({}, optionsToUse, value); + this.populate(optionsToUse); +} + +function handleFunction(fn, options) { + const val = fn.call(this, options); + processOption.call(this, val, options); +} + +function mergeOptions(destination, source) { + const keys = Object.keys(source); + const numKeys = keys.length; + for (let i = 0; i < numKeys; ++i) { + destination[keys[i]] = source[keys[i]]; + } +} + +function eachPathRecursive(schema, handler, path) { + if (!path) { + path = []; + } + schema.eachPath((pathname, schemaType) => { + path.push(pathname); + if (schemaType.schema) { + eachPathRecursive(schemaType.schema, handler, path); + } else { + handler(path.join('.'), schemaType); + } + path.pop(); + }); +} diff --git a/src/mongoose/requestHandlers/create.js b/src/mongoose/requestHandlers/create.js index c9c79db6a8..695a4780cc 100644 --- a/src/mongoose/requestHandlers/create.js +++ b/src/mongoose/requestHandlers/create.js @@ -1,17 +1,16 @@ -import httpStatus from 'http-status'; - -const create = (req, res) => { - req.model.setDefaultLocale(req.locale); // TODO - move to middleware - req.model.create(req.body, (err, result) => { - if (err) - return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: err }); - - return res.status(httpStatus.CREATED) - .json({ - message: 'success', - result: result.toJSON({ virtuals: true }) - }); - }); -}; - -export default create; +import httpStatus from 'http-status'; + +const create = (req, res) => { + req.model.create(req.body, (err, result) => { + if (err) + return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: err }); + + return res.status(httpStatus.CREATED) + .json({ + message: 'success', + result: result.toJSON({ virtuals: true }) + }); + }); +}; + +export default create; diff --git a/src/mongoose/requestHandlers/update.js b/src/mongoose/requestHandlers/update.js index 70acfe4f97..a92fa6f481 100644 --- a/src/mongoose/requestHandlers/update.js +++ b/src/mongoose/requestHandlers/update.js @@ -1,7 +1,6 @@ import httpStatus from 'http-status'; const update = (req, res) => { - req.model.setDefaultLocale(req.locale); // TODO - move to middleware req.model.findOne({ _id: req.params.id }, '', {}, (err, doc) => { if (!doc) return res.status(httpStatus.NOT_FOUND).send('Not Found'); diff --git a/src/mongoose/setModelLocale.middleware.js b/src/mongoose/setModelLocale.middleware.js new file mode 100644 index 0000000000..e14997acfa --- /dev/null +++ b/src/mongoose/setModelLocale.middleware.js @@ -0,0 +1,9 @@ +const setModelLocaleMiddleware = () => { + return (req, res, next) => { + if (req.locale && req.model.setDefaultLocale) + req.model.setDefaultLocale(req.locale); + next(); + }; +}; + +export default setModelLocaleMiddleware; diff --git a/src/tests/jest.config.integration.js b/src/tests/jest.config.integration.js index 82036479e5..b834d956cf 100644 --- a/src/tests/jest.config.integration.js +++ b/src/tests/jest.config.integration.js @@ -1,4 +1,4 @@ -const { defaults } = require('./jest.config'); +import defaults from './jest.config'; module.exports = { ...defaults,