removes unnecessary try / catch from operations
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
10
src/errors/ErrorDeletingFile.js
Normal file
10
src/errors/ErrorDeletingFile.js
Normal 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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -58,7 +58,6 @@ class Payload {
|
||||
this.router.use(
|
||||
this.config.routes.graphQL,
|
||||
identifyAPI('GraphQL'),
|
||||
authenticate(this.config),
|
||||
new GraphQL(this).init(),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user