removes autopopulate, adds manual population

This commit is contained in:
James
2020-07-20 16:11:22 -04:00
parent 0bfc10d55c
commit 85bcbfd918
56 changed files with 527 additions and 528 deletions

View File

@@ -5,7 +5,8 @@ module.exports = {
label: 'Global with Strict Access',
access: {
update: ({ req: { user } }) => checkRole(['admin'], user),
read: ({ req: { user } }) => checkRole(['admin'], user),
// read: ({ req: { user } }) => checkRole(['admin'], user),
read: () => true,
},
fields: [
{

View File

@@ -66,7 +66,6 @@
"mkdirp": "^0.5.1",
"mongodb-memory-server": "^6.5.2",
"mongoose": "^5.8.9",
"mongoose-autopopulate": "^0.11.0",
"mongoose-hidden": "^1.8.1",
"mongoose-paginate-v2": "^1.3.6",
"node-sass": "^4.13.1",

View File

@@ -5,7 +5,7 @@ const executeAccess = async (operation, access) => {
const result = await access(operation);
if (!result) {
throw new Forbidden();
if (!operation.disableErrors) throw new Forbidden();
}
return result;
@@ -15,7 +15,8 @@ const executeAccess = async (operation, access) => {
return true;
}
throw new Forbidden();
if (!operation.disableErrors) throw new Forbidden();
return false;
};
module.exports = executeAccess;

View File

@@ -1,8 +1,9 @@
const allOperations = ['create', 'read', 'update', 'delete'];
const accessOperation = async (args) => {
async function accessOperation(args) {
const { config } = this;
const {
config,
req,
req: { user },
} = args;
@@ -101,6 +102,6 @@ const accessOperation = async (args) => {
await Promise.all(promises);
return results;
};
}
module.exports = accessOperation;

View File

@@ -1,7 +1,9 @@
const crypto = require('crypto');
const { APIError } = require('../../errors');
const forgotPassword = async (args) => {
async function forgotPassword(args) {
const { config, email } = this;
if (!Object.prototype.hasOwnProperty.call(args.data, 'email')) {
throw new APIError('Missing email.');
}
@@ -26,9 +28,7 @@ const forgotPassword = async (args) => {
collection: {
Model,
},
config,
data,
email,
} = options;
let token = await crypto.randomBytes(20);
@@ -66,6 +66,6 @@ const forgotPassword = async (args) => {
if (typeof afterForgotPassword === 'function') {
await afterForgotPassword(options);
}
};
}
module.exports = forgotPassword;

View File

@@ -1,25 +0,0 @@
const login = require('./login');
const refresh = require('./refresh');
const register = require('./register');
const init = require('./init');
const forgotPassword = require('./forgotPassword');
const resetPassword = require('./resetPassword');
const registerFirstUser = require('./registerFirstUser');
const update = require('./update');
const access = require('./access');
const me = require('./me');
const logout = require('./logout');
module.exports = {
login,
refresh,
init,
register,
forgotPassword,
update,
resetPassword,
registerFirstUser,
access,
me,
logout,
};

View File

@@ -1,4 +1,4 @@
const init = async (args) => {
async function init(args) {
const {
Model,
} = args;
@@ -8,6 +8,6 @@ const init = async (args) => {
if (count >= 1) return true;
return false;
};
}
module.exports = init;

View File

@@ -1,8 +1,8 @@
const jwt = require('jsonwebtoken');
const { AuthenticationError } = require('../../errors');
const login = async (args) => {
// Await validation here
async function login(args) {
const { config } = this;
const options = { ...args };
@@ -21,7 +21,6 @@ const login = async (args) => {
Model,
config: collectionConfig,
},
config,
data,
} = options;
@@ -90,6 +89,6 @@ const login = async (args) => {
// /////////////////////////////////////
return token;
};
}
module.exports = login;

View File

@@ -1,6 +1,7 @@
const logout = async (args) => {
async function logout(args) {
const { config } = this;
const {
config,
collection: {
config: collectionConfig,
},
@@ -28,6 +29,6 @@ const logout = async (args) => {
res.cookie(`${config.cookiePrefix}-token`, '', cookieOptions);
return 'Logged out successfully.';
};
}
module.exports = logout;

View File

@@ -1,8 +1,8 @@
const jwt = require('jsonwebtoken');
const getExtractJWT = require('../getExtractJWT');
const me = async ({ req, config }) => {
const extractJWT = getExtractJWT(config);
async function me({ req }) {
const extractJWT = getExtractJWT(this.config);
if (req.user) {
const response = {
@@ -26,6 +26,6 @@ const me = async ({ req, config }) => {
return {
user: null,
};
};
}
module.exports = me;

View File

@@ -1,9 +1,10 @@
const jwt = require('jsonwebtoken');
const { Forbidden } = require('../../errors');
const refresh = async (args) => {
async function refresh(args) {
// Await validation here
const { secret, cookiePrefix } = this.config;
let options = { ...args };
// /////////////////////////////////////
@@ -20,7 +21,6 @@ const refresh = async (args) => {
// 2. Perform refresh
// /////////////////////////////////////
const { secret, cookiePrefix } = options.config;
const opts = {};
opts.expiresIn = options.collection.config.auth.tokenExpiration;
@@ -71,6 +71,6 @@ const refresh = async (args) => {
refreshedToken,
user: payload,
};
};
}
module.exports = refresh;

View File

@@ -1,12 +1,12 @@
const passport = require('passport');
const executeAccess = require('../executeAccess');
const performFieldOperations = require('../../fields/performFieldOperations');
const register = async (args) => {
async function register(args) {
const { config } = this;
const {
depth,
overrideAccess,
config,
collection: {
Model,
config: collectionConfig,
@@ -45,7 +45,7 @@ const register = async (args) => {
// 3. Execute field-level hooks, access, and validation
// /////////////////////////////////////
data = await performFieldOperations(config, collectionConfig, {
data = await this.performFieldOperations(collectionConfig, {
data,
hook: 'beforeCreate',
operationName: 'create',
@@ -77,7 +77,7 @@ const register = async (args) => {
// 7. Execute field-level hooks and access
// /////////////////////////////////////
result = await performFieldOperations(config, collectionConfig, {
result = await this.performFieldOperations(collectionConfig, {
data: result,
hook: 'afterRead',
operationName: 'read',
@@ -103,6 +103,6 @@ const register = async (args) => {
// /////////////////////////////////////
return result;
};
}
module.exports = register;

View File

@@ -2,7 +2,7 @@ const register = require('./register');
const login = require('./login');
const { Forbidden } = require('../../errors');
const registerFirstUser = async (args) => {
async function registerFirstUser(args) {
const {
collection: {
Model,
@@ -40,6 +40,6 @@ const registerFirstUser = async (args) => {
message: 'Registered successfully. Welcome to Payload!',
user: result,
};
};
}
module.exports = registerFirstUser;

View File

@@ -1,7 +1,9 @@
const jwt = require('jsonwebtoken');
const { APIError } = require('../../errors');
const resetPassword = async (args) => {
async function resetPassword(args) {
const { config } = this;
if (!Object.prototype.hasOwnProperty.call(args.data, 'token')
|| !Object.prototype.hasOwnProperty.call(args.data, 'password')) {
throw new APIError('Missing required data.');
@@ -28,7 +30,6 @@ const resetPassword = async (args) => {
Model,
config: collectionConfig,
},
config,
data,
} = options;
@@ -85,6 +86,6 @@ const resetPassword = async (args) => {
// /////////////////////////////////////
return token;
};
}
module.exports = resetPassword;

View File

@@ -2,12 +2,12 @@ const deepmerge = require('deepmerge');
const overwriteMerge = require('../../utilities/overwriteMerge');
const { NotFound, Forbidden } = require('../../errors');
const executeAccess = require('../executeAccess');
const performFieldOperations = require('../../fields/performFieldOperations');
const update = async (args) => {
async function update(args) {
const { config } = this;
const {
depth,
config,
collection: {
Model,
config: collectionConfig,
@@ -77,7 +77,7 @@ const update = async (args) => {
// 4. Execute field-level hooks, access, and validation
// /////////////////////////////////////
data = await performFieldOperations(config, collectionConfig, {
data = await this.performFieldOperations(collectionConfig, {
data,
req,
hook: 'beforeUpdate',
@@ -111,7 +111,7 @@ const update = async (args) => {
// 7. Execute field-level hooks and access
// /////////////////////////////////////
user = performFieldOperations(config, collectionConfig, {
user = this.performFieldOperations(collectionConfig, {
data: user,
hook: 'afterRead',
operationName: 'read',
@@ -137,6 +137,6 @@ const update = async (args) => {
// /////////////////////////////////////
return user;
};
}
module.exports = update;

View File

@@ -1,11 +1,9 @@
const httpStatus = require('http-status');
const { access } = require('../operations');
const policiesHandler = (config) => async (req, res, next) => {
async function policiesHandler(req, res, next) {
try {
const accessResults = await access({
const accessResults = await this.operations.collections.auth.access({
req,
config,
});
return res.status(httpStatus.OK)
@@ -13,6 +11,6 @@ const policiesHandler = (config) => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = policiesHandler;

View File

@@ -1,14 +1,11 @@
const httpStatus = require('http-status');
const { forgotPassword } = require('../operations');
const forgotPasswordHandler = (config, email) => async (req, res, next) => {
async function forgotPasswordHandler(req, res, next) {
try {
await forgotPassword({
await this.operations.collections.auth.forgotPassword({
req,
collection: req.collection,
config,
data: req.body,
email,
});
return res.status(httpStatus.OK)
@@ -18,6 +15,6 @@ const forgotPasswordHandler = (config, email) => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = forgotPasswordHandler;

View File

@@ -1,25 +0,0 @@
const login = require('./login');
const me = require('./me');
const refresh = require('./refresh');
const register = require('./register');
const init = require('./init');
const forgotPassword = require('./forgotPassword');
const resetPassword = require('./resetPassword');
const registerFirstUser = require('./registerFirstUser');
const update = require('./update');
const access = require('./access');
const logout = require('./logout');
module.exports = {
login,
logout,
me,
refresh,
init,
register,
forgotPassword,
registerFirstUser,
resetPassword,
update,
access,
};

View File

@@ -1,12 +1,10 @@
const { init } = require('../operations');
const initHandler = async (req, res, next) => {
async function initHandler(req, res, next) {
try {
const initialized = await init({ Model: req.collection.Model });
const initialized = await this.operations.collections.auth.init({ Model: req.collection.Model });
return res.status(200).json({ initialized });
} catch (error) {
return next(error);
}
};
}
module.exports = initHandler;

View File

@@ -1,13 +1,11 @@
const httpStatus = require('http-status');
const { login } = require('../operations');
const loginHandler = (config) => async (req, res, next) => {
async function loginHandler(req, res, next) {
try {
const token = await login({
const token = await this.operations.collections.auth.login({
req,
res,
collection: req.collection,
config,
data: req.body,
});
@@ -19,6 +17,6 @@ const loginHandler = (config) => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = loginHandler;

View File

@@ -1,9 +1,6 @@
const { logout } = require('../operations');
const logoutHandler = (config) => async (req, res, next) => {
async function logoutHandler(req, res, next) {
try {
const message = await logout({
config,
const message = await this.operations.collections.auth.logout({
collection: req.collection,
res,
req,
@@ -13,6 +10,6 @@ const logoutHandler = (config) => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = logoutHandler;

View File

@@ -1,12 +1,10 @@
const { me } = require('../operations');
const meHandler = config => async (req, res, next) => {
async function me(req, res, next) {
try {
const response = await me({ req, config });
const response = await this.operations.collections.auth.me({ req });
return res.status(200).json(response);
} catch (err) {
return next(err);
}
};
}
module.exports = meHandler;
module.exports = me;

View File

@@ -1,16 +1,14 @@
const { refresh } = require('../operations');
const getExtractJWT = require('../getExtractJWT');
const refreshHandler = config => async (req, res, next) => {
async function refreshHandler(req, res, next) {
try {
const extractJWT = getExtractJWT(config);
const extractJWT = getExtractJWT(this.config);
const token = extractJWT(req);
const result = await refresh({
const result = await this.operations.collections.auth.refresh({
req,
res,
collection: req.collection,
config,
token,
});
@@ -21,6 +19,6 @@ const refreshHandler = config => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = refreshHandler;

View File

@@ -1,11 +1,9 @@
const httpStatus = require('http-status');
const formatSuccessResponse = require('../../express/responses/formatSuccess');
const { register } = require('../operations');
const registerHandler = (config) => async (req, res, next) => {
async function register(req, res, next) {
try {
const user = await register({
config,
const user = await this.operations.collections.auth.register({
collection: req.collection,
req,
data: req.body,
@@ -18,6 +16,6 @@ const registerHandler = (config) => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = registerHandler;
module.exports = register;

View File

@@ -1,11 +1,8 @@
const { registerFirstUser } = require('../operations');
const registerFirstUserHandler = (config) => async (req, res, next) => {
async function registerFirstUser(req, res, next) {
try {
const firstUser = await registerFirstUser({
const firstUser = await this.operations.collections.auth.registerFirstUser({
req,
res,
config,
collection: req.collection,
data: req.body,
});
@@ -14,6 +11,6 @@ const registerFirstUserHandler = (config) => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = registerFirstUserHandler;
module.exports = registerFirstUser;

View File

@@ -1,12 +1,10 @@
const httpStatus = require('http-status');
const { resetPassword } = require('../operations');
const resetPasswordHandler = config => async (req, res, next) => {
async function resetPassword(req, res, next) {
try {
const token = await resetPassword({
const token = await this.operations.collections.auth.resetPassword({
req,
collection: req.collection,
config,
data: req.body,
});
@@ -18,6 +16,6 @@ const resetPasswordHandler = config => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = resetPasswordHandler;
module.exports = resetPassword;

View File

@@ -1,14 +1,12 @@
const httpStatus = require('http-status');
const formatSuccessResponse = require('../../express/responses/formatSuccess');
const { update } = require('../operations');
const updateHandler = (config) => async (req, res, next) => {
async function update(req, res, next) {
try {
const user = await update({
const user = await this.operations.collections.auth.update({
req,
data: req.body,
collection: req.collection,
config,
id: req.params.id,
});
@@ -19,6 +17,6 @@ const updateHandler = (config) => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = updateHandler;
module.exports = update;

View File

@@ -1,79 +0,0 @@
const express = require('express');
const bindCollectionMiddleware = require('../collections/bindCollection');
const {
init,
login,
logout,
refresh,
me,
register,
registerFirstUser,
forgotPassword,
resetPassword,
update,
} = require('./requestHandlers');
const {
find,
findByID,
deleteHandler,
} = require('../collections/requestHandlers');
const router = express.Router();
const authRoutes = (collection, config, sendEmail) => {
const { slug } = collection.config;
router.all('*',
bindCollectionMiddleware(collection));
router
.route(`/${slug}/init`)
.get(init);
router
.route(`/${slug}/login`)
.post(login(config));
router
.route(`/${slug}/logout`)
.get(logout(config));
router
.route(`/${slug}/refresh-token`)
.post(refresh(config));
router
.route(`/${slug}/me`)
.get(me(config));
router
.route(`/${slug}/first-register`)
.post(registerFirstUser(config));
router
.route(`/${slug}/forgot-password`)
.post(forgotPassword(config, sendEmail));
router
.route(`${slug}/reset-password`)
.post(resetPassword);
router
.route(`/${slug}/register`)
.post(register(config));
router
.route(`/${slug}`)
.get(find(config));
router.route(`/${slug}/:id`)
.get(findByID(config))
.put(update(config))
.delete(deleteHandler(config));
return router;
};
module.exports = authRoutes;

View File

@@ -1,7 +1,5 @@
/* eslint-disable no-param-reassign */
const { findByID } = require('../../operations');
const findByIDResolver = (config, collection) => async (_, args, context) => {
const findByIDResolver = (collection) => async (_, args, context) => {
if (args.locale) context.req.locale = args.locale;
if (args.fallbackLocale) context.req.fallbackLocale = args.fallbackLocale;

View File

@@ -1,4 +1,5 @@
const mongoose = require('mongoose');
const express = require('express');
const mongooseHidden = require('mongoose-hidden')({
hidden: {
salt: true, hash: true, _id: true, __v: true,
@@ -9,9 +10,8 @@ const passport = require('passport');
const passportLocalMongoose = require('passport-local-mongoose');
const LocalStrategy = require('passport-local').Strategy;
const apiKeyStrategy = require('../auth/strategies/apiKey');
const collectionRoutes = require('./routes');
const buildSchema = require('./buildSchema');
const authRoutes = require('../auth/routes');
const bindCollectionMiddleware = require('./bindCollection');
function registerCollections() {
this.config.collections = this.config.collections.map((collection) => {
@@ -30,6 +30,27 @@ function registerCollections() {
config: formattedCollection,
};
const router = express.Router();
const { slug } = collection;
router.all(`/${slug}*`, bindCollectionMiddleware(this.collections[formattedCollection.slug]));
const {
create,
find,
update,
findByID,
delete: deleteHandler,
} = this.requestHandlers.collections;
router.route(`/${slug}`)
.get(find)
.put(update);
router.route(`/${slug}/:id`)
.get(findByID)
.delete(deleteHandler);
if (collection.auth) {
const AuthCollection = this.collections[formattedCollection.slug];
passport.use(new LocalStrategy(AuthCollection.Model.authenticate()));
@@ -38,11 +59,65 @@ function registerCollections() {
passport.use(`${AuthCollection.config.slug}-api-key`, apiKeyStrategy(AuthCollection));
}
this.router.use(authRoutes(AuthCollection, this.config, this.sendEmail));
const {
init,
login,
logout,
refresh,
me,
register,
registerFirstUser,
forgotPassword,
resetPassword,
update: authUpdate,
} = this.requestHandlers.collections.auth;
router
.route(`/${slug}/init`)
.get(init);
router
.route(`/${slug}/login`)
.post(login);
router
.route(`/${slug}/logout`)
.get(logout);
router
.route(`/${slug}/refresh-token`)
.post(refresh);
router
.route(`/${slug}/me`)
.get(me);
router
.route(`/${slug}/first-register`)
.post(registerFirstUser);
router
.route(`/${slug}/forgot-password`)
.post(forgotPassword);
router
.route(`${slug}/reset-password`)
.post(resetPassword);
router
.route(`/${slug}/register`)
.post(register);
router.route(`/${slug}/:id`)
.put(authUpdate);
} else {
this.router.use(collectionRoutes(this.collections[formattedCollection.slug], this.config));
router.route(`/${slug}`)
.get(find)
.post(create);
}
this.router.use(router);
return formattedCollection;
});
}

View File

@@ -8,9 +8,9 @@ const getSafeFilename = require('../../uploads/getSafeFilename');
const getImageSize = require('../../uploads/getImageSize');
const imageMIMETypes = require('../../uploads/imageMIMETypes');
const performFieldOperations = require('../../fields/performFieldOperations');
async function create(args) {
const { performFieldOperations } = this;
const create = async (args) => {
const {
collection: {
Model,
@@ -21,7 +21,7 @@ const create = async (args) => {
locale,
fallbackLocale,
},
config,
depth,
} = args;
let { data } = args;
@@ -88,7 +88,7 @@ const create = async (args) => {
// 4. Execute field-level access, hooks, and validation
// /////////////////////////////////////
data = await performFieldOperations(config, collectionConfig, {
data = await performFieldOperations(collectionConfig, {
data,
hook: 'beforeCreate',
operationName: 'create',
@@ -114,11 +114,12 @@ const create = async (args) => {
// 6. Execute field-level hooks and access
// /////////////////////////////////////
result = await performFieldOperations(config, collectionConfig, {
result = await performFieldOperations(collectionConfig, {
data: result,
hook: 'afterRead',
operationName: 'read',
req,
depth,
});
// /////////////////////////////////////
@@ -139,6 +140,6 @@ const create = async (args) => {
// /////////////////////////////////////
return result;
};
}
module.exports = create;

View File

@@ -2,9 +2,7 @@ const fs = require('fs');
const { NotFound, Forbidden, ErrorDeletingFile } = require('../../errors');
const executeAccess = require('../../auth/executeAccess');
const performFieldOperations = require('../../fields/performFieldOperations');
const deleteQuery = async (args) => {
async function deleteQuery(args) {
const {
depth,
collection: {
@@ -17,7 +15,6 @@ const deleteQuery = async (args) => {
locale,
fallbackLocale,
},
config,
} = args;
// /////////////////////////////////////
@@ -97,7 +94,7 @@ const deleteQuery = async (args) => {
// 6. Execute field-level hooks and access
// /////////////////////////////////////
result = await performFieldOperations(config, collectionConfig, {
result = await this.performFieldOperations(collectionConfig, {
data: result,
hook: 'afterRead',
operationName: 'read',
@@ -120,6 +117,6 @@ const deleteQuery = async (args) => {
// /////////////////////////////////////
return result;
};
}
module.exports = deleteQuery;

View File

@@ -1,12 +1,10 @@
const executeAccess = require('../../auth/executeAccess');
const performFieldOperations = require('../../fields/performFieldOperations');
const find = async (args) => {
async function find(args) {
const {
where,
page,
limit,
config,
depth,
collection: {
Model,
@@ -90,13 +88,17 @@ const find = async (args) => {
const data = doc.toJSON({ virtuals: true });
return performFieldOperations(config, collectionConfig, {
return this.performFieldOperations(
collectionConfig,
{
depth,
data,
req,
hook: 'afterRead',
operationName: 'read',
});
},
find,
);
})),
};
@@ -128,6 +130,6 @@ const find = async (args) => {
// /////////////////////////////////////
return afterReadResult;
};
}
module.exports = find;

View File

@@ -1,10 +1,8 @@
const { Forbidden, NotFound } = require('../../errors');
const executeAccess = require('../../auth/executeAccess');
const performFieldOperations = require('../../fields/performFieldOperations');
const findByID = async (args) => {
async function findByID(args) {
const {
config,
depth,
collection: {
Model,
@@ -16,13 +14,15 @@ const findByID = async (args) => {
locale,
fallbackLocale,
},
disableErrors,
currentDepth,
} = args;
// /////////////////////////////////////
// 1. Retrieve and execute access
// /////////////////////////////////////
const accessResults = await executeAccess({ req }, collectionConfig.access.read);
const accessResults = await executeAccess({ req, disableErrors }, collectionConfig.access.read);
const hasWhereAccess = typeof accessResults === 'object';
const queryToBuild = {
@@ -55,8 +55,14 @@ const findByID = async (args) => {
let result = await Model.findOne(query, {});
if (!result && !hasWhereAccess) throw new NotFound();
if (!result && hasWhereAccess) throw new Forbidden();
if (!result) {
if (!disableErrors) {
if (!hasWhereAccess) throw new NotFound();
if (hasWhereAccess) throw new Forbidden();
}
return null;
}
if (locale && result.setLocale) {
result.setLocale(locale, fallbackLocale);
@@ -68,12 +74,13 @@ const findByID = async (args) => {
// 4. Execute field-level hooks and access
// /////////////////////////////////////
result = await performFieldOperations(config, collectionConfig, {
result = await this.performFieldOperations(collectionConfig, {
depth,
req,
data: result,
hook: 'afterRead',
operationName: 'read',
currentDepth,
});
@@ -96,6 +103,6 @@ const findByID = async (args) => {
// /////////////////////////////////////
return result;
};
}
module.exports = findByID;

View File

@@ -1,13 +0,0 @@
const findByID = require('./findByID');
const find = require('./find');
const create = require('./create');
const update = require('./update');
const deleteQuery = require('./delete');
module.exports = {
findByID,
find,
create,
update,
deleteQuery,
};

View File

@@ -2,16 +2,14 @@ const deepmerge = require('deepmerge');
const overwriteMerge = require('../../utilities/overwriteMerge');
const executeAccess = require('../../auth/executeAccess');
const { NotFound, Forbidden } = require('../../errors');
const performFieldOperations = require('../../fields/performFieldOperations');
const imageMIMETypes = require('../../uploads/imageMIMETypes');
const getImageSize = require('../../uploads/getImageSize');
const getSafeFilename = require('../../uploads/getSafeFilename');
const resizeAndSave = require('../../uploads/imageResizer');
const update = async (args) => {
async function update(args) {
const {
config,
depth,
collection: {
Model,
@@ -91,7 +89,7 @@ const update = async (args) => {
// 4. Execute field-level hooks, access, and validation
// /////////////////////////////////////
data = await performFieldOperations(config, collectionConfig, {
data = await this.performFieldOperations(collectionConfig, {
data,
req,
originalDoc,
@@ -154,7 +152,7 @@ const update = async (args) => {
// 7. Execute field-level hooks and access
// /////////////////////////////////////
doc = await performFieldOperations(config, collectionConfig, {
doc = await this.performFieldOperations(collectionConfig, {
data: doc,
hook: 'afterRead',
operationName: 'read',
@@ -180,6 +178,6 @@ const update = async (args) => {
// /////////////////////////////////////
return doc;
};
}
module.exports = update;

View File

@@ -1,13 +1,11 @@
const httpStatus = require('http-status');
const formatSuccessResponse = require('../../express/responses/formatSuccess');
const { create } = require('../operations');
const createHandler = (config) => async (req, res, next) => {
async function create(req, res, next) {
try {
const doc = await create({
const doc = await this.operations.collections.create({
req,
collection: req.collection,
config,
data: req.body,
depth: req.query.depth,
});
@@ -19,6 +17,6 @@ const createHandler = (config) => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = createHandler;
module.exports = create;

View File

@@ -1,13 +1,11 @@
const httpStatus = require('http-status');
const { NotFound } = require('../../errors');
const { deleteQuery } = require('../operations');
const deleteHandler = (config) => async (req, res, next) => {
async function deleteHandler(req, res, next) {
try {
const doc = await deleteQuery({
const doc = await this.operations.collections.deleteQuery({
req,
collection: req.collection,
config,
id: req.params.id,
depth: req.query.depth,
});
@@ -20,6 +18,6 @@ const deleteHandler = (config) => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = deleteHandler;

View File

@@ -1,12 +1,10 @@
const httpStatus = require('http-status');
const { find } = require('../operations');
const findHandler = (config) => async (req, res, next) => {
async function find(req, res, next) {
try {
const options = {
req,
collection: req.collection,
config,
where: req.query.where,
page: req.query.page,
limit: req.query.limit,
@@ -14,12 +12,12 @@ const findHandler = (config) => async (req, res, next) => {
depth: req.query.depth,
};
const result = await find(options);
const result = await this.operations.collections.find(options);
return res.status(httpStatus.OK).json(result);
} catch (error) {
return next(error);
}
};
}
module.exports = findHandler;
module.exports = find;

View File

@@ -1,20 +1,17 @@
const { findByID } = require('../operations');
const findByIDHandler = (config) => async (req, res, next) => {
async function findByID(req, res, next) {
const options = {
req,
collection: req.collection,
config,
id: req.params.id,
depth: req.query.depth,
};
try {
const doc = await findByID(options);
const doc = await this.operations.collections.findByID(options);
return res.json(doc);
} catch (error) {
return next(error);
}
};
}
module.exports = findByIDHandler;
module.exports = findByID;

View File

@@ -1,13 +0,0 @@
const create = require('./create');
const deleteHandler = require('./delete');
const findByID = require('./findByID');
const find = require('./find');
const update = require('./update');
module.exports = {
create,
deleteHandler,
findByID,
find,
update,
};

View File

@@ -1,13 +1,11 @@
const httpStatus = require('http-status');
const formatSuccessResponse = require('../../express/responses/formatSuccess');
const { update } = require('../operations');
const updateHandler = (config) => async (req, res, next) => {
async function update(req, res, next) {
try {
const doc = await update({
const doc = await this.operations.collections.update({
req,
collection: req.collection,
config,
id: req.params.id,
data: req.body,
depth: req.query.depth,
@@ -20,6 +18,6 @@ const updateHandler = (config) => async (req, res, next) => {
} catch (error) {
return next(error);
}
};
}
module.exports = updateHandler;
module.exports = update;

View File

@@ -1,27 +0,0 @@
const express = require('express');
const requestHandlers = require('./requestHandlers');
const bindCollectionMiddleware = require('./bindCollection');
const {
find, create, findByID, deleteHandler, update,
} = requestHandlers;
const router = express.Router();
const registerRoutes = (collection, config) => {
router.all(`/${collection.config.slug}*`, bindCollectionMiddleware(collection));
router.route(`/${collection.config.slug}`)
.get(find(config))
.post(create(config));
router.route(`/${collection.config.slug}/:id`)
.get(findByID(config))
.put(update(config))
.delete(deleteHandler(config));
return router;
};
module.exports = registerRoutes;

View File

@@ -1,6 +1,7 @@
const { ValidationError } = require('../errors');
const executeAccess = require('../auth/executeAccess');
const performFieldOperations = async (config, entityConfig, operation) => {
async function performFieldOperations(entityConfig, operation) {
const {
data: fullData,
originalDoc: fullOriginalDoc,
@@ -9,12 +10,54 @@ const performFieldOperations = async (config, entityConfig, operation) => {
req,
} = operation;
const recursivePerformFieldOperations = performFieldOperations.bind(this);
const depth = (operation.depth || operation.depth === 0) ? parseInt(operation.depth, 10) : this.config.defaultDepth;
const currentDepth = operation.currentDepth || 0;
const populateRelationship = async (dataReference, data, field, i) => {
let dataToUpdate = dataReference;
const relation = Array.isArray(field.relationTo) ? data.relationTo : field.relationTo;
const relatedCollection = this.collections[relation];
const accessResult = await executeAccess({ req, disableErrors: true }, relatedCollection.config.access.read);
let populatedRelationship = null;
if (accessResult && (depth && currentDepth <= depth)) {
populatedRelationship = await this.operations.collections.findByID({
req,
collection: relatedCollection,
id: Array.isArray(field.relationTo) ? data.value : data,
currentDepth: currentDepth + 1,
disableErrors: true,
});
}
// If access control fails, update value to null
// If populatedRelationship comes back, update value
if (!accessResult || populatedRelationship) {
if (typeof i === 'number') {
if (Array.isArray(field.relationTo)) {
dataToUpdate[field.name][i].value = populatedRelationship;
} else {
dataToUpdate[field.name][i] = populatedRelationship;
}
} else if (Array.isArray(field.relationTo)) {
dataToUpdate.value = populatedRelationship;
} else {
dataToUpdate = populatedRelationship;
}
}
};
// Maintain a top-level list of promises
// so that all async field access / validations / hooks
// can run in parallel
const validationPromises = [];
const accessPromises = [];
const relationshipAccessPromises = [];
const relationshipPopulationPromises = [];
const hookPromises = [];
const errors = [];
@@ -36,13 +79,25 @@ const performFieldOperations = async (config, entityConfig, operation) => {
}
};
const createRelationshipAccessPromise = async (data, field, access) => {
const createRelationshipPopulationPromise = async (data, field) => {
const resultingData = data;
const result = await access({ req });
if (field.hasMany && Array.isArray(data[field.name])) {
const rowPromises = [];
if (result === false) {
delete resultingData[field.name];
data[field.name].forEach((relatedDoc, i) => {
const rowPromise = async () => {
if (relatedDoc) {
await populateRelationship(resultingData, relatedDoc, field, i);
}
};
rowPromises.push(rowPromise());
});
await Promise.all(rowPromises);
} else if (data[field.name]) {
await populateRelationship(resultingData, data[field.name], field);
}
};
@@ -59,30 +114,13 @@ const performFieldOperations = async (config, entityConfig, operation) => {
}
}
if (field.type === 'relationship' && operationName === 'read') {
const relatedCollections = Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo];
relatedCollections.forEach((slug) => {
const collection = config.collections.find((coll) => coll.slug === slug);
if (collection && collection.access && collection.access.read) {
relationshipAccessPromises.push(createRelationshipAccessPromise(data, field, collection.access.read));
}
});
if ((field.type === 'relationship' || field.type === 'upload') && hook === 'afterRead') {
relationshipPopulationPromises.push(createRelationshipPopulationPromise(data, field));
}
};
const createHookPromise = async (data, field) => {
const resultingData = data;
const findRelatedCollection = (relation) => config.collections.find((collection) => collection.slug === relation);
// Todo:
// Check for afterRead operation and if found,
// Run relationship and upload-based hooks here
// Handle following scenarios:
//
// hasMany
// relationTo hasMany
// single
if (hook === 'afterRead') {
if ((field.type === 'relationship' || field.type === 'upload')) {
@@ -90,8 +128,10 @@ const performFieldOperations = async (config, entityConfig, operation) => {
// If there are many related documents
if (field.hasMany && Array.isArray(data[field.name])) {
const relationshipDocPromises = [];
// Loop through relations
data[field.name].forEach(async (value, i) => {
data[field.name].forEach((value, i) => {
const generateRelationshipDocPromise = async () => {
let relation = field.relationTo;
// If this field can be related to many collections,
@@ -101,7 +141,7 @@ const performFieldOperations = async (config, entityConfig, operation) => {
}
if (relation) {
const relatedCollection = findRelatedCollection(relation);
const relatedCollection = this.collections[relation].config;
if (relatedCollection) {
let relatedDocumentData = data[field.name][i];
@@ -115,7 +155,7 @@ const performFieldOperations = async (config, entityConfig, operation) => {
// Only run hooks for populated sub documents - NOT IDs
if (relatedDocumentData && typeof relatedDocumentData !== 'string') {
// Perform field hooks on related collection
dataToHook = await performFieldOperations(config, relatedCollection, {
dataToHook = await recursivePerformFieldOperations(relatedCollection, {
req,
data: relatedDocumentData,
hook: 'afterRead',
@@ -133,8 +173,13 @@ const performFieldOperations = async (config, entityConfig, operation) => {
}
}
}
};
relationshipDocPromises.push(generateRelationshipDocPromise());
});
await Promise.all(relationshipDocPromises);
// Otherwise, there is only one related document
} else {
let relation = field.relationTo;
@@ -143,7 +188,7 @@ const performFieldOperations = async (config, entityConfig, operation) => {
relation = data[field.name].relationTo;
}
const relatedCollection = findRelatedCollection(relation);
const relatedCollection = this.collections[relation].config;
if (relatedCollection) {
let relatedDocumentData = data[field.name];
@@ -157,7 +202,7 @@ const performFieldOperations = async (config, entityConfig, operation) => {
// Only run hooks for populated sub documents - NOT IDs
if (relatedDocumentData && typeof relatedDocumentData !== 'string') {
// Perform field hooks on related collection
dataToHook = await performFieldOperations(config, relatedCollection, {
dataToHook = await recursivePerformFieldOperations(relatedCollection, {
req,
data: relatedDocumentData,
hook: 'afterRead',
@@ -255,10 +300,11 @@ const performFieldOperations = async (config, entityConfig, operation) => {
}
await Promise.all(accessPromises);
await Promise.all(relationshipPopulationPromises);
await Promise.all(hookPromises);
return fullData;
};
}
module.exports = performFieldOperations;

View File

@@ -1,5 +1,5 @@
const express = require('express');
const buildModel = require('./buildModel');
const routes = require('./routes');
function initGlobals() {
if (this.config.globals) {
@@ -8,7 +8,16 @@ function initGlobals() {
config: this.config.globals,
};
this.router.use(routes(this.config, this.globals.Model));
const router = express.Router();
this.config.globals.forEach((global) => {
router
.route(`/globals/${global.slug}`)
.get(this.requestHandlers.globals.findOne(global))
.post(this.requestHandlers.globals.update(global));
});
this.router.use(router);
}
}

View File

@@ -1,11 +1,10 @@
const executeAccess = require('../../auth/executeAccess');
const performFieldOperations = require('../../fields/performFieldOperations');
const findOne = async (args) => {
async function findOne(args) {
const { globals: { Model } } = this;
const {
config,
globalConfig,
Model,
req,
req: {
locale,
@@ -48,7 +47,7 @@ const findOne = async (args) => {
// 4. Execute field-level hooks and access
// /////////////////////////////////////
doc = performFieldOperations(config, globalConfig, {
doc = this.performFieldOperations(globalConfig, {
data: doc,
hook: 'afterRead',
operationName: 'read',
@@ -74,6 +73,6 @@ const findOne = async (args) => {
// /////////////////////////////////////
return doc;
};
}
module.exports = findOne;

View File

@@ -1,7 +0,0 @@
const update = require('./update');
const findOne = require('./findOne');
module.exports = {
findOne,
update,
};

View File

@@ -1,13 +1,12 @@
const deepmerge = require('deepmerge');
const overwriteMerge = require('../../utilities/overwriteMerge');
const executeAccess = require('../../auth/executeAccess');
const performFieldOperations = require('../../fields/performFieldOperations');
const update = async (args) => {
async function update(args) {
const { config, globals: { Model } } = this;
const {
config,
globalConfig,
Model,
slug,
req,
req: {
@@ -65,7 +64,7 @@ const update = async (args) => {
// 5. Execute field-level hooks, access, and validation
// /////////////////////////////////////
data = await performFieldOperations(config, globalConfig, {
data = await this.performFieldOperations(globalConfig, {
data,
req,
hook: 'beforeUpdate',
@@ -87,7 +86,7 @@ const update = async (args) => {
// 7. Execute field-level hooks and access
// /////////////////////////////////////
global = await performFieldOperations(config, globalConfig, {
global = await this.performFieldOperations(globalConfig, {
data: global,
hook: 'afterRead',
operationName: 'read',
@@ -113,6 +112,6 @@ const update = async (args) => {
// /////////////////////////////////////
return global;
};
}
module.exports = update;

View File

@@ -1,14 +1,12 @@
const httpStatus = require('http-status');
const { findOne } = require('../operations');
const findOneHandler = (config, Model, globalConfig) => async (req, res, next) => {
function findOne(globalConfig) {
async function handler(req, res, next) {
try {
const { slug } = globalConfig;
const result = await findOne({
const result = await this.operations.globals.findOne({
req,
Model,
config,
globalConfig,
slug,
depth: req.query.depth,
@@ -18,6 +16,11 @@ const findOneHandler = (config, Model, globalConfig) => async (req, res, next) =
} catch (error) {
return next(error);
}
};
}
module.exports = findOneHandler;
const findOneHandler = handler.bind(this);
return findOneHandler;
}
module.exports = findOne;

View File

@@ -1,7 +0,0 @@
const findOne = require('./findOne');
const update = require('./update');
module.exports = {
findOne,
update,
};

View File

@@ -1,14 +1,12 @@
const httpStatus = require('http-status');
const { update } = require('../operations');
const updateHandler = (config, Model, globalConfig) => async (req, res, next) => {
function update(globalConfig) {
async function handler(req, res, next) {
try {
const { slug } = globalConfig;
const result = await update({
const result = await this.operations.globals.update({
req,
Model,
config,
globalConfig,
slug,
depth: req.query.depth,
@@ -19,6 +17,10 @@ const updateHandler = (config, Model, globalConfig) => async (req, res, next) =>
} catch (error) {
return next(error);
}
};
}
module.exports = updateHandler;
const updateHandler = handler.bind(this);
return updateHandler;
}
module.exports = update;

View File

@@ -1,19 +0,0 @@
const express = require('express');
const requestHandlers = require('./requestHandlers');
const { update, findOne } = requestHandlers;
const router = express.Router();
const registerGlobals = (config, Globals) => {
config.globals.forEach((global) => {
router
.route(`/globals/${global.slug}`)
.get(findOne(config, Globals, global))
.post(update(config, Globals, global));
});
return router;
};
module.exports = registerGlobals;

View File

@@ -16,7 +16,6 @@ const { GraphQLJSON } = require('graphql-type-json');
const formatName = require('../utilities/formatName');
const combineParentName = require('../utilities/combineParentName');
const withNullableType = require('./withNullableType');
const { find } = require('../../collections/operations');
function buildObjectType(name, fields, parentName, baseFields = {}) {
const recursiveBuildObjectType = buildObjectType.bind(this);
@@ -122,7 +121,7 @@ function buildObjectType(name, fields, parentName, baseFields = {}) {
id = relatedDoc.value;
}
const result = await find({
const result = await this.operations.collections.find({
Model: this.collections[relatedCollectionSlug].Model,
query: {
where: {
@@ -169,7 +168,7 @@ function buildObjectType(name, fields, parentName, baseFields = {}) {
if (args.page) relatedDocumentQuery.paginate.page = args.page;
if (args.limit) relatedDocumentQuery.paginate.limit = args.limit;
const relatedDocument = await find();
const relatedDocument = await this.operations.collections.find();
if (relatedDocument.docs[0]) return relatedDocument.docs[0];

View File

@@ -2,7 +2,9 @@ require('es6-promise').polyfill();
require('isomorphic-fetch');
const express = require('express');
const graphQLPlayground = require('graphql-playground-middleware-express').default;
// const graphQLPlayground = require('graphql-playground-middleware-express').default;
const bindOperations = require('./init/bindOperations');
const bindRequestHandlers = require('./init/bindRequestHandlers');
const getConfig = require('./utilities/getConfig');
const authenticate = require('./express/middleware/authenticate');
const connectMongoose = require('./mongoose/connect');
@@ -12,12 +14,12 @@ const initAuth = require('./auth/init');
const initCollections = require('./collections/init');
const initGlobals = require('./globals/init');
const initStatic = require('./express/static');
const GraphQL = require('./graphql');
// const GraphQL = require('./graphql');
const sanitizeConfig = require('./utilities/sanitizeConfig');
const buildEmail = require('./email/build');
const identifyAPI = require('./express/middleware/identifyAPI');
// const identifyAPI = require('./express/middleware/identifyAPI');
const errorHandler = require('./express/middleware/errorHandler');
const { access } = require('./auth/requestHandlers');
const performFieldOperations = require('./fields/performFieldOperations');
class Payload {
constructor(options) {
@@ -31,6 +33,9 @@ class Payload {
this.router = express.Router();
this.collections = {};
bindOperations(this);
bindRequestHandlers(this);
this.initAuth = initAuth.bind(this);
this.initCollections = initCollections.bind(this);
this.initGlobals = initGlobals.bind(this);
@@ -39,6 +44,7 @@ class Payload {
this.getMockEmailCredentials = this.getMockEmailCredentials.bind(this);
this.initStatic = initStatic.bind(this);
this.initAdmin = initAdmin.bind(this);
this.performFieldOperations = performFieldOperations.bind(this);
// Configure email service
this.email = this.buildEmail();
@@ -53,22 +59,22 @@ class Payload {
this.initGlobals();
this.initAdmin();
this.router.get('/access', access(this.config));
this.router.get('/access', this.requestHandlers.collections.auth.access);
const graphQLHandler = new GraphQL(this);
// const graphQLHandler = new GraphQL(this);
this.router.use(
this.config.routes.graphQL,
identifyAPI('GraphQL'),
(req, res) => graphQLHandler.init(req, res)(req, res),
);
// this.router.use(
// this.config.routes.graphQL,
// identifyAPI('GraphQL'),
// (req, res) => graphQLHandler.init(req, res)(req, res),
// );
this.router.get(this.config.routes.graphQLPlayground, graphQLPlayground({
endpoint: `${this.config.routes.api}${this.config.routes.graphQL}`,
settings: {
'request.credentials': 'include',
},
}));
// this.router.get(this.config.routes.graphQLPlayground, graphQLPlayground({
// endpoint: `${this.config.routes.api}${this.config.routes.graphQL}`,
// settings: {
// 'request.credentials': 'include',
// },
// }));
// Bind router to API
this.express.use(this.config.routes.api, this.router);

View File

@@ -0,0 +1,53 @@
const access = require('../auth/operations/access');
const forgotPassword = require('../auth/operations/forgotPassword');
const init = require('../auth/operations/init');
const login = require('../auth/operations/login');
const logout = require('../auth/operations/logout');
const me = require('../auth/operations/me');
const refresh = require('../auth/operations/refresh');
const register = require('../auth/operations/register');
const registerFirstUser = require('../auth/operations/registerFirstUser');
const resetPassword = require('../auth/operations/resetPassword');
const authUpdate = require('../auth/operations/update');
const create = require('../collections/operations/create');
const find = require('../collections/operations/find');
const findByID = require('../collections/operations/findByID');
const update = require('../collections/operations/update');
const deleteHandler = require('../collections/operations/delete');
const findOne = require('../globals/operations/findOne');
const globalUpdate = require('../globals/operations/update');
function bindOperations(ctx) {
const payload = ctx;
payload.operations = {
collections: {
create: create.bind(ctx),
find: find.bind(ctx),
findByID: findByID.bind(ctx),
update: update.bind(ctx),
delete: deleteHandler.bind(ctx),
auth: {
access: access.bind(ctx),
forgotPassword: forgotPassword.bind(ctx),
init: init.bind(ctx),
login: login.bind(ctx),
logout: logout.bind(ctx),
me: me.bind(ctx),
refresh: refresh.bind(ctx),
register: register.bind(ctx),
registerFirstUser: registerFirstUser.bind(ctx),
resetPassword: resetPassword.bind(ctx),
update: authUpdate.bind(ctx),
},
},
globals: {
findOne: findOne.bind(ctx),
update: globalUpdate.bind(ctx),
},
};
}
module.exports = bindOperations;

View File

@@ -0,0 +1,53 @@
const access = require('../auth/requestHandlers/access');
const forgotPassword = require('../auth/requestHandlers/forgotPassword');
const init = require('../auth/requestHandlers/init');
const login = require('../auth/requestHandlers/login');
const logout = require('../auth/requestHandlers/logout');
const me = require('../auth/requestHandlers/me');
const refresh = require('../auth/requestHandlers/refresh');
const register = require('../auth/requestHandlers/register');
const registerFirstUser = require('../auth/requestHandlers/registerFirstUser');
const resetPassword = require('../auth/requestHandlers/resetPassword');
const authUpdate = require('../auth/requestHandlers/update');
const create = require('../collections/requestHandlers/create');
const find = require('../collections/requestHandlers/find');
const findByID = require('../collections/requestHandlers/findByID');
const update = require('../collections/requestHandlers/update');
const deleteHandler = require('../collections/requestHandlers/delete');
const findOne = require('../globals/requestHandlers/findOne');
const globalUpdate = require('../globals/requestHandlers/update');
function bindRequestHandlers(ctx) {
const payload = ctx;
payload.requestHandlers = {
collections: {
create: create.bind(ctx),
find: find.bind(ctx),
findByID: findByID.bind(ctx),
update: update.bind(ctx),
delete: deleteHandler.bind(ctx),
auth: {
access: access.bind(ctx),
forgotPassword: forgotPassword.bind(ctx),
init: init.bind(ctx),
login: login.bind(ctx),
logout: logout.bind(ctx),
me: me.bind(ctx),
refresh: refresh.bind(ctx),
register: register.bind(ctx),
registerFirstUser: registerFirstUser.bind(ctx),
resetPassword: resetPassword.bind(ctx),
update: authUpdate.bind(ctx),
},
},
globals: {
findOne: findOne.bind(ctx),
update: globalUpdate.bind(ctx),
},
};
}
module.exports = bindRequestHandlers;