Merge branch 'master' into move-frontend-to-src

This commit is contained in:
Dan Ribbens
2019-10-07 10:04:40 -04:00
10 changed files with 264 additions and 253 deletions

View File

@@ -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({

View File

@@ -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;

View File

@@ -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)

View File

@@ -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) => {

View File

@@ -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;
}

View File

@@ -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();
});
}

View File

@@ -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;

View File

@@ -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');

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
const { defaults } = require('./jest.config');
import defaults from './jest.config';
module.exports = {
...defaults,