removes unnecessary try / catch from operations

This commit is contained in:
James
2020-07-08 09:21:25 -04:00
parent ac5caf4da2
commit 1502b2e2ce
22 changed files with 1282 additions and 1345 deletions

View File

@@ -2,73 +2,69 @@ const crypto = require('crypto');
const { APIError } = require('../../errors');
const forgotPassword = async (args) => {
try {
if (!Object.prototype.hasOwnProperty.call(args.data, 'email')) {
throw new APIError('Missing email.');
}
if (!Object.prototype.hasOwnProperty.call(args.data, 'email')) {
throw new APIError('Missing email.');
}
let options = { ...args };
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before login hook
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Execute before login hook
// /////////////////////////////////////
const { beforeForgotPassword } = args.collection.config.hooks;
const { beforeForgotPassword } = args.collection.config.hooks;
if (typeof beforeForgotPassword === 'function') {
options = await beforeForgotPassword(options);
}
if (typeof beforeForgotPassword === 'function') {
options = await beforeForgotPassword(options);
}
// /////////////////////////////////////
// 2. Perform forgot password
// /////////////////////////////////////
// /////////////////////////////////////
// 2. Perform forgot password
// /////////////////////////////////////
const {
collection: {
Model,
},
config,
data,
email,
} = options;
const {
collection: {
Model,
},
config,
data,
email,
} = options;
let token = await crypto.randomBytes(20);
token = token.toString('hex');
let token = await crypto.randomBytes(20);
token = token.toString('hex');
const user = await Model.findOne({ email: data.email });
const user = await Model.findOne({ email: data.email });
if (!user) return;
if (!user) return;
user.resetPasswordToken = token;
user.resetPasswordExpiration = Date.now() + 3600000; // 1 hour
user.resetPasswordToken = token;
user.resetPasswordExpiration = Date.now() + 3600000; // 1 hour
await user.save();
await user.save();
const html = `You are receiving this because you (or someone else) have requested the reset of the password for your account.
const html = `You are receiving this because you (or someone else) have requested the reset of the password for your account.
Please click on the following link, or paste this into your browser to complete the process:
<a href="${config.serverURL}${config.routes.admin}/reset/${token}">
${config.serverURL}${config.routes.admin}/reset/${token}
</a>
If you did not request this, please ignore this email and your password will remain unchanged.`;
email({
from: `"${config.email.fromName}" <${config.email.fromAddress}>`,
to: data.email,
subject: 'Password Reset',
html,
});
email({
from: `"${config.email.fromName}" <${config.email.fromAddress}>`,
to: data.email,
subject: 'Password Reset',
html,
});
// /////////////////////////////////////
// 3. Execute after forgot password hook
// /////////////////////////////////////
// /////////////////////////////////////
// 3. Execute after forgot password hook
// /////////////////////////////////////
const { afterForgotPassword } = args.req.collection.config.hooks;
const { afterForgotPassword } = args.req.collection.config.hooks;
if (typeof afterForgotPassword === 'function') {
await afterForgotPassword(options);
}
} catch (error) {
throw error;
if (typeof afterForgotPassword === 'function') {
await afterForgotPassword(options);
}
};

View File

@@ -1,17 +1,13 @@
const init = async (args) => {
try {
const {
Model,
} = args;
const {
Model,
} = args;
const count = await Model.countDocuments({});
const count = await Model.countDocuments({});
if (count >= 1) return true;
if (count >= 1) return true;
return false;
} catch (error) {
throw error;
}
return false;
};
module.exports = init;

View File

@@ -2,100 +2,96 @@ const jwt = require('jsonwebtoken');
const { AuthenticationError } = require('../../errors');
const login = async (args) => {
try {
// Await validation here
// Await validation here
let options = { ...args };
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before login hook
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Execute before login hook
// /////////////////////////////////////
const beforeLoginHook = args.collection.config.hooks.beforeLogin;
const beforeLoginHook = args.collection.config.hooks.beforeLogin;
if (typeof beforeLoginHook === 'function') {
options = await beforeLoginHook(options);
}
// /////////////////////////////////////
// 2. Perform login
// /////////////////////////////////////
const {
collection: {
Model,
config: collectionConfig,
},
config,
data,
} = options;
const { email, password } = data;
const user = await Model.findByUsername(email);
if (!user) throw new AuthenticationError();
const authResult = await user.authenticate(password);
if (!authResult.user) {
throw new AuthenticationError();
}
const fieldsToSign = collectionConfig.fields.reduce((signedFields, field) => {
if (field.saveToJWT) {
return {
...signedFields,
[field.name]: user[field.name],
};
}
return signedFields;
}, {
email,
id: user.id,
});
fieldsToSign.collection = collectionConfig.slug;
const token = jwt.sign(
fieldsToSign,
config.secret,
{
expiresIn: collectionConfig.auth.tokenExpiration,
},
);
if (args.res) {
const cookieOptions = {
path: '/',
httpOnly: true,
};
if (collectionConfig.auth.secureCookie) {
cookieOptions.secure = true;
}
args.res.cookie(`${config.cookiePrefix}-token`, token, cookieOptions);
}
// /////////////////////////////////////
// 3. Execute after login hook
// /////////////////////////////////////
const afterLoginHook = args.collection.config.hooks.afterLogin;
if (typeof afterLoginHook === 'function') {
await afterLoginHook({ ...options, token, user });
}
// /////////////////////////////////////
// 4. Return token
// /////////////////////////////////////
return token;
} catch (error) {
throw error;
if (typeof beforeLoginHook === 'function') {
options = await beforeLoginHook(options);
}
// /////////////////////////////////////
// 2. Perform login
// /////////////////////////////////////
const {
collection: {
Model,
config: collectionConfig,
},
config,
data,
} = options;
const { email, password } = data;
const user = await Model.findByUsername(email);
if (!user) throw new AuthenticationError();
const authResult = await user.authenticate(password);
if (!authResult.user) {
throw new AuthenticationError();
}
const fieldsToSign = collectionConfig.fields.reduce((signedFields, field) => {
if (field.saveToJWT) {
return {
...signedFields,
[field.name]: user[field.name],
};
}
return signedFields;
}, {
email,
id: user.id,
});
fieldsToSign.collection = collectionConfig.slug;
const token = jwt.sign(
fieldsToSign,
config.secret,
{
expiresIn: collectionConfig.auth.tokenExpiration,
},
);
if (args.res) {
const cookieOptions = {
path: '/',
httpOnly: true,
};
if (collectionConfig.auth.secureCookie) {
cookieOptions.secure = true;
}
args.res.cookie(`${config.cookiePrefix}-token`, token, cookieOptions);
}
// /////////////////////////////////////
// 3. Execute after login hook
// /////////////////////////////////////
const afterLoginHook = args.collection.config.hooks.afterLogin;
if (typeof afterLoginHook === 'function') {
await afterLoginHook({ ...options, token, user });
}
// /////////////////////////////////////
// 4. Return token
// /////////////////////////////////////
return token;
};
module.exports = login;

View File

@@ -2,28 +2,24 @@ const jwt = require('jsonwebtoken');
const getExtractJWT = require('../getExtractJWT');
const me = async ({ req, config }) => {
try {
const extractJWT = getExtractJWT(config);
const extractJWT = getExtractJWT(config);
if (req.user) {
const response = req.user;
if (req.user) {
const response = req.user;
const token = extractJWT(req);
const token = extractJWT(req);
if (token) {
const decoded = jwt.decode(token);
if (decoded) {
response.exp = decoded.exp;
}
if (token) {
const decoded = jwt.decode(token);
if (decoded) {
response.exp = decoded.exp;
}
return response;
}
return null;
} catch (error) {
throw error;
return response;
}
return null;
};
module.exports = me;

View File

@@ -11,7 +11,7 @@ const policies = async (args) => {
const promises = [];
const isLoggedIn = !!(user);
const userCollectionConfig = (user && user.collection) ? config.collections.find(collection => collection.slug === user.collection) : null;
const userCollectionConfig = (user && user.collection) ? config.collections.find((collection) => collection.slug === user.collection) : null;
const createPolicyPromise = async (obj, policy, operation, disableWhere = false) => {
const updatedObj = obj;
@@ -72,27 +72,23 @@ const policies = async (args) => {
});
};
try {
if (userCollectionConfig) {
results.canAccessAdmin = userCollectionConfig.policies.admin ? userCollectionConfig.policies.admin(args) : isLoggedIn;
} else {
results.canAccessAdmin = false;
}
config.collections.forEach((collection) => {
executeEntityPolicies(collection, allOperations);
});
config.globals.forEach((global) => {
executeEntityPolicies(global, ['read', 'update']);
});
await Promise.all(promises);
return results;
} catch (error) {
throw error;
if (userCollectionConfig) {
results.canAccessAdmin = userCollectionConfig.policies.admin ? userCollectionConfig.policies.admin(args) : isLoggedIn;
} else {
results.canAccessAdmin = false;
}
config.collections.forEach((collection) => {
executeEntityPolicies(collection, allOperations);
});
config.globals.forEach((global) => {
executeEntityPolicies(global, ['read', 'update']);
});
await Promise.all(promises);
return results;
};
module.exports = policies;

View File

@@ -2,73 +2,69 @@ const jwt = require('jsonwebtoken');
const { Forbidden } = require('../../errors');
const refresh = async (args) => {
try {
// Await validation here
// Await validation here
let options = { ...args };
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before refresh hook
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Execute before refresh hook
// /////////////////////////////////////
const { beforeRefresh } = args.collection.config.hooks;
const { beforeRefresh } = args.collection.config.hooks;
if (typeof beforeRefresh === 'function') {
options = await beforeRefresh(options);
}
// /////////////////////////////////////
// 2. Perform refresh
// /////////////////////////////////////
const { secret, cookiePrefix } = options.config;
const opts = {};
opts.expiresIn = options.collection.config.auth.tokenExpiration;
if (typeof options.token !== 'string') throw new Forbidden();
const payload = jwt.verify(options.token, secret, {});
delete payload.iat;
delete payload.exp;
const refreshedToken = jwt.sign(payload, secret, opts);
if (args.res) {
const cookieOptions = {
path: '/',
httpOnly: true,
};
if (options.collection.config.auth.secureCookie) {
cookieOptions.secure = true;
}
args.res.cookie(`${cookiePrefix}-token`, refreshedToken, cookieOptions);
}
// /////////////////////////////////////
// 3. Execute after login hook
// /////////////////////////////////////
const { afterRefresh } = args.collection.config.hooks;
if (typeof afterRefresh === 'function') {
await afterRefresh(options, refreshedToken);
}
// /////////////////////////////////////
// 4. Return refreshed token
// /////////////////////////////////////
payload.exp = jwt.decode(refreshedToken).exp;
return {
refreshedToken,
user: payload,
};
} catch (error) {
throw error;
if (typeof beforeRefresh === 'function') {
options = await beforeRefresh(options);
}
// /////////////////////////////////////
// 2. Perform refresh
// /////////////////////////////////////
const { secret, cookiePrefix } = options.config;
const opts = {};
opts.expiresIn = options.collection.config.auth.tokenExpiration;
if (typeof options.token !== 'string') throw new Forbidden();
const payload = jwt.verify(options.token, secret, {});
delete payload.iat;
delete payload.exp;
const refreshedToken = jwt.sign(payload, secret, opts);
if (args.res) {
const cookieOptions = {
path: '/',
httpOnly: true,
};
if (options.collection.config.auth.secureCookie) {
cookieOptions.secure = true;
}
args.res.cookie(`${cookiePrefix}-token`, refreshedToken, cookieOptions);
}
// /////////////////////////////////////
// 3. Execute after login hook
// /////////////////////////////////////
const { afterRefresh } = args.collection.config.hooks;
if (typeof afterRefresh === 'function') {
await afterRefresh(options, refreshedToken);
}
// /////////////////////////////////////
// 4. Return refreshed token
// /////////////////////////////////////
payload.exp = jwt.decode(refreshedToken).exp;
return {
refreshedToken,
user: payload,
};
};
module.exports = refresh;

View File

@@ -3,91 +3,87 @@ const executePolicy = require('../executePolicy');
const performFieldOperations = require('../../fields/performFieldOperations');
const register = async (args) => {
try {
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
if (!args.overridePolicy) {
await executePolicy(args, args.collection.config.policies.create);
}
let options = { ...args };
// /////////////////////////////////////
// 2. Execute before register hook
// /////////////////////////////////////
const { beforeRegister } = args.collection.config.hooks;
if (typeof beforeRegister === 'function') {
options = await beforeRegister(options);
}
// /////////////////////////////////////
// 3. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.collection.config, { ...options, hook: 'beforeCreate', operationName: 'create' });
// /////////////////////////////////////
// 6. Perform register
// /////////////////////////////////////
const {
collection: {
Model,
},
data,
req: {
locale,
fallbackLocale,
},
} = options;
const modelData = { ...data };
delete modelData.password;
const user = new Model();
if (locale && user.setLocale) {
user.setLocale(locale, fallbackLocale);
}
Object.assign(user, modelData);
let result = await Model.register(user, data.password);
await passport.authenticate('local');
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and policies
// /////////////////////////////////////
result = await performFieldOperations(args.collection.config, {
...options, data: result, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 8. Execute after register hook
// /////////////////////////////////////
const afterRegister = args.collection.config.hooks;
if (typeof afterRegister === 'function') {
result = await afterRegister(options, result);
}
// /////////////////////////////////////
// 9. Return user
// /////////////////////////////////////
return result;
} catch (error) {
throw error;
if (!args.overridePolicy) {
await executePolicy(args, args.collection.config.policies.create);
}
let options = { ...args };
// /////////////////////////////////////
// 2. Execute before register hook
// /////////////////////////////////////
const { beforeRegister } = args.collection.config.hooks;
if (typeof beforeRegister === 'function') {
options = await beforeRegister(options);
}
// /////////////////////////////////////
// 3. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.collection.config, { ...options, hook: 'beforeCreate', operationName: 'create' });
// /////////////////////////////////////
// 6. Perform register
// /////////////////////////////////////
const {
collection: {
Model,
},
data,
req: {
locale,
fallbackLocale,
},
} = options;
const modelData = { ...data };
delete modelData.password;
const user = new Model();
if (locale && user.setLocale) {
user.setLocale(locale, fallbackLocale);
}
Object.assign(user, modelData);
let result = await Model.register(user, data.password);
await passport.authenticate('local');
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and policies
// /////////////////////////////////////
result = await performFieldOperations(args.collection.config, {
...options, data: result, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 8. Execute after register hook
// /////////////////////////////////////
const afterRegister = args.collection.config.hooks;
if (typeof afterRegister === 'function') {
result = await afterRegister(options, result);
}
// /////////////////////////////////////
// 9. Return user
// /////////////////////////////////////
return result;
};
module.exports = register;

View File

@@ -3,62 +3,58 @@ const login = require('./login');
const { Forbidden } = require('../../errors');
const registerFirstUser = async (args) => {
try {
const count = await args.collection.Model.countDocuments({});
const count = await args.collection.Model.countDocuments({});
if (count >= 1) throw new Forbidden();
if (count >= 1) throw new Forbidden();
// Await validation here
// Await validation here
let options = { ...args };
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before register first user hook
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Execute before register first user hook
// /////////////////////////////////////
const { beforeRegister } = args.collection.config.hooks;
const { beforeRegister } = args.collection.config.hooks;
if (typeof beforeRegister === 'function') {
options = await beforeRegister(options);
}
// /////////////////////////////////////
// 2. Perform register first user
// /////////////////////////////////////
let result = await register({
...options,
overridePolicy: true,
});
// /////////////////////////////////////
// 3. Log in new user
// /////////////////////////////////////
const token = await login({
...options,
});
result = {
...result,
token,
};
// /////////////////////////////////////
// 4. Execute after register first user hook
// /////////////////////////////////////
const afterRegister = args.config.hooks;
if (typeof afterRegister === 'function') {
result = await afterRegister(options, result);
}
return result;
} catch (error) {
throw error;
if (typeof beforeRegister === 'function') {
options = await beforeRegister(options);
}
// /////////////////////////////////////
// 2. Perform register first user
// /////////////////////////////////////
let result = await register({
...options,
overridePolicy: true,
});
// /////////////////////////////////////
// 3. Log in new user
// /////////////////////////////////////
const token = await login({
...options,
});
result = {
...result,
token,
};
// /////////////////////////////////////
// 4. Execute after register first user hook
// /////////////////////////////////////
const afterRegister = args.config.hooks;
if (typeof afterRegister === 'function') {
result = await afterRegister(options, result);
}
return result;
};
module.exports = registerFirstUser;

View File

@@ -2,93 +2,89 @@ const jwt = require('jsonwebtoken');
const { APIError } = require('../../errors');
const resetPassword = async (args) => {
try {
if (!Object.prototype.hasOwnProperty.call(args.data, 'token')
|| !Object.prototype.hasOwnProperty.call(args.data, 'password')) {
throw new APIError('Missing required data.');
}
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before reset password hook
// /////////////////////////////////////
const { beforeResetPassword } = args.collection.config.hooks;
if (typeof beforeResetPassword === 'function') {
options = await beforeResetPassword(options);
}
// /////////////////////////////////////
// 2. Perform password reset
// /////////////////////////////////////
const {
collection: {
Model,
config: collectionConfig,
},
config,
data,
} = options;
const { email } = data;
const user = await Model.findOne({
resetPasswordToken: data.token,
resetPasswordExpiration: { $gt: Date.now() },
});
if (!user) throw new APIError('Token is either invalid or has expired.');
await user.setPassword(data.password);
user.resetPasswordExpiration = Date.now();
await user.save();
await user.authenticate(data.password);
const fieldsToSign = collectionConfig.fields.reduce((signedFields, field) => {
if (field.saveToJWT) {
return {
...signedFields,
[field.name]: user[field.name],
};
}
return signedFields;
}, {
email,
});
const token = jwt.sign(
fieldsToSign,
config.secret,
{
expiresIn: collectionConfig.auth.tokenExpiration,
},
);
// /////////////////////////////////////
// 3. Execute after reset password hook
// /////////////////////////////////////
const { afterResetPassword } = collectionConfig.hooks;
if (typeof afterResetPassword === 'function') {
await afterResetPassword(options, user);
}
// /////////////////////////////////////
// 4. Return updated user
// /////////////////////////////////////
return token;
} catch (error) {
throw error;
if (!Object.prototype.hasOwnProperty.call(args.data, 'token')
|| !Object.prototype.hasOwnProperty.call(args.data, 'password')) {
throw new APIError('Missing required data.');
}
let options = { ...args };
// /////////////////////////////////////
// 1. Execute before reset password hook
// /////////////////////////////////////
const { beforeResetPassword } = args.collection.config.hooks;
if (typeof beforeResetPassword === 'function') {
options = await beforeResetPassword(options);
}
// /////////////////////////////////////
// 2. Perform password reset
// /////////////////////////////////////
const {
collection: {
Model,
config: collectionConfig,
},
config,
data,
} = options;
const { email } = data;
const user = await Model.findOne({
resetPasswordToken: data.token,
resetPasswordExpiration: { $gt: Date.now() },
});
if (!user) throw new APIError('Token is either invalid or has expired.');
await user.setPassword(data.password);
user.resetPasswordExpiration = Date.now();
await user.save();
await user.authenticate(data.password);
const fieldsToSign = collectionConfig.fields.reduce((signedFields, field) => {
if (field.saveToJWT) {
return {
...signedFields,
[field.name]: user[field.name],
};
}
return signedFields;
}, {
email,
});
const token = jwt.sign(
fieldsToSign,
config.secret,
{
expiresIn: collectionConfig.auth.tokenExpiration,
},
);
// /////////////////////////////////////
// 3. Execute after reset password hook
// /////////////////////////////////////
const { afterResetPassword } = collectionConfig.hooks;
if (typeof afterResetPassword === 'function') {
await afterResetPassword(options, user);
}
// /////////////////////////////////////
// 4. Return updated user
// /////////////////////////////////////
return token;
};
module.exports = resetPassword;

View File

@@ -5,119 +5,115 @@ const executePolicy = require('../executePolicy');
const performFieldOperations = require('../../fields/performFieldOperations');
const update = async (args) => {
try {
// /////////////////////////////////////
// 1. Execute policy
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Execute policy
// /////////////////////////////////////
const policyResults = await executePolicy(args, args.config.policies.update);
const hasWherePolicy = typeof policyResults === 'object';
const policyResults = await executePolicy(args, args.config.policies.update);
const hasWherePolicy = typeof policyResults === 'object';
let options = { ...args };
let options = { ...args };
// /////////////////////////////////////
// 2. Retrieve document
// /////////////////////////////////////
// /////////////////////////////////////
// 2. Retrieve document
// /////////////////////////////////////
const {
Model,
id,
req: {
locale,
fallbackLocale,
},
} = options;
const {
Model,
id,
req: {
locale,
fallbackLocale,
},
} = options;
let query = { _id: id };
let query = { _id: id };
if (hasWherePolicy) {
query = {
...query,
...policyResults,
};
}
let user = await Model.findOne(query);
if (!user && !hasWherePolicy) throw new NotFound();
if (!user && hasWherePolicy) throw new Forbidden();
if (locale && user.setLocale) {
user.setLocale(locale, fallbackLocale);
}
const userJSON = user.toJSON({ virtuals: true });
// /////////////////////////////////////
// 2. Execute before update hook
// /////////////////////////////////////
const { beforeUpdate } = args.config.hooks;
if (typeof beforeUpdate === 'function') {
options = await beforeUpdate(options);
}
// /////////////////////////////////////
// 3. Merge updates into existing data
// /////////////////////////////////////
options.data = deepmerge(userJSON, options.data, { arrayMerge: overwriteMerge });
// /////////////////////////////////////
// 4. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeUpdate', operationName: 'update' });
// /////////////////////////////////////
// 5. Handle password update
// /////////////////////////////////////
const dataToUpdate = { ...options.data };
const { password } = dataToUpdate;
if (password) {
delete dataToUpdate.password;
await user.setPassword(password);
}
// /////////////////////////////////////
// 6. Perform database operation
// /////////////////////////////////////
Object.assign(user, dataToUpdate);
await user.save();
user = user.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and policies
// /////////////////////////////////////
user = performFieldOperations(args.config, {
...options, data: user, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 8. Execute after update hook
// /////////////////////////////////////
const afterUpdateHook = args.config.hooks && args.config.hooks.afterUpdate;
if (typeof afterUpdateHook === 'function') {
user = await afterUpdateHook(options, user);
}
// /////////////////////////////////////
// 9. Return user
// /////////////////////////////////////
return user;
} catch (error) {
throw error;
if (hasWherePolicy) {
query = {
...query,
...policyResults,
};
}
let user = await Model.findOne(query);
if (!user && !hasWherePolicy) throw new NotFound();
if (!user && hasWherePolicy) throw new Forbidden();
if (locale && user.setLocale) {
user.setLocale(locale, fallbackLocale);
}
const userJSON = user.toJSON({ virtuals: true });
// /////////////////////////////////////
// 2. Execute before update hook
// /////////////////////////////////////
const { beforeUpdate } = args.config.hooks;
if (typeof beforeUpdate === 'function') {
options = await beforeUpdate(options);
}
// /////////////////////////////////////
// 3. Merge updates into existing data
// /////////////////////////////////////
options.data = deepmerge(userJSON, options.data, { arrayMerge: overwriteMerge });
// /////////////////////////////////////
// 4. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeUpdate', operationName: 'update' });
// /////////////////////////////////////
// 5. Handle password update
// /////////////////////////////////////
const dataToUpdate = { ...options.data };
const { password } = dataToUpdate;
if (password) {
delete dataToUpdate.password;
await user.setPassword(password);
}
// /////////////////////////////////////
// 6. Perform database operation
// /////////////////////////////////////
Object.assign(user, dataToUpdate);
await user.save();
user = user.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and policies
// /////////////////////////////////////
user = performFieldOperations(args.config, {
...options, data: user, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 8. Execute after update hook
// /////////////////////////////////////
const afterUpdateHook = args.config.hooks && args.config.hooks.afterUpdate;
if (typeof afterUpdateHook === 'function') {
user = await afterUpdateHook(options, user);
}
// /////////////////////////////////////
// 9. Return user
// /////////////////////////////////////
return user;
};
module.exports = update;

View File

@@ -1,7 +1,7 @@
const httpStatus = require('http-status');
const { login } = require('../operations');
const loginHandler = config => async (req, res, next) => {
const loginHandler = (config) => async (req, res, next) => {
try {
const token = await login({
req,

View File

@@ -11,120 +11,116 @@ const imageMIMETypes = require('../../uploads/imageMIMETypes');
const performFieldOperations = require('../../fields/performFieldOperations');
const create = async (args) => {
try {
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
await executePolicy(args, args.config.policies.create);
await executePolicy(args, args.config.policies.create);
let options = { ...args };
let options = { ...args };
// /////////////////////////////////////
// 2. Execute before collection hook
// /////////////////////////////////////
// /////////////////////////////////////
// 2. Execute before collection hook
// /////////////////////////////////////
const { beforeCreate } = args.config.hooks;
const { beforeCreate } = args.config.hooks;
if (typeof beforeCreate === 'function') {
options = await beforeCreate(options);
}
// /////////////////////////////////////
// 3. Execute field-level policies, hooks, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeCreate', operationName: 'create' });
// /////////////////////////////////////
// 4. Upload and resize any files that may be present
// /////////////////////////////////////
if (args.config.upload) {
const { staticDir, imageSizes } = options.req.collection.config.upload;
const fileData = {};
if (!args.req.files || Object.keys(args.req.files).length === 0) {
throw new MissingFile();
}
await mkdirp(staticDir);
const fsSafeName = await getSafeFilename(staticDir, options.req.files.file.name);
await options.req.files.file.mv(`${staticDir}/${fsSafeName}`);
if (imageMIMETypes.indexOf(options.req.files.file.mimetype) > -1) {
const dimensions = await getImageSize(`${staticDir}/${fsSafeName}`);
fileData.width = dimensions.width;
fileData.height = dimensions.height;
if (Array.isArray(imageSizes) && options.req.files.file.mimetype !== 'image/svg+xml') {
fileData.sizes = await resizeAndSave(options.config, fsSafeName, fileData.mimeType);
}
}
fileData.filename = fsSafeName;
fileData.filesize = options.req.files.file.size;
fileData.mimeType = options.req.files.file.mimetype;
options.data = {
...options.data,
...fileData,
};
}
// /////////////////////////////////////
// 5. Perform database operation
// /////////////////////////////////////
const {
Model,
data,
req: {
locale,
fallbackLocale,
},
} = options;
let result = new Model();
if (locale && result.setLocale) {
result.setLocale(locale, fallbackLocale);
}
Object.assign(result, data);
await result.save();
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 6. Execute field-level hooks and policies
// /////////////////////////////////////
result = await performFieldOperations(args.config, {
...options, data: result, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 7. Execute after collection hook
// /////////////////////////////////////
const { afterCreate } = args.config.hooks;
if (typeof afterCreate === 'function') {
result = await afterCreate(options, result);
}
// /////////////////////////////////////
// 8. Return results
// /////////////////////////////////////
return result;
} catch (err) {
throw err;
if (typeof beforeCreate === 'function') {
options = await beforeCreate(options);
}
// /////////////////////////////////////
// 3. Execute field-level policies, hooks, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeCreate', operationName: 'create' });
// /////////////////////////////////////
// 4. Upload and resize any files that may be present
// /////////////////////////////////////
if (args.config.upload) {
const { staticDir, imageSizes } = options.req.collection.config.upload;
const fileData = {};
if (!args.req.files || Object.keys(args.req.files).length === 0) {
throw new MissingFile();
}
await mkdirp(staticDir);
const fsSafeName = await getSafeFilename(staticDir, options.req.files.file.name);
await options.req.files.file.mv(`${staticDir}/${fsSafeName}`);
if (imageMIMETypes.indexOf(options.req.files.file.mimetype) > -1) {
const dimensions = await getImageSize(`${staticDir}/${fsSafeName}`);
fileData.width = dimensions.width;
fileData.height = dimensions.height;
if (Array.isArray(imageSizes) && options.req.files.file.mimetype !== 'image/svg+xml') {
fileData.sizes = await resizeAndSave(options.config, fsSafeName, fileData.mimeType);
}
}
fileData.filename = fsSafeName;
fileData.filesize = options.req.files.file.size;
fileData.mimeType = options.req.files.file.mimetype;
options.data = {
...options.data,
...fileData,
};
}
// /////////////////////////////////////
// 5. Perform database operation
// /////////////////////////////////////
const {
Model,
data,
req: {
locale,
fallbackLocale,
},
} = options;
let result = new Model();
if (locale && result.setLocale) {
result.setLocale(locale, fallbackLocale);
}
Object.assign(result, data);
await result.save();
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 6. Execute field-level hooks and policies
// /////////////////////////////////////
result = await performFieldOperations(args.config, {
...options, data: result, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 7. Execute after collection hook
// /////////////////////////////////////
const { afterCreate } = args.config.hooks;
if (typeof afterCreate === 'function') {
result = await afterCreate(options, result);
}
// /////////////////////////////////////
// 8. Return results
// /////////////////////////////////////
return result;
};
module.exports = create;

View File

@@ -1,113 +1,109 @@
const fs = require('fs');
const { NotFound, Forbidden } = require('../../errors');
const { NotFound, Forbidden, ErrorDeletingFile } = require('../../errors');
const executePolicy = require('../../auth/executePolicy');
const deleteQuery = async (args) => {
try {
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
const policyResults = await executePolicy(args, args.config.policies.delete);
const hasWherePolicy = typeof policyResults === 'object';
const policyResults = await executePolicy(args, args.config.policies.delete);
const hasWherePolicy = typeof policyResults === 'object';
let options = {
...args,
};
let options = {
...args,
};
// /////////////////////////////////////
// 2. Execute before collection hook
// /////////////////////////////////////
// /////////////////////////////////////
// 2. Execute before collection hook
// /////////////////////////////////////
const { beforeDelete } = args.config.hooks;
const { beforeDelete } = args.config.hooks;
if (typeof beforeDelete === 'function') {
options = await beforeDelete(options);
}
// /////////////////////////////////////
// 3. Get existing document
// /////////////////////////////////////
const {
Model,
id,
req: {
locale,
fallbackLocale,
},
} = options;
let query = { _id: id };
if (hasWherePolicy) {
query = {
...query,
...policyResults,
};
}
let resultToDelete = await Model.findOne(query);
if (!resultToDelete && !hasWherePolicy) throw new NotFound();
if (!resultToDelete && hasWherePolicy) throw new Forbidden();
resultToDelete = resultToDelete.toJSON({ virtuals: true });
if (locale && resultToDelete.setLocale) {
resultToDelete.setLocale(locale, fallbackLocale);
}
// /////////////////////////////////////
// 4. Delete any associated files
// /////////////////////////////////////
if (options.req.collection.config.upload) {
const { staticDir } = options.req.collection.config.upload;
fs.unlink(`${staticDir}/${resultToDelete.filename}`, (err) => {
console.log('Error deleting file:', err);
});
if (resultToDelete.sizes) {
Object.values(resultToDelete.sizes).forEach((size) => {
fs.unlink(`${staticDir}/${size.filename}`, (err) => {
console.log('Error deleting file:', err);
});
});
}
}
// /////////////////////////////////////
// 5. Delete database document
// /////////////////////////////////////
let result = await Model.findOneAndDelete({ _id: id });
result = result.toJSON({ virtuals: true });
if (locale && result.setLocale) {
result.setLocale(locale, fallbackLocale);
}
// /////////////////////////////////////
// 4. Execute after collection hook
// /////////////////////////////////////
const { afterDelete } = args.config.hooks;
if (typeof afterDelete === 'function') {
result = await afterDelete(options, result) || result;
}
// /////////////////////////////////////
// 5. Return results
// /////////////////////////////////////
return result;
} catch (err) {
throw err;
if (typeof beforeDelete === 'function') {
options = await beforeDelete(options);
}
// /////////////////////////////////////
// 3. Get existing document
// /////////////////////////////////////
const {
Model,
id,
req: {
locale,
fallbackLocale,
},
} = options;
let query = { _id: id };
if (hasWherePolicy) {
query = {
...query,
...policyResults,
};
}
let resultToDelete = await Model.findOne(query);
if (!resultToDelete && !hasWherePolicy) throw new NotFound();
if (!resultToDelete && hasWherePolicy) throw new Forbidden();
resultToDelete = resultToDelete.toJSON({ virtuals: true });
if (locale && resultToDelete.setLocale) {
resultToDelete.setLocale(locale, fallbackLocale);
}
// /////////////////////////////////////
// 4. Delete any associated files
// /////////////////////////////////////
if (options.req.collection.config.upload) {
const { staticDir } = options.req.collection.config.upload;
fs.unlink(`${staticDir}/${resultToDelete.filename}`, () => {
throw new ErrorDeletingFile();
});
if (resultToDelete.sizes) {
Object.values(resultToDelete.sizes).forEach((size) => {
fs.unlink(`${staticDir}/${size.filename}`, () => {
throw new ErrorDeletingFile();
});
});
}
}
// /////////////////////////////////////
// 5. Delete database document
// /////////////////////////////////////
let result = await Model.findOneAndDelete({ _id: id });
result = result.toJSON({ virtuals: true });
if (locale && result.setLocale) {
result.setLocale(locale, fallbackLocale);
}
// /////////////////////////////////////
// 4. Execute after collection hook
// /////////////////////////////////////
const { afterDelete } = args.config.hooks;
if (typeof afterDelete === 'function') {
result = await afterDelete(options, result) || result;
}
// /////////////////////////////////////
// 5. Return results
// /////////////////////////////////////
return result;
};
module.exports = deleteQuery;

View File

@@ -2,146 +2,140 @@ const executePolicy = require('../../auth/executePolicy');
const performFieldOperations = require('../../fields/performFieldOperations');
const find = async (args) => {
try {
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
const policyResults = await executePolicy(args, args.config.policies.read);
const hasWherePolicy = typeof policyResults === 'object';
const policyResults = await executePolicy(args, args.config.policies.read);
const hasWherePolicy = typeof policyResults === 'object';
const queryToBuild = {};
const queryToBuild = {};
if (args.where) {
queryToBuild.where = {
and: [args.where],
};
}
if (hasWherePolicy) {
if (!args.where) {
queryToBuild.where = {
and: [
policyResults,
],
};
} else {
queryToBuild.where.and.push(policyResults);
}
}
let options = {
...args,
query: await args.Model.buildQuery(queryToBuild, args.req.locale),
if (args.where) {
queryToBuild.where = {
and: [args.where],
};
// /////////////////////////////////////
// 2. Execute before collection hook
// /////////////////////////////////////
const { beforeRead } = args.config.hooks;
if (typeof beforeRead === 'function') {
options = await beforeRead(options);
}
// /////////////////////////////////////
// 3. Perform database operation
// /////////////////////////////////////
const {
query,
page,
limit,
depth,
Model,
req: {
locale,
fallbackLocale,
payloadAPI,
},
config,
} = options;
let { sort } = options;
if (!sort) {
if (config.timestamps) {
sort = '-createdAt';
} else {
sort = '-_id';
}
}
const optionsToExecute = {
page: page || 1,
limit: limit || 10,
sort,
collation: sort ? { locale: 'en' } : {}, // case-insensitive sort in MongoDB
options: {},
};
// Only allow depth override within REST.
// If allowed in GraphQL, it would break resolvers
// as a full object will be returned instead of an ID string
if (payloadAPI === 'REST') {
if (depth && depth !== '0') {
optionsToExecute.options.autopopulate = {
maxDepth: parseInt(depth, 10),
};
} else {
optionsToExecute.options.autopopulate = false;
}
}
let result = await Model.paginate(query, optionsToExecute);
// /////////////////////////////////////
// 4. Execute field-level policies
// /////////////////////////////////////
result = {
...result,
docs: await Promise.all(result.docs.map(async (doc) => {
if (locale && doc.setLocale) {
doc.setLocale(locale, fallbackLocale);
}
const data = doc.toJSON({ virtuals: true });
return performFieldOperations(args.config, {
...options, data, hook: 'afterRead', operationName: 'read',
});
})),
};
// /////////////////////////////////////
// 6. Execute afterRead collection hook
// /////////////////////////////////////
const { afterRead } = args.config.hooks;
let afterReadResult = null;
if (typeof afterRead === 'function') {
afterReadResult = {
...result,
docs: await Promise.all(result.docs.map(async (doc) => {
return afterRead({
options,
doc,
}) || doc;
})),
};
}
// /////////////////////////////////////
// 7. Return results
// /////////////////////////////////////
return afterReadResult || result;
} catch (err) {
throw err;
}
if (hasWherePolicy) {
if (!args.where) {
queryToBuild.where = {
and: [
policyResults,
],
};
} else {
queryToBuild.where.and.push(policyResults);
}
}
let options = {
...args,
query: await args.Model.buildQuery(queryToBuild, args.req.locale),
};
// /////////////////////////////////////
// 2. Execute before collection hook
// /////////////////////////////////////
const { beforeRead } = args.config.hooks;
if (typeof beforeRead === 'function') {
options = await beforeRead(options);
}
// /////////////////////////////////////
// 3. Perform database operation
// /////////////////////////////////////
const {
query,
page,
limit,
depth,
Model,
req: {
locale,
fallbackLocale,
payloadAPI,
},
config,
} = options;
let { sort } = options;
if (!sort) {
if (config.timestamps) {
sort = '-createdAt';
} else {
sort = '-_id';
}
}
const optionsToExecute = {
page: page || 1,
limit: limit || 10,
sort,
collation: sort ? { locale: 'en' } : {}, // case-insensitive sort in MongoDB
options: {},
};
// Only allow depth override within REST.
// If allowed in GraphQL, it would break resolvers
// as a full object will be returned instead of an ID string
if (payloadAPI === 'REST') {
if (depth && depth !== '0') {
optionsToExecute.options.autopopulate = {
maxDepth: parseInt(depth, 10),
};
} else {
optionsToExecute.options.autopopulate = false;
}
}
let result = await Model.paginate(query, optionsToExecute);
// /////////////////////////////////////
// 4. Execute field-level policies
// /////////////////////////////////////
result = {
...result,
docs: await Promise.all(result.docs.map(async (doc) => {
if (locale && doc.setLocale) {
doc.setLocale(locale, fallbackLocale);
}
const data = doc.toJSON({ virtuals: true });
return performFieldOperations(args.config, {
...options, data, hook: 'afterRead', operationName: 'read',
});
})),
};
// /////////////////////////////////////
// 6. Execute afterRead collection hook
// /////////////////////////////////////
const { afterRead } = args.config.hooks;
let afterReadResult = null;
if (typeof afterRead === 'function') {
afterReadResult = {
...result,
docs: await Promise.all(result.docs.map(async (doc) => afterRead({
options,
doc,
}) || doc)),
};
}
// /////////////////////////////////////
// 7. Return results
// /////////////////////////////////////
return afterReadResult || result;
};
module.exports = find;

View File

@@ -3,118 +3,114 @@ const executePolicy = require('../../auth/executePolicy');
const performFieldOperations = require('../../fields/performFieldOperations');
const findByID = async (args) => {
try {
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
const policyResults = await executePolicy(args, args.config.policies.read);
const hasWherePolicy = typeof policyResults === 'object';
const policyResults = await executePolicy(args, args.config.policies.read);
const hasWherePolicy = typeof policyResults === 'object';
const queryToBuild = {
where: {
and: [
{
_id: {
equals: args.id,
},
const queryToBuild = {
where: {
and: [
{
_id: {
equals: args.id,
},
],
},
};
},
],
},
};
if (hasWherePolicy) {
queryToBuild.where.and.push(policyResults);
}
let options = {
...args,
query: await args.Model.buildQuery(queryToBuild, args.req.locale),
};
// /////////////////////////////////////
// 2. Execute before collection hook
// /////////////////////////////////////
const { beforeRead } = args.config.hooks;
if (typeof beforeRead === 'function') {
options = await beforeRead(options);
}
// /////////////////////////////////////
// 3. Perform database operation
// /////////////////////////////////////
const {
depth,
Model,
query,
req: {
locale,
fallbackLocale,
payloadAPI,
},
} = options;
const queryOptionsToExecute = {
options: {},
};
// Only allow depth override within REST.
// If allowed in GraphQL, it would break resolvers
// as a full object will be returned instead of an ID string
if (payloadAPI === 'REST') {
if (depth && depth !== '0') {
queryOptionsToExecute.options.autopopulate = {
maxDepth: parseInt(depth, 10),
};
} else {
queryOptionsToExecute.options.autopopulate = false;
}
}
let result = await Model.findOne(query, {}, queryOptionsToExecute);
if (!result && !hasWherePolicy) throw new NotFound();
if (!result && hasWherePolicy) throw new Forbidden();
if (locale && result.setLocale) {
result.setLocale(locale, fallbackLocale);
}
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 4. Execute field-level hooks and policies
// /////////////////////////////////////
result = await performFieldOperations(args.config, {
...options, data: result, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 5. Execute after collection hook
// /////////////////////////////////////
const { afterRead } = args.config.hooks;
if (typeof afterRead === 'function') {
result = await afterRead({
...options,
doc: result,
}) || result;
}
// /////////////////////////////////////
// 6. Return results
// /////////////////////////////////////
return result;
} catch (err) {
throw err;
if (hasWherePolicy) {
queryToBuild.where.and.push(policyResults);
}
let options = {
...args,
query: await args.Model.buildQuery(queryToBuild, args.req.locale),
};
// /////////////////////////////////////
// 2. Execute before collection hook
// /////////////////////////////////////
const { beforeRead } = args.config.hooks;
if (typeof beforeRead === 'function') {
options = await beforeRead(options);
}
// /////////////////////////////////////
// 3. Perform database operation
// /////////////////////////////////////
const {
depth,
Model,
query,
req: {
locale,
fallbackLocale,
payloadAPI,
},
} = options;
const queryOptionsToExecute = {
options: {},
};
// Only allow depth override within REST.
// If allowed in GraphQL, it would break resolvers
// as a full object will be returned instead of an ID string
if (payloadAPI === 'REST') {
if (depth && depth !== '0') {
queryOptionsToExecute.options.autopopulate = {
maxDepth: parseInt(depth, 10),
};
} else {
queryOptionsToExecute.options.autopopulate = false;
}
}
let result = await Model.findOne(query, {}, queryOptionsToExecute);
if (!result && !hasWherePolicy) throw new NotFound();
if (!result && hasWherePolicy) throw new Forbidden();
if (locale && result.setLocale) {
result.setLocale(locale, fallbackLocale);
}
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 4. Execute field-level hooks and policies
// /////////////////////////////////////
result = await performFieldOperations(args.config, {
...options, data: result, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 5. Execute after collection hook
// /////////////////////////////////////
const { afterRead } = args.config.hooks;
if (typeof afterRead === 'function') {
result = await afterRead({
...options,
doc: result,
}) || result;
}
// /////////////////////////////////////
// 6. Return results
// /////////////////////////////////////
return result;
};
module.exports = findByID;

View File

@@ -10,157 +10,153 @@ const getSafeFilename = require('../../uploads/getSafeFilename');
const resizeAndSave = require('../../uploads/imageResizer');
const update = async (args) => {
try {
// /////////////////////////////////////
// 1. Execute policy
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Execute policy
// /////////////////////////////////////
const policyResults = await executePolicy(args, args.config.policies.update);
const hasWherePolicy = typeof policyResults === 'object';
const policyResults = await executePolicy(args, args.config.policies.update);
const hasWherePolicy = typeof policyResults === 'object';
let options = { ...args };
let options = { ...args };
// /////////////////////////////////////
// 2. Retrieve document
// /////////////////////////////////////
// /////////////////////////////////////
// 2. Retrieve document
// /////////////////////////////////////
const {
Model,
id,
req: {
locale,
fallbackLocale,
},
} = options;
const {
Model,
id,
req: {
locale,
fallbackLocale,
},
} = options;
const queryToBuild = {
where: {
and: [
{
id: {
equals: id,
},
const queryToBuild = {
where: {
and: [
{
id: {
equals: id,
},
],
},
};
},
],
},
};
if (hasWherePolicy) {
queryToBuild.where.and.push(hasWherePolicy);
}
options.query = await args.Model.buildQuery(queryToBuild, locale);
let doc = await Model.findOne(options.query);
if (!doc && !hasWherePolicy) throw new NotFound();
if (!doc && hasWherePolicy) throw new Forbidden();
if (locale && doc.setLocale) {
doc.setLocale(locale, fallbackLocale);
}
options.originalDoc = doc.toJSON({ virtuals: true });
// /////////////////////////////////////
// 2. Execute before update hook
// /////////////////////////////////////
const { beforeUpdate } = args.config.hooks;
if (typeof beforeUpdate === 'function') {
options = await beforeUpdate(options);
}
// /////////////////////////////////////
// 3. Merge updates into existing data
// /////////////////////////////////////
options.data = deepmerge(options.originalDoc, options.data, { arrayMerge: overwriteMerge });
// /////////////////////////////////////
// 4. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeUpdate', operationName: 'update' });
// /////////////////////////////////////
// 5. Upload and resize any files that may be present
// /////////////////////////////////////
if (args.config.upload) {
const fileData = {};
const { staticDir, imageSizes } = args.config.upload;
if (options.req.files && options.req.files.file) {
const fsSafeName = await getSafeFilename(staticDir, options.req.files.file.name);
await options.req.files.file.mv(`${staticDir}/${fsSafeName}`);
fileData.filename = fsSafeName;
fileData.filesize = options.req.files.file.size;
fileData.mimeType = options.req.files.file.mimetype;
if (imageMIMETypes.indexOf(options.req.files.file.mimetype) > -1) {
const dimensions = await getImageSize(`${staticDir}/${fsSafeName}`);
fileData.width = dimensions.width;
fileData.height = dimensions.height;
if (Array.isArray(imageSizes) && options.req.files.file.mimetype !== 'image/svg+xml') {
fileData.sizes = await resizeAndSave(options.config, fsSafeName, fileData.mimeType);
}
}
options.data = {
...options.data,
...fileData,
};
} else if (options.data.file === null) {
options.data = {
...options.data,
filename: null,
sizes: null,
};
}
}
// /////////////////////////////////////
// 6. Perform database operation
// /////////////////////////////////////
Object.assign(doc, options.data);
await doc.save();
doc = doc.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and policies
// /////////////////////////////////////
doc = await performFieldOperations(args.config, {
...options, data: doc, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 8. Execute after collection hook
// /////////////////////////////////////
const { afterUpdate } = args.config.hooks;
if (typeof afterUpdate === 'function') {
doc = await afterUpdate(options, doc) || doc;
}
// /////////////////////////////////////
// 9. Return updated document
// /////////////////////////////////////
return doc;
} catch (err) {
throw err;
if (hasWherePolicy) {
queryToBuild.where.and.push(hasWherePolicy);
}
options.query = await args.Model.buildQuery(queryToBuild, locale);
let doc = await Model.findOne(options.query);
if (!doc && !hasWherePolicy) throw new NotFound();
if (!doc && hasWherePolicy) throw new Forbidden();
if (locale && doc.setLocale) {
doc.setLocale(locale, fallbackLocale);
}
options.originalDoc = doc.toJSON({ virtuals: true });
// /////////////////////////////////////
// 2. Execute before update hook
// /////////////////////////////////////
const { beforeUpdate } = args.config.hooks;
if (typeof beforeUpdate === 'function') {
options = await beforeUpdate(options);
}
// /////////////////////////////////////
// 3. Merge updates into existing data
// /////////////////////////////////////
options.data = deepmerge(options.originalDoc, options.data, { arrayMerge: overwriteMerge });
// /////////////////////////////////////
// 4. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeUpdate', operationName: 'update' });
// /////////////////////////////////////
// 5. Upload and resize any files that may be present
// /////////////////////////////////////
if (args.config.upload) {
const fileData = {};
const { staticDir, imageSizes } = args.config.upload;
if (options.req.files && options.req.files.file) {
const fsSafeName = await getSafeFilename(staticDir, options.req.files.file.name);
await options.req.files.file.mv(`${staticDir}/${fsSafeName}`);
fileData.filename = fsSafeName;
fileData.filesize = options.req.files.file.size;
fileData.mimeType = options.req.files.file.mimetype;
if (imageMIMETypes.indexOf(options.req.files.file.mimetype) > -1) {
const dimensions = await getImageSize(`${staticDir}/${fsSafeName}`);
fileData.width = dimensions.width;
fileData.height = dimensions.height;
if (Array.isArray(imageSizes) && options.req.files.file.mimetype !== 'image/svg+xml') {
fileData.sizes = await resizeAndSave(options.config, fsSafeName, fileData.mimeType);
}
}
options.data = {
...options.data,
...fileData,
};
} else if (options.data.file === null) {
options.data = {
...options.data,
filename: null,
sizes: null,
};
}
}
// /////////////////////////////////////
// 6. Perform database operation
// /////////////////////////////////////
Object.assign(doc, options.data);
await doc.save();
doc = doc.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and policies
// /////////////////////////////////////
doc = await performFieldOperations(args.config, {
...options, data: doc, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 8. Execute after collection hook
// /////////////////////////////////////
const { afterUpdate } = args.config.hooks;
if (typeof afterUpdate === 'function') {
doc = await afterUpdate(options, doc) || doc;
}
// /////////////////////////////////////
// 9. Return updated document
// /////////////////////////////////////
return doc;
};
module.exports = update;

View File

@@ -0,0 +1,10 @@
const httpStatus = require('http-status');
const APIError = require('./APIError');
class ErrorDeletingFile extends APIError {
constructor() {
super('There was an error deleting file.', httpStatus.INTERNAL_SERVER_ERROR);
}
}
module.exports = ErrorDeletingFile;

View File

@@ -10,9 +10,11 @@ const ValidationError = require('./ValidationError');
const errorHandler = require('../express/middleware/errorHandler');
const MissingFile = require('./MissingFile');
const MissingSelectOptions = require('./MissingSelectOptions');
const ErrorDeletingFile = require('./ErrorDeletingFile');
module.exports = {
errorHandler,
ErrorDeletingFile,
APIError,
AuthenticationError,
DuplicateCollection,

View File

@@ -101,19 +101,15 @@ module.exports = async (config, operation) => {
// Entry point for field validation
// //////////////////////////////////////////
try {
traverseFields(config.fields, fullData, fullOriginalDoc, '');
await Promise.all(validationPromises);
traverseFields(config.fields, fullData, fullOriginalDoc, '');
await Promise.all(validationPromises);
if (errors.length > 0) {
throw new ValidationError(errors);
}
await Promise.all(policyPromises);
await Promise.all(hookPromises);
return fullData;
} catch (error) {
throw error;
if (errors.length > 0) {
throw new ValidationError(errors);
}
await Promise.all(policyPromises);
await Promise.all(hookPromises);
return fullData;
};

View File

@@ -2,97 +2,93 @@ const executePolicy = require('../../auth/executePolicy');
const performFieldOperations = require('../../fields/performFieldOperations');
const findOne = async (args) => {
try {
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
await executePolicy(args, args.config.policies.read);
await executePolicy(args, args.config.policies.read);
let options = { ...args };
let options = { ...args };
// /////////////////////////////////////
// 2. Execute before collection hook
// /////////////////////////////////////
// /////////////////////////////////////
// 2. Execute before collection hook
// /////////////////////////////////////
const { beforeRead } = args.config.hooks;
const { beforeRead } = args.config.hooks;
if (typeof beforeRead === 'function') {
options = await beforeRead(options);
}
// /////////////////////////////////////
// 3. Perform database operation
// /////////////////////////////////////
const {
depth,
Model,
slug,
req: {
payloadAPI,
locale,
fallbackLocale,
},
} = options;
const queryOptionsToExecute = {
options: {},
};
// Only allow depth override within REST.
// If allowed in GraphQL, it would break resolvers
// as a full object will be returned instead of an ID string
if (payloadAPI === 'REST') {
if (depth && depth !== '0') {
queryOptionsToExecute.options.autopopulate = {
maxDepth: parseInt(depth, 10),
};
} else {
queryOptionsToExecute.options.autopopulate = false;
}
}
let result = await Model.findOne({ globalType: slug });
let data = {};
if (!result) {
result = {};
} else {
if (locale && result.setLocale) {
result.setLocale(locale, fallbackLocale);
}
data = result.toJSON({ virtuals: true });
}
// /////////////////////////////////////
// 4. Execute field-level hooks and policies
// /////////////////////////////////////
result = performFieldOperations(args.config, {
...options, data, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 5. Execute after collection hook
// /////////////////////////////////////
const { afterRead } = args.config.hooks;
if (typeof afterRead === 'function') {
data = await afterRead(options, result, data) || data;
}
// /////////////////////////////////////
// 6. Return results
// /////////////////////////////////////
return data;
} catch (error) {
throw error;
if (typeof beforeRead === 'function') {
options = await beforeRead(options);
}
// /////////////////////////////////////
// 3. Perform database operation
// /////////////////////////////////////
const {
depth,
Model,
slug,
req: {
payloadAPI,
locale,
fallbackLocale,
},
} = options;
const queryOptionsToExecute = {
options: {},
};
// Only allow depth override within REST.
// If allowed in GraphQL, it would break resolvers
// as a full object will be returned instead of an ID string
if (payloadAPI === 'REST') {
if (depth && depth !== '0') {
queryOptionsToExecute.options.autopopulate = {
maxDepth: parseInt(depth, 10),
};
} else {
queryOptionsToExecute.options.autopopulate = false;
}
}
let result = await Model.findOne({ globalType: slug });
let data = {};
if (!result) {
result = {};
} else {
if (locale && result.setLocale) {
result.setLocale(locale, fallbackLocale);
}
data = result.toJSON({ virtuals: true });
}
// /////////////////////////////////////
// 4. Execute field-level hooks and policies
// /////////////////////////////////////
result = performFieldOperations(args.config, {
...options, data, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 5. Execute after collection hook
// /////////////////////////////////////
const { afterRead } = args.config.hooks;
if (typeof afterRead === 'function') {
data = await afterRead(options, result, data) || data;
}
// /////////////////////////////////////
// 6. Return results
// /////////////////////////////////////
return data;
};
module.exports = findOne;

View File

@@ -4,98 +4,94 @@ const executePolicy = require('../../auth/executePolicy');
const performFieldOperations = require('../../fields/performFieldOperations');
const update = async (args) => {
try {
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
// /////////////////////////////////////
// 1. Retrieve and execute policy
// /////////////////////////////////////
await executePolicy(args, args.config.policies.update);
await executePolicy(args, args.config.policies.update);
let options = { ...args };
let options = { ...args };
// /////////////////////////////////////
// 2. Retrieve document
// /////////////////////////////////////
// /////////////////////////////////////
// 2. Retrieve document
// /////////////////////////////////////
const {
Model,
slug,
req: {
locale,
fallbackLocale,
},
} = options;
const {
Model,
slug,
req: {
locale,
fallbackLocale,
},
} = options;
let global = await Model.findOne({ globalType: slug });
let global = await Model.findOne({ globalType: slug });
if (!global) {
global = new Model({ globalType: slug });
}
if (locale && global.setLocale) {
global.setLocale(locale, fallbackLocale);
}
const globalJSON = global.toJSON({ virtuals: true });
// /////////////////////////////////////
// 3. Execute before global hook
// /////////////////////////////////////
const { beforeUpdate } = args.config.hooks;
if (typeof beforeUpdate === 'function') {
options = await beforeUpdate(options);
}
// /////////////////////////////////////
// 4. Merge updates into existing data
// /////////////////////////////////////
options.data = deepmerge(globalJSON, options.data, { arrayMerge: overwriteMerge });
// /////////////////////////////////////
// 5. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeUpdate', operationName: 'update' });
// /////////////////////////////////////
// 6. Perform database operation
// /////////////////////////////////////
Object.assign(global, options.data);
await global.save();
global = global.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and policies
// /////////////////////////////////////
global = await performFieldOperations(args.config, {
...options, data: global, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 8. Execute after global hook
// /////////////////////////////////////
const { afterUpdate } = args.config.hooks;
if (typeof afterUpdate === 'function') {
global = await afterUpdate(options, global);
}
// /////////////////////////////////////
// 9. Return global
// /////////////////////////////////////
return global;
} catch (error) {
throw error;
if (!global) {
global = new Model({ globalType: slug });
}
if (locale && global.setLocale) {
global.setLocale(locale, fallbackLocale);
}
const globalJSON = global.toJSON({ virtuals: true });
// /////////////////////////////////////
// 3. Execute before global hook
// /////////////////////////////////////
const { beforeUpdate } = args.config.hooks;
if (typeof beforeUpdate === 'function') {
options = await beforeUpdate(options);
}
// /////////////////////////////////////
// 4. Merge updates into existing data
// /////////////////////////////////////
options.data = deepmerge(globalJSON, options.data, { arrayMerge: overwriteMerge });
// /////////////////////////////////////
// 5. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeUpdate', operationName: 'update' });
// /////////////////////////////////////
// 6. Perform database operation
// /////////////////////////////////////
Object.assign(global, options.data);
await global.save();
global = global.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and policies
// /////////////////////////////////////
global = await performFieldOperations(args.config, {
...options, data: global, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
// 8. Execute after global hook
// /////////////////////////////////////
const { afterUpdate } = args.config.hooks;
if (typeof afterUpdate === 'function') {
global = await afterUpdate(options, global);
}
// /////////////////////////////////////
// 9. Return global
// /////////////////////////////////////
return global;
};
module.exports = update;

View File

@@ -58,7 +58,6 @@ class Payload {
this.router.use(
this.config.routes.graphQL,
identifyAPI('GraphQL'),
authenticate(this.config),
new GraphQL(this).init(),
);