From 85bcbfd918f1fec52384a133a7caa657bf3f39b4 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 20 Jul 2020 16:11:22 -0400 Subject: [PATCH] removes autopopulate, adds manual population --- demo/globals/GlobalWithStrictAccess.js | 3 +- package.json | 1 - src/auth/executeAccess.js | 5 +- src/auth/operations/access.js | 7 +- src/auth/operations/forgotPassword.js | 8 +- src/auth/operations/index.js | 25 --- src/auth/operations/init.js | 4 +- src/auth/operations/login.js | 7 +- src/auth/operations/logout.js | 7 +- src/auth/operations/me.js | 6 +- src/auth/operations/refresh.js | 6 +- src/auth/operations/register.js | 12 +- src/auth/operations/registerFirstUser.js | 4 +- src/auth/operations/resetPassword.js | 7 +- src/auth/operations/update.js | 12 +- src/auth/requestHandlers/access.js | 8 +- src/auth/requestHandlers/forgotPassword.js | 9 +- src/auth/requestHandlers/index.js | 25 --- src/auth/requestHandlers/init.js | 8 +- src/auth/requestHandlers/login.js | 8 +- src/auth/requestHandlers/logout.js | 9 +- src/auth/requestHandlers/me.js | 10 +- src/auth/requestHandlers/refresh.js | 10 +- src/auth/requestHandlers/register.js | 10 +- src/auth/requestHandlers/registerFirstUser.js | 11 +- src/auth/requestHandlers/resetPassword.js | 10 +- src/auth/requestHandlers/update.js | 10 +- src/auth/routes.js | 79 -------- src/collections/graphql/resolvers/findByID.js | 4 +- src/collections/init.js | 83 ++++++++- src/collections/operations/create.js | 13 +- src/collections/operations/delete.js | 9 +- src/collections/operations/find.js | 24 +-- src/collections/operations/findByID.js | 23 ++- src/collections/operations/index.js | 13 -- src/collections/operations/update.js | 10 +- src/collections/requestHandlers/create.js | 10 +- src/collections/requestHandlers/delete.js | 8 +- src/collections/requestHandlers/find.js | 10 +- src/collections/requestHandlers/findByID.js | 11 +- src/collections/requestHandlers/index.js | 13 -- src/collections/requestHandlers/update.js | 10 +- src/collections/routes.js | 27 --- src/fields/performFieldOperations.js | 170 +++++++++++------- src/globals/init.js | 13 +- src/globals/operations/findOne.js | 11 +- src/globals/operations/index.js | 7 - src/globals/operations/update.js | 13 +- src/globals/requestHandlers/findOne.js | 37 ++-- src/globals/requestHandlers/index.js | 7 - src/globals/requestHandlers/update.js | 38 ++-- src/globals/routes.js | 19 -- src/graphql/schema/buildObjectType.js | 5 +- src/index.js | 40 +++-- src/init/bindOperations.js | 53 ++++++ src/init/bindRequestHandlers.js | 53 ++++++ 56 files changed, 527 insertions(+), 528 deletions(-) delete mode 100644 src/auth/operations/index.js delete mode 100644 src/auth/requestHandlers/index.js delete mode 100644 src/auth/routes.js delete mode 100644 src/collections/operations/index.js delete mode 100644 src/collections/requestHandlers/index.js delete mode 100644 src/collections/routes.js delete mode 100644 src/globals/operations/index.js delete mode 100644 src/globals/requestHandlers/index.js delete mode 100644 src/globals/routes.js create mode 100644 src/init/bindOperations.js create mode 100644 src/init/bindRequestHandlers.js diff --git a/demo/globals/GlobalWithStrictAccess.js b/demo/globals/GlobalWithStrictAccess.js index ab15f5aa7b..1361bfaaec 100644 --- a/demo/globals/GlobalWithStrictAccess.js +++ b/demo/globals/GlobalWithStrictAccess.js @@ -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: [ { diff --git a/package.json b/package.json index 72fb2268ff..0cb3ad825e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/auth/executeAccess.js b/src/auth/executeAccess.js index 322d8bb2e1..2a161d5801 100644 --- a/src/auth/executeAccess.js +++ b/src/auth/executeAccess.js @@ -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; diff --git a/src/auth/operations/access.js b/src/auth/operations/access.js index 3e460d744c..6e3ab6a955 100644 --- a/src/auth/operations/access.js +++ b/src/auth/operations/access.js @@ -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; diff --git a/src/auth/operations/forgotPassword.js b/src/auth/operations/forgotPassword.js index d9dc731902..3c287ec65b 100644 --- a/src/auth/operations/forgotPassword.js +++ b/src/auth/operations/forgotPassword.js @@ -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; diff --git a/src/auth/operations/index.js b/src/auth/operations/index.js deleted file mode 100644 index f9da1162a5..0000000000 --- a/src/auth/operations/index.js +++ /dev/null @@ -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, -}; diff --git a/src/auth/operations/init.js b/src/auth/operations/init.js index ecb46d3b79..75b05143fa 100644 --- a/src/auth/operations/init.js +++ b/src/auth/operations/init.js @@ -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; diff --git a/src/auth/operations/login.js b/src/auth/operations/login.js index 83de911111..8e6ac89a40 100644 --- a/src/auth/operations/login.js +++ b/src/auth/operations/login.js @@ -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; diff --git a/src/auth/operations/logout.js b/src/auth/operations/logout.js index 44963edcb7..5cf893c7c2 100644 --- a/src/auth/operations/logout.js +++ b/src/auth/operations/logout.js @@ -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; diff --git a/src/auth/operations/me.js b/src/auth/operations/me.js index 40438a22f8..876192e798 100644 --- a/src/auth/operations/me.js +++ b/src/auth/operations/me.js @@ -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; diff --git a/src/auth/operations/refresh.js b/src/auth/operations/refresh.js index b84fb16fe5..e1761f34ef 100644 --- a/src/auth/operations/refresh.js +++ b/src/auth/operations/refresh.js @@ -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; diff --git a/src/auth/operations/register.js b/src/auth/operations/register.js index 10efd5f61e..4bbc3054ad 100644 --- a/src/auth/operations/register.js +++ b/src/auth/operations/register.js @@ -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; diff --git a/src/auth/operations/registerFirstUser.js b/src/auth/operations/registerFirstUser.js index 88c181ba09..dfa163a0da 100644 --- a/src/auth/operations/registerFirstUser.js +++ b/src/auth/operations/registerFirstUser.js @@ -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; diff --git a/src/auth/operations/resetPassword.js b/src/auth/operations/resetPassword.js index 7994b92563..1914c071ef 100644 --- a/src/auth/operations/resetPassword.js +++ b/src/auth/operations/resetPassword.js @@ -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; diff --git a/src/auth/operations/update.js b/src/auth/operations/update.js index 8ea33fe02c..5ec4088040 100644 --- a/src/auth/operations/update.js +++ b/src/auth/operations/update.js @@ -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; diff --git a/src/auth/requestHandlers/access.js b/src/auth/requestHandlers/access.js index e94fe8e702..d4883ae42e 100644 --- a/src/auth/requestHandlers/access.js +++ b/src/auth/requestHandlers/access.js @@ -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; diff --git a/src/auth/requestHandlers/forgotPassword.js b/src/auth/requestHandlers/forgotPassword.js index 742c7f6140..05fa8daa55 100644 --- a/src/auth/requestHandlers/forgotPassword.js +++ b/src/auth/requestHandlers/forgotPassword.js @@ -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; diff --git a/src/auth/requestHandlers/index.js b/src/auth/requestHandlers/index.js deleted file mode 100644 index 96ce73a390..0000000000 --- a/src/auth/requestHandlers/index.js +++ /dev/null @@ -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, -}; diff --git a/src/auth/requestHandlers/init.js b/src/auth/requestHandlers/init.js index a7891ffb0e..272d34d826 100644 --- a/src/auth/requestHandlers/init.js +++ b/src/auth/requestHandlers/init.js @@ -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; diff --git a/src/auth/requestHandlers/login.js b/src/auth/requestHandlers/login.js index 0bf3152743..7cff1d5e5b 100644 --- a/src/auth/requestHandlers/login.js +++ b/src/auth/requestHandlers/login.js @@ -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; diff --git a/src/auth/requestHandlers/logout.js b/src/auth/requestHandlers/logout.js index 857be47577..e181764c23 100644 --- a/src/auth/requestHandlers/logout.js +++ b/src/auth/requestHandlers/logout.js @@ -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; diff --git a/src/auth/requestHandlers/me.js b/src/auth/requestHandlers/me.js index b3847a31d0..c26c159d92 100644 --- a/src/auth/requestHandlers/me.js +++ b/src/auth/requestHandlers/me.js @@ -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; diff --git a/src/auth/requestHandlers/refresh.js b/src/auth/requestHandlers/refresh.js index ec3558b3d0..8e2f2ebc26 100644 --- a/src/auth/requestHandlers/refresh.js +++ b/src/auth/requestHandlers/refresh.js @@ -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; diff --git a/src/auth/requestHandlers/register.js b/src/auth/requestHandlers/register.js index 9757114ae6..2621cbefbe 100644 --- a/src/auth/requestHandlers/register.js +++ b/src/auth/requestHandlers/register.js @@ -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; diff --git a/src/auth/requestHandlers/registerFirstUser.js b/src/auth/requestHandlers/registerFirstUser.js index 23b2cba917..1ca01af6f0 100644 --- a/src/auth/requestHandlers/registerFirstUser.js +++ b/src/auth/requestHandlers/registerFirstUser.js @@ -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; diff --git a/src/auth/requestHandlers/resetPassword.js b/src/auth/requestHandlers/resetPassword.js index 73665abca7..a35f24df3c 100644 --- a/src/auth/requestHandlers/resetPassword.js +++ b/src/auth/requestHandlers/resetPassword.js @@ -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; diff --git a/src/auth/requestHandlers/update.js b/src/auth/requestHandlers/update.js index a4927f355a..778400a0f1 100644 --- a/src/auth/requestHandlers/update.js +++ b/src/auth/requestHandlers/update.js @@ -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; diff --git a/src/auth/routes.js b/src/auth/routes.js deleted file mode 100644 index f0a2088b3e..0000000000 --- a/src/auth/routes.js +++ /dev/null @@ -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; diff --git a/src/collections/graphql/resolvers/findByID.js b/src/collections/graphql/resolvers/findByID.js index 880a432f1f..4bae467c09 100644 --- a/src/collections/graphql/resolvers/findByID.js +++ b/src/collections/graphql/resolvers/findByID.js @@ -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; diff --git a/src/collections/init.js b/src/collections/init.js index eb29a8b3b8..8d3b92f0b6 100644 --- a/src/collections/init.js +++ b/src/collections/init.js @@ -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; }); } diff --git a/src/collections/operations/create.js b/src/collections/operations/create.js index 71c68431da..9d2d71f167 100644 --- a/src/collections/operations/create.js +++ b/src/collections/operations/create.js @@ -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; diff --git a/src/collections/operations/delete.js b/src/collections/operations/delete.js index 83ca1db052..784e6964ee 100644 --- a/src/collections/operations/delete.js +++ b/src/collections/operations/delete.js @@ -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; diff --git a/src/collections/operations/find.js b/src/collections/operations/find.js index 519fcfcc0d..236847ec14 100644 --- a/src/collections/operations/find.js +++ b/src/collections/operations/find.js @@ -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, { - depth, - data, - req, - hook: 'afterRead', - operationName: 'read', - }); + 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; diff --git a/src/collections/operations/findByID.js b/src/collections/operations/findByID.js index d8e5aa70e0..c7c1064319 100644 --- a/src/collections/operations/findByID.js +++ b/src/collections/operations/findByID.js @@ -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; diff --git a/src/collections/operations/index.js b/src/collections/operations/index.js deleted file mode 100644 index 6ec58ad38e..0000000000 --- a/src/collections/operations/index.js +++ /dev/null @@ -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, -}; diff --git a/src/collections/operations/update.js b/src/collections/operations/update.js index aeab18284f..15fbfc0fdb 100644 --- a/src/collections/operations/update.js +++ b/src/collections/operations/update.js @@ -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; diff --git a/src/collections/requestHandlers/create.js b/src/collections/requestHandlers/create.js index 06b2ce4493..f9f41341df 100644 --- a/src/collections/requestHandlers/create.js +++ b/src/collections/requestHandlers/create.js @@ -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; diff --git a/src/collections/requestHandlers/delete.js b/src/collections/requestHandlers/delete.js index 30a7230ded..40e83d34a6 100644 --- a/src/collections/requestHandlers/delete.js +++ b/src/collections/requestHandlers/delete.js @@ -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; diff --git a/src/collections/requestHandlers/find.js b/src/collections/requestHandlers/find.js index 904a44c905..322cdfd688 100644 --- a/src/collections/requestHandlers/find.js +++ b/src/collections/requestHandlers/find.js @@ -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; diff --git a/src/collections/requestHandlers/findByID.js b/src/collections/requestHandlers/findByID.js index 35b9eaebaa..b7004c137b 100644 --- a/src/collections/requestHandlers/findByID.js +++ b/src/collections/requestHandlers/findByID.js @@ -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; diff --git a/src/collections/requestHandlers/index.js b/src/collections/requestHandlers/index.js deleted file mode 100644 index 8422458ddf..0000000000 --- a/src/collections/requestHandlers/index.js +++ /dev/null @@ -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, -}; diff --git a/src/collections/requestHandlers/update.js b/src/collections/requestHandlers/update.js index bf81a199ea..59c8a36928 100644 --- a/src/collections/requestHandlers/update.js +++ b/src/collections/requestHandlers/update.js @@ -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; diff --git a/src/collections/routes.js b/src/collections/routes.js deleted file mode 100644 index 7a721410ac..0000000000 --- a/src/collections/routes.js +++ /dev/null @@ -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; diff --git a/src/fields/performFieldOperations.js b/src/fields/performFieldOperations.js index 6fec7a0e05..a076e8995b 100644 --- a/src/fields/performFieldOperations.js +++ b/src/fields/performFieldOperations.js @@ -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,51 +128,58 @@ 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) => { - let relation = field.relationTo; + data[field.name].forEach((value, i) => { + const generateRelationshipDocPromise = async () => { + let relation = field.relationTo; - // If this field can be related to many collections, - // Set relationTo based on value - if (hasManyRelations && value && value.relationTo) { - relation = value.relationTo; - } + // If this field can be related to many collections, + // Set relationTo based on value + if (hasManyRelations && value && value.relationTo) { + relation = value.relationTo; + } - if (relation) { - const relatedCollection = findRelatedCollection(relation); + if (relation) { + const relatedCollection = this.collections[relation].config; - if (relatedCollection) { - let relatedDocumentData = data[field.name][i]; - let dataToHook = resultingData[field.name][i]; + if (relatedCollection) { + let relatedDocumentData = data[field.name][i]; + let dataToHook = resultingData[field.name][i]; - if (hasManyRelations) { - relatedDocumentData = data[field.name][i].value; - dataToHook = resultingData[field.name][i].value; - } + if (hasManyRelations) { + relatedDocumentData = data[field.name][i].value; + dataToHook = resultingData[field.name][i].value; + } - // 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, { - req, - data: relatedDocumentData, - hook: 'afterRead', - operationName: 'read', - }); - - await relatedCollection.hooks.afterRead.reduce(async (priorHook, currentHook) => { - await priorHook; - - dataToHook = await currentHook({ + // Only run hooks for populated sub documents - NOT IDs + if (relatedDocumentData && typeof relatedDocumentData !== 'string') { + // Perform field hooks on related collection + dataToHook = await recursivePerformFieldOperations(relatedCollection, { req, - doc: relatedDocumentData, - }) || dataToHook; - }, Promise.resolve()); + data: relatedDocumentData, + hook: 'afterRead', + operationName: 'read', + }); + + await relatedCollection.hooks.afterRead.reduce(async (priorHook, currentHook) => { + await priorHook; + + dataToHook = await currentHook({ + req, + doc: relatedDocumentData, + }) || dataToHook; + }, Promise.resolve()); + } } } - } + }; + + 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; diff --git a/src/globals/init.js b/src/globals/init.js index 24333a3860..ef49baa5b5 100644 --- a/src/globals/init.js +++ b/src/globals/init.js @@ -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); } } diff --git a/src/globals/operations/findOne.js b/src/globals/operations/findOne.js index 9c0272714b..bd8f9f6227 100644 --- a/src/globals/operations/findOne.js +++ b/src/globals/operations/findOne.js @@ -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; diff --git a/src/globals/operations/index.js b/src/globals/operations/index.js deleted file mode 100644 index 3a7c971df0..0000000000 --- a/src/globals/operations/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const update = require('./update'); -const findOne = require('./findOne'); - -module.exports = { - findOne, - update, -}; diff --git a/src/globals/operations/update.js b/src/globals/operations/update.js index 1c2e2486cc..0e0f5a74e9 100644 --- a/src/globals/operations/update.js +++ b/src/globals/operations/update.js @@ -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; diff --git a/src/globals/requestHandlers/findOne.js b/src/globals/requestHandlers/findOne.js index b923db6154..0178b1dd13 100644 --- a/src/globals/requestHandlers/findOne.js +++ b/src/globals/requestHandlers/findOne.js @@ -1,23 +1,26 @@ const httpStatus = require('http-status'); -const { findOne } = require('../operations'); -const findOneHandler = (config, Model, globalConfig) => async (req, res, next) => { - try { - const { slug } = globalConfig; +function findOne(globalConfig) { + async function handler(req, res, next) { + try { + const { slug } = globalConfig; - const result = await findOne({ - req, - Model, - config, - globalConfig, - slug, - depth: req.query.depth, - }); + const result = await this.operations.globals.findOne({ + req, + globalConfig, + slug, + depth: req.query.depth, + }); - return res.status(httpStatus.OK).json(result); - } catch (error) { - return next(error); + return res.status(httpStatus.OK).json(result); + } catch (error) { + return next(error); + } } -}; -module.exports = findOneHandler; + const findOneHandler = handler.bind(this); + + return findOneHandler; +} + +module.exports = findOne; diff --git a/src/globals/requestHandlers/index.js b/src/globals/requestHandlers/index.js deleted file mode 100644 index 511bbcb223..0000000000 --- a/src/globals/requestHandlers/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const findOne = require('./findOne'); -const update = require('./update'); - -module.exports = { - findOne, - update, -}; diff --git a/src/globals/requestHandlers/update.js b/src/globals/requestHandlers/update.js index d2b4020eb7..80e131dc3f 100644 --- a/src/globals/requestHandlers/update.js +++ b/src/globals/requestHandlers/update.js @@ -1,24 +1,26 @@ const httpStatus = require('http-status'); -const { update } = require('../operations'); -const updateHandler = (config, Model, globalConfig) => async (req, res, next) => { - try { - const { slug } = globalConfig; +function update(globalConfig) { + async function handler(req, res, next) { + try { + const { slug } = globalConfig; - const result = await update({ - req, - Model, - config, - globalConfig, - slug, - depth: req.query.depth, - data: req.body, - }); + const result = await this.operations.globals.update({ + req, + globalConfig, + slug, + depth: req.query.depth, + data: req.body, + }); - return res.status(httpStatus.OK).json({ message: 'Global saved successfully.', result }); - } catch (error) { - return next(error); + return res.status(httpStatus.OK).json({ message: 'Global saved successfully.', result }); + } catch (error) { + return next(error); + } } -}; -module.exports = updateHandler; + const updateHandler = handler.bind(this); + return updateHandler; +} + +module.exports = update; diff --git a/src/globals/routes.js b/src/globals/routes.js deleted file mode 100644 index 0ab285d4d5..0000000000 --- a/src/globals/routes.js +++ /dev/null @@ -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; diff --git a/src/graphql/schema/buildObjectType.js b/src/graphql/schema/buildObjectType.js index c4e1f76355..a37efcd27a 100644 --- a/src/graphql/schema/buildObjectType.js +++ b/src/graphql/schema/buildObjectType.js @@ -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]; diff --git a/src/index.js b/src/index.js index 473f0f8b1a..9758dc3cf8 100644 --- a/src/index.js +++ b/src/index.js @@ -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); diff --git a/src/init/bindOperations.js b/src/init/bindOperations.js new file mode 100644 index 0000000000..07a2736689 --- /dev/null +++ b/src/init/bindOperations.js @@ -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; diff --git a/src/init/bindRequestHandlers.js b/src/init/bindRequestHandlers.js new file mode 100644 index 0000000000..a9dbb4fa80 --- /dev/null +++ b/src/init/bindRequestHandlers.js @@ -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;