diff --git a/demo/collections/Admin.js b/demo/collections/Admin.js index 8a9481bba8..ee970dbe17 100644 --- a/demo/collections/Admin.js +++ b/demo/collections/Admin.js @@ -45,6 +45,7 @@ module.exports = { }, { name: 'apiKey', + type: 'text', access: { read: ({ req: { user } }) => { if (checkRole(['admin'], user)) { diff --git a/demo/collections/PublicUsers.js b/demo/collections/PublicUsers.js index 906c800e38..81fe766f27 100644 --- a/demo/collections/PublicUsers.js +++ b/demo/collections/PublicUsers.js @@ -35,7 +35,6 @@ module.exports = { verify: true, maxLoginAttempts: 5, lockTime: 600 * 1000, // lock time in ms - generateVerificationUrl: (req, token) => `http://localhost:3000/api/verify?token=${token}`, cookies: { secure: process.env.NODE_ENV === 'production', sameSite: 'Lax', diff --git a/src/auth/init.ts b/src/auth/init.ts index d6dcc24fbd..cd914bdd82 100644 --- a/src/auth/init.ts +++ b/src/auth/init.ts @@ -1,10 +1,11 @@ import passport from 'passport'; import AnonymousStrategy from 'passport-anonymous'; +import { Payload } from '../index'; import jwtStrategy from './strategies/jwt'; -function initAuth(): void { +function initAuth(ctx: Payload): void { passport.use(new AnonymousStrategy.Strategy()); - passport.use('jwt', jwtStrategy(this)); + passport.use('jwt', jwtStrategy(ctx)); } export default initAuth; diff --git a/src/collections/config/schema.ts b/src/collections/config/schema.ts index d18a5b1c55..c8ef6841c2 100644 --- a/src/collections/config/schema.ts +++ b/src/collections/config/schema.ts @@ -1,19 +1,89 @@ import joi from 'joi'; +import fieldSchema from '../../fields/config/schema'; -const schema = joi.object().keys({ +const collectionSchema = joi.object().keys({ slug: joi.string().required(), labels: joi.object().keys({ singular: joi.string(), plural: joi.string(), }), + preview: joi.func(), access: joi.object().keys({ create: joi.func(), read: joi.func(), update: joi.func(), delete: joi.func(), + unlock: joi.func(), + admin: joi.func(), }), timestamps: joi.boolean() .default(true), -}).unknown(); + admin: joi.object() + .keys({ + useAsTitle: joi.string().default('id'), + defaultColumns: joi.array().items(joi.string()), + enableRichTextRelationship: joi.boolean().default(false), + components: joi.object() + .keys({ + List: joi.func(), + Edit: joi.func(), + }), + }), + fields: joi.array() + .items(fieldSchema) + .default([]), + hooks: joi.object() + .keys({ + beforeOperation: joi.array().items(joi.func()).default([]), + beforeValidate: joi.array().items(joi.func()).default([]), + beforeChange: joi.array().items(joi.func()).default([]), + afterChange: joi.array().items(joi.func()).default([]), + beforeRead: joi.array().items(joi.func()).default([]), + afterRead: joi.array().items(joi.func()).default([]), + beforeDelete: joi.array().items(joi.func()).default([]), + afterDelete: joi.array().items(joi.func()).default([]), + beforeLogin: joi.array().items(joi.func()).default([]), + afterLogin: joi.array().items(joi.func()).default([]), + afterForgotPassword: joi.array().items(joi.func()).default([]), + }).default(), + auth: joi.object() + .keys({ + tokenExpiration: joi.number(), + depth: joi.number().default(0), + verify: joi.alternatives().try( + joi.boolean(), + joi.object().keys({ + generateEmailHTML: joi.func(), + generateEmailSubject: joi.func(), + }), + ), + lockTime: joi.number(), + useAPIKey: joi.boolean(), + cookies: joi.object().keys({ + secure: joi.boolean(), + sameSite: joi.string(), // TODO: add further specificity with joi.xor + domain: joi.string(), + }), + forgotPassword: joi.object().keys({ + generateEmailHTML: joi.func(), + generateEmailSubject: joi.func(), + }), + maxLoginAttempts: joi.number(), + }).default(), + upload: joi.object() + .keys({ + staticURL: joi.string(), + staticDir: joi.string(), + adminThumbnail: joi.string(), + imageSizes: joi.array().items( + joi.object().keys({ + name: joi.string(), + width: joi.number(), + height: joi.number(), + crop: joi.string(), // TODO: add further specificity with joi.xor + }), + ), + }).default(), +}); -export default schema; +export default collectionSchema; diff --git a/src/collections/config/types.ts b/src/collections/config/types.ts index 6f2786b230..b7676e0220 100644 --- a/src/collections/config/types.ts +++ b/src/collections/config/types.ts @@ -1,69 +1,48 @@ /* eslint-disable no-use-before-define */ /* eslint-disable @typescript-eslint/no-explicit-any */ +import joi from 'joi'; +import 'joi-extract-type'; +import { PaginateModel, Document, PassportLocalModel } from 'mongoose'; +import { DeepRequired } from 'ts-essentials'; import { Access } from '../../config/types'; import { Field } from '../../fields/config/types'; import { PayloadRequest } from '../../express/types/payloadRequest'; +import schema from './schema'; + +interface PayloadModel extends PaginateModel, PassportLocalModel{} export type Collection = { - slug: string; - labels: { - singular: string; - plural: string; - }; - fields: Field[]; - admin: { - useAsTitle: string; - defaultColumns: string[]; - components: any; - }; + Model: PayloadModel; + config: CollectionConfig; +}; + +type PayloadCollectionConfigFromSchema = joi.extractType + +interface PayloadCollectionConfig extends PayloadCollectionConfigFromSchema { hooks: { - beforeOperation?: BeforeOperationHook[]; - beforeValidate?: BeforeValidateHook[]; - beforeChange?: BeforeChangeHook[]; - afterChange?: AfterChangeHook[]; - beforeRead?: BeforeReadHook[]; - afterRead?: AfterReadHook[]; - beforeDelete?: BeforeDeleteHook[]; - afterDelete?: AfterDeleteHook[]; - beforeLogin?: BeforeLoginHook[]; - afterLogin?: AfterLoginHook[]; - afterForgotPassword?: AfterForgotPasswordHook[]; - }; - access: { - create: Access; - read: Access; - update: Access; - delete: Access; - admin: Access; + beforeOperation: BeforeOperationHook[]; + beforeValidate: BeforeValidateHook[]; + beforeChange: BeforeChangeHook[]; + afterChange: AfterChangeHook[]; + beforeRead: BeforeReadHook[]; + afterRead: AfterReadHook[]; + beforeDelete: BeforeDeleteHook[]; + afterDelete: AfterDeleteHook[]; + beforeLogin: BeforeLoginHook[]; + afterLogin: AfterLoginHook[]; + afterForgotPassword: AfterForgotPasswordHook[]; + } + access?: { + create?: Access; + read?: Access; + update?: Access; + delete?: Access; + admin?: Access; unlock: Access; }; - auth?: { - tokenExpiration: number; - verify: - | boolean - | { generateEmailHTML: string; generateEmailSubject: string }; - maxLoginAttempts: number; - lockTime: number; - useAPIKey: boolean; - cookies: - | { - secure: boolean; - sameSite: string; - domain?: string; - } - | boolean; - forgotPassword?: { - generateEmailHTML?: (args?: { token?: string, email?: string, req?: PayloadRequest }) => string, - generateEmailSubject?: (args?: { req?: PayloadRequest }) => string, - } - }; - upload: { - imageSizes: ImageSize[]; - staticURL: string; - staticDir: string; - adminThumbnail?: string; - }; -}; +} + +export type CollectionConfig = DeepRequired export type ImageSize = { name: string, diff --git a/src/collections/init.ts b/src/collections/init.ts index fb08393515..a9e624e8f8 100644 --- a/src/collections/init.ts +++ b/src/collections/init.ts @@ -7,15 +7,16 @@ import { UpdateQuery } from 'mongodb'; import apiKeyStrategy from '../auth/strategies/apiKey'; import buildSchema from './buildSchema'; import bindCollectionMiddleware from './bindCollection'; -import { Collection } from './config/types'; +import { CollectionConfig } from './config/types'; +import { Payload } from '../index'; const LocalStrategy = Passport.Strategy; -export default function registerCollections(): void { - this.config.collections = this.config.collections.map((collection: Collection) => { +export default function registerCollections(ctx: Payload): void { + ctx.config.collections = ctx.config.collections.map((collection: CollectionConfig) => { const formattedCollection = collection; - const schema = buildSchema(formattedCollection, this.config); + const schema = buildSchema(formattedCollection, ctx.config); if (collection.auth) { schema.plugin(passportLocalMongoose, { @@ -55,17 +56,17 @@ export default function registerCollections(): void { } } - this.collections[formattedCollection.slug] = { + ctx.collections[formattedCollection.slug] = { Model: mongoose.model(formattedCollection.slug, schema), config: formattedCollection, }; // If not local, open routes - if (!this.config.local) { + if (!ctx.config.local) { const router = express.Router(); const { slug } = collection; - router.all(`/${slug}*`, bindCollectionMiddleware(this.collections[formattedCollection.slug])); + router.all(`/${slug}*`, bindCollectionMiddleware(ctx.collections[formattedCollection.slug])); const { create, @@ -73,14 +74,14 @@ export default function registerCollections(): void { update, findByID, delete: deleteHandler, - } = this.requestHandlers.collections; + } = ctx.requestHandlers.collections; if (collection.auth) { - const AuthCollection = this.collections[formattedCollection.slug]; + const AuthCollection = ctx.collections[formattedCollection.slug]; passport.use(new LocalStrategy(AuthCollection.Model.authenticate())); if (collection.auth.useAPIKey) { - passport.use(`${AuthCollection.config.slug}-api-key`, apiKeyStrategy(this, AuthCollection)); + passport.use(`${AuthCollection.config.slug}-api-key`, apiKeyStrategy(ctx, AuthCollection)); } const { @@ -94,7 +95,7 @@ export default function registerCollections(): void { resetPassword, verifyEmail, unlock, - } = this.requestHandlers.collections.auth; + } = ctx.requestHandlers.collections.auth; if (collection.auth.verify) { router @@ -150,7 +151,7 @@ export default function registerCollections(): void { .get(findByID) .delete(deleteHandler); - this.router.use(router); + ctx.router.use(router); } return formattedCollection; diff --git a/src/config/load.ts b/src/config/load.ts index 70f6d98196..842c80a6fa 100644 --- a/src/config/load.ts +++ b/src/config/load.ts @@ -1,11 +1,11 @@ /* eslint-disable import/no-dynamic-require */ /* eslint-disable global-require */ import path from 'path'; -import { Config } from './types'; +import { PayloadConfig } from './types'; import findConfig from './find'; const configPath = findConfig(); -const loadConfig = (): Config => { +const loadConfig = (): PayloadConfig => { // eslint-disable-next-line @typescript-eslint/no-var-requires let publicConfig = require(configPath); diff --git a/src/config/schema.ts b/src/config/schema.ts index 2acfd8f6b1..72d70889b7 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -89,6 +89,8 @@ const schema = joi.object().keys({ .keys({ window: joi.number().default(15 * 60 * 100), max: joi.number().default(500), + trustProxy: joi.boolean().default(false), + skip: joi.func(), }).default(), graphQL: joi.object() .keys({ @@ -97,6 +99,16 @@ const schema = joi.object().keys({ maxComplexity: joi.number().default(1000), disablePlaygroundInProduction: joi.boolean().default(true), }).default(), + compression: joi.object().unknown(), + localization: joi.alternatives() + .try( + joi.object().keys({ + locales: joi.array().items(joi.string()), + defaultLocale: joi.string(), + fallback: joi.boolean(), + }), + joi.boolean(), + ).default(false), email: joi.alternatives() .try( joi.object() @@ -122,6 +134,6 @@ const schema = joi.object().keys({ config: joi.string(), scss: joi.string().default(path.resolve(__dirname, '../admin/scss/overrides.scss')), }).default(), -}).unknown(); +}); export default schema; diff --git a/src/config/types.ts b/src/config/types.ts index 2a9a98c38e..365002d1b5 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -1,4 +1,4 @@ -import { Express } from 'express'; +import { Express, Response } from 'express'; import joi from 'joi'; import 'joi-extract-type'; import { DeepRequired } from 'ts-essentials'; @@ -31,7 +31,7 @@ export type InitOptions = { license?: string; email?: EmailOptions; local?: boolean; - onInit?: () => void; + onInit?: (Payload) => void; }; export type SendEmailOptions = { @@ -50,9 +50,11 @@ export type MockEmailCredentials = { export type Access = (args?: any) => boolean; // Create type out of Joi schema -// Extend the type with a bit more specificity +// Extend the type with a bit more TypeScript specificity -export type PayloadConfig = joi.extractType & { +type PayloadConfigFromSchema = joi.extractType + +export interface PayloadConfig extends PayloadConfigFromSchema { graphQL: { mutations: { [key: string]: unknown @@ -64,6 +66,9 @@ export type PayloadConfig = joi.extractType & { disablePlaygroundInProduction: boolean; }, email: EmailOptions, -}; + hooks: { + afterError: (err: Error, res: Response) => void, + } +} export type Config = DeepRequired diff --git a/src/config/validate.ts b/src/config/validate.ts index ab51f57953..654cdfb386 100644 --- a/src/config/validate.ts +++ b/src/config/validate.ts @@ -13,6 +13,7 @@ const validateSchema = (config: PayloadConfig): Config => { logger.error(`There were ${result.error.details.length} errors validating your Payload config`); result.error.details.forEach(({ message }, i) => { + console.log(JSON.stringify(result.error.details[i])); logger.error(`${i + 1}: ${message}`); }); diff --git a/src/errors/InvalidConfiguration.ts b/src/errors/InvalidConfiguration.ts index a7b1f2337f..757f8fbe7f 100644 --- a/src/errors/InvalidConfiguration.ts +++ b/src/errors/InvalidConfiguration.ts @@ -2,8 +2,8 @@ import httpStatus from 'http-status'; import APIError from './APIError'; class InvalidConfiguration extends APIError { - constructor(message: string, results?: any) { - super(message, httpStatus.INTERNAL_SERVER_ERROR, results); + constructor(message: string) { + super(message, httpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/src/express/admin.ts b/src/express/admin.ts index d927a5beb3..71b4555f90 100644 --- a/src/express/admin.ts +++ b/src/express/admin.ts @@ -3,13 +3,12 @@ import compression from 'compression'; import history from 'connect-history-api-fallback'; import path from 'path'; import initWebpack from '../webpack/init'; +import { Payload } from '../index'; const router = express.Router(); -function initAdmin(): void { - if (!this.config.admin.disable && process.env.NODE_ENV !== 'test') { - this.initWebpack = initWebpack.bind(this); - +function initAdmin(ctx: Payload): void { + if (!ctx.config.admin.disable && process.env.NODE_ENV !== 'test') { router.use(history()); if (process.env.NODE_ENV === 'production') { @@ -22,13 +21,13 @@ function initAdmin(): void { } }); - router.use(compression(this.config.compression)); + router.use(compression(ctx.config.compression)); router.use(express.static(path.resolve(process.cwd(), 'build'), { redirect: false })); - this.express.use(this.config.routes.admin, router); + ctx.express.use(ctx.config.routes.admin, router); } else { - this.express.use(this.config.routes.admin, history()); - this.express.use(this.initWebpack()); + ctx.express.use(ctx.config.routes.admin, history()); + ctx.express.use(initWebpack(ctx.config)); } } } diff --git a/src/express/middleware/authenticate.ts b/src/express/middleware/authenticate.ts index 91d29fa4ed..a1367e33f1 100644 --- a/src/express/middleware/authenticate.ts +++ b/src/express/middleware/authenticate.ts @@ -1,6 +1,7 @@ import passport from 'passport'; +import { PayloadConfig } from '../../config/types'; -export default (config) => { +export default (config: PayloadConfig) => { const methods = config.collections.reduce((enabledMethods, collection) => { if (collection.auth && collection.auth.useAPIKey) { const collectionMethods = [...enabledMethods]; diff --git a/src/express/static.ts b/src/express/static.ts index 8267a85c7e..ab864ce913 100644 --- a/src/express/static.ts +++ b/src/express/static.ts @@ -3,24 +3,25 @@ import passport from 'passport'; import path from 'path'; import getExecuteStaticAccess from '../auth/getExecuteStaticAccess'; import authenticate from './middleware/authenticate'; +import { Payload } from '../index'; -function initStatic() { - Object.entries(this.collections).forEach(([_, collection]) => { +function initStatic(ctx: Payload) { + Object.entries(ctx.collections).forEach(([_, collection]) => { const { config } = collection; if (config.upload) { const router = express.Router(); router.use(passport.initialize()); - router.use(authenticate(this.config)); + router.use(authenticate(ctx.config)); router.use(getExecuteStaticAccess(collection)); - const staticPath = path.resolve(this.config.paths.configDir, config.upload.staticDir); + const staticPath = path.resolve(ctx.config.paths.configDir, config.upload.staticDir); router.use(express.static(staticPath)); - this.express.use(`${config.upload.staticURL}`, router); + ctx.express.use(`${config.upload.staticURL}`, router); } }); } diff --git a/src/fields/config/schema.ts b/src/fields/config/schema.ts index e69de29bb2..cc7220d69c 100644 --- a/src/fields/config/schema.ts +++ b/src/fields/config/schema.ts @@ -0,0 +1,137 @@ +import joi from 'joi'; + +const baseField = joi.object().keys({ + label: joi.string(), + required: joi.boolean().default(false), + saveToJWT: joi.boolean().default(false), + unique: joi.boolean().default(false), + localized: joi.boolean().default(false), + index: joi.boolean().default(false), + hidden: joi.boolean().default(false), + access: joi.object().keys({ + create: joi.func(), + read: joi.func(), + update: joi.func(), + delete: joi.func(), + unlock: joi.func(), + }), + hooks: joi.object() + .keys({ + beforeValidate: joi.array().items(joi.func()).default([]), + beforeChange: joi.array().items(joi.func()).default([]), + afterChange: joi.array().items(joi.func()).default([]), + afterRead: joi.array().items(joi.func()).default([]), + }).default(), + admin: joi.object().keys({ + position: joi.string().valid('sidebar'), + width: joi.string(), + style: joi.object().unknown(), + readOnly: joi.boolean().default(false), + disabled: joi.boolean().default(false), + condition: joi.func(), + components: joi.object().keys({ + Cell: joi.func(), + Field: joi.func(), + Filter: joi.func(), + }).default({}), + }).default({}), +}); + +// Joi.object({ +// type: Joi.string().required().only(['pizza', 'salad']) +// }) +// .when(Joi.object({ type: 'pizza' }).unknown(), { +// then: Joi.object({ pepperoni: Joi.boolean() }) +// }) +// .when(Joi.object({ type: 'salad' }).unknown(), { +// then: Joi.object({ croutons: Joi.boolean() }) +// }) + +const types = { + text: baseField.keys({ + name: joi.string().required(), + defaultValue: joi.string(), + }), + number: baseField.keys({ + name: joi.string().required(), + defaultValue: joi.string(), + }), + email: baseField.keys({ + name: joi.string().required(), + defaultValue: joi.string(), + }), + row: baseField.keys({ + defaultValue: joi.object().unknown(), + fields: joi.array().items(joi.link('#field')), + }), +}; + +const allTypes = Object.keys(types); + +const fieldSchema = allTypes.reduce((prev, type) => prev.when(joi.object({ type }).unknown(), { + then: types[type], +}), +joi.object({ + type: joi.string().valid(...allTypes).required(), +})).id('field'); + +// const fieldSchema = joi.object({ +// type: joi.string() +// .required() +// .valid( +// 'text', +// 'number', +// 'email', +// 'textarea', +// 'code', +// 'select', +// 'row', +// ).when(joi.object({ type: 'text' }).unknown(), { +// then: , +// }) +// .when(joi.object({ type: 'number' }).unknown(), { +// then: , +// }) +// .when(joi.object({ type: 'email' }).unknown(), { +// then: +// .when(joi.object({ type: 'row' }).unknown(), { +// then: baseField.keys({ +// defaultValue: joi.object().unknown(), +// fields: joi.array().items(joi.link('#field')), +// }), +// }), +// }).id('field'); + +// const fieldSchema = joi.alternatives() +// .try( +// , +// baseField.keys({ +// type: joi.string().valid('number').required(), +// name: joi.string().required(), +// defaultValue: joi.number(), +// }), +// baseField.keys({ +// type: joi.string().valid('email').required(), +// name: joi.string().required(), +// }), +// baseField.keys({ +// type: joi.string().valid('textarea').required(), +// name: joi.string().required(), +// }), +// baseField.keys({ +// type: joi.string().valid('code').required(), +// name: joi.string().required(), +// }), +// baseField.keys({ +// type: joi.string().valid('select').required(), +// name: joi.string().required(), +// options: joi.array().items(joi.string()).required(), +// hasMany: joi.boolean().default(false), +// }).default(), +// baseField.keys({ +// type: joi.string().valid('row').required(), +// fields: joi.array().items(joi.link('#field')), +// }), +// ).id('field'); + +export default fieldSchema; diff --git a/src/globals/buildModel.ts b/src/globals/buildModel.ts index a874d97e1f..d39afdfbf0 100644 --- a/src/globals/buildModel.ts +++ b/src/globals/buildModel.ts @@ -1,9 +1,9 @@ import mongoose from 'mongoose'; import buildSchema from '../mongoose/buildSchema'; import localizationPlugin from '../localization/plugin'; -import { Config } from '../config/types'; +import { PayloadConfig } from '../config/types'; -const buildModel = (config: Config): mongoose.PaginateModel | null => { +const buildModel = (config: PayloadConfig): mongoose.PaginateModel | null => { if (config.globals && config.globals.length > 0) { const globalsSchema = new mongoose.Schema({}, { discriminatorKey: 'globalType', timestamps: true }); diff --git a/src/globals/init.ts b/src/globals/init.ts index 537821fb70..f9ef122897 100644 --- a/src/globals/init.ts +++ b/src/globals/init.ts @@ -1,25 +1,26 @@ import express from 'express'; import buildModel from './buildModel'; +import { Payload } from '../index'; -export default function initGlobals(): void { - if (this.config.globals) { - this.globals = { - Model: buildModel(this.config), - config: this.config.globals, +export default function initGlobals(ctx: Payload): void { + if (ctx.config.globals) { + ctx.globals = { + Model: buildModel(ctx.config), + config: ctx.config.globals, }; // If not local, open routes - if (!this.config.local) { + if (!ctx.config.local) { const router = express.Router(); - this.config.globals.forEach((global) => { + ctx.config.globals.forEach((global) => { router .route(`/globals/${global.slug}`) - .get(this.requestHandlers.globals.findOne(global)) - .post(this.requestHandlers.globals.update(global)); + .get(ctx.requestHandlers.globals.findOne(global)) + .post(ctx.requestHandlers.globals.update(global)); }); - this.router.use(router); + ctx.router.use(router); } } } diff --git a/src/graphql/initPlayground.ts b/src/graphql/initPlayground.ts index 3595d23ae8..f755eeef15 100644 --- a/src/graphql/initPlayground.ts +++ b/src/graphql/initPlayground.ts @@ -1,9 +1,10 @@ import graphQLPlayground from 'graphql-playground-middleware-express'; +import { Payload } from '../index'; -function initPlayground(): void { - if ((!this.config.graphQL.disablePlaygroundInProduction && process.env.NODE_ENV === 'production') || process.env.NODE_ENV !== 'production') { - this.router.get(this.config.routes.graphQLPlayground, graphQLPlayground({ - endpoint: `${this.config.routes.api}${this.config.routes.graphQL}`, +function initPlayground(ctx: Payload): void { + if ((!ctx.config.graphQL.disablePlaygroundInProduction && process.env.NODE_ENV === 'production') || process.env.NODE_ENV !== 'production') { + ctx.router.get(ctx.config.routes.graphQLPlayground, graphQLPlayground({ + endpoint: `${ctx.config.routes.api}${ctx.config.routes.graphQL}`, settings: { 'request.credentials': 'include', }, diff --git a/src/index.ts b/src/index.ts index 16f9a4ef9e..45064982fc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,11 @@ -import express, { Express, Request, Router } from 'express'; +import express, { Express, Router } from 'express'; import crypto from 'crypto'; - import { TestAccount } from 'nodemailer'; +import { AuthenticateOptions } from 'passport'; import { - Config, EmailOptions, InitOptions, + PayloadConfig, } from './config/types'; import { Collection, @@ -20,7 +20,7 @@ import { DeleteOptions, FindResponse, } from './types'; -import Logger, { PayloadLogger } from './utilities/logger'; +import Logger from './utilities/logger'; import bindOperations from './init/bindOperations'; import bindRequestHandlers from './init/bindRequestHandlers'; import bindResolvers from './init/bindResolvers'; @@ -51,11 +51,15 @@ require('isomorphic-fetch'); * @description Payload */ export class Payload { - config: Config; + config: PayloadConfig = loadConfig(); collections: Collection[] = []; - logger: PayloadLogger; + graphQL: any; + + globals: any; + + logger = Logger(); express: Express @@ -73,35 +77,26 @@ export class Payload { local: boolean; - initAuth: typeof initAuth; + encrypt = encrypt; - encrypt: typeof encrypt; - - decrypt: typeof decrypt; - - initCollections: typeof initCollections; - - initGlobals: typeof initGlobals; - - initGraphQLPlayground: typeof initGraphQLPlayground; - - initStatic: typeof initStatic; - - initAdmin: typeof initAdmin; + decrypt = decrypt; operations: { [key: string]: any }; + errorHandler: any; + + authenticate: (strategy: string | string[], options: AuthenticateOptions, callback?: (...args: any[]) => any) => any; + performFieldOperations: typeof performFieldOperations; - // requestHandlers: { collections: { create: any; find: any; findByID: any; update: any; delete: any; auth: { access: any; forgotPassword: any; init: any; login: any; logout: any; me: any; refresh: any; registerFirstUser: any; resetPassword: any; verifyEmail: any; unlock: any; }; }; globals: { ...; }; }; + + requestHandlers: { [key: string]: any }; /** * @description Initializes Payload * @param options */ init(options: InitOptions): void { - this.logger = Logger(); this.logger.info('Starting Payload...'); - if (!options.secret) { throw new Error( 'Error: missing secret key. A secret key is needed to secure Payload.', @@ -129,44 +124,21 @@ export class Payload { bindRequestHandlers(this); bindResolvers(this); - this.initAuth = initAuth.bind(this); - this.encrypt = encrypt.bind(this); - this.decrypt = decrypt.bind(this); - this.initCollections = initCollections.bind(this); - this.initGlobals = initGlobals.bind(this); - this.initGraphQLPlayground = initGraphQLPlayground.bind(this); - // this.buildEmail = buildEmail.bind(this); - this.sendEmail = this.sendEmail.bind(this); - this.getMockEmailCredentials = this.getMockEmailCredentials.bind(this); - this.initStatic = initStatic.bind(this); - this.initAdmin = initAdmin.bind(this); this.performFieldOperations = performFieldOperations.bind(this); - this.create = this.create.bind(this); - this.find = this.find.bind(this); - this.findGlobal = this.findGlobal.bind(this); - this.updateGlobal = this.updateGlobal.bind(this); - this.findByID = this.findByID.bind(this); - this.update = this.update.bind(this); - this.login = this.login.bind(this); - this.forgotPassword = this.forgotPassword.bind(this); - this.resetPassword = this.resetPassword.bind(this); - this.unlock = this.unlock.bind(this); - this.verifyEmail = this.verifyEmail.bind(this); - // If not initializing locally, scaffold router if (!this.config.local) { this.router = express.Router(); this.router.use(...expressMiddleware(this)); - this.initAuth(); + initAuth(this); } // Configure email service this.email = buildEmail(this.config.email); // Initialize collections & globals - this.initCollections(); - this.initGlobals(); + initCollections(this); + initGlobals(this); // Connect to database connectMongoose(this.mongoURL); @@ -179,9 +151,11 @@ export class Payload { // If not initializing locally, set up HTTP routing if (!this.config.local) { this.express = options.express; - if (this.config.rateLimit && this.config.rateLimit.trustProxy) { this.express.set('trust proxy', 1); } + if (this.config.rateLimit.trustProxy) { + this.express.set('trust proxy', 1); + } - this.initAdmin(); + initAdmin(this); this.router.get('/access', this.requestHandlers.collections.auth.access); @@ -193,13 +167,13 @@ export class Payload { (req, res) => graphQLHandler.init(req, res)(req, res), ); - this.initGraphQLPlayground(); + initGraphQLPlayground(this); // Bind router to API this.express.use(this.config.routes.api, this.router); // Enable static routes for all collections permitting upload - this.initStatic(); + initStatic(this); this.errorHandler = errorHandler(this.config, this.logger); this.router.use(this.errorHandler); @@ -210,13 +184,13 @@ export class Payload { if (typeof options.onInit === 'function') options.onInit(this); } - async sendEmail(message: Message): Promise { + sendEmail = async (message: Message): Promise => { const email = await this.email; const result = email.transport.sendMail(message); return result; } - async getMockEmailCredentials(): Promise { + getMockEmailCredentials = async (): Promise => { const email = await this.email as MockEmailHandler; return email.account; } diff --git a/src/init/bindOperations.ts b/src/init/bindOperations.ts index 78c6da52bf..6b26b81a21 100644 --- a/src/init/bindOperations.ts +++ b/src/init/bindOperations.ts @@ -1,3 +1,4 @@ +import { Payload } from '../index'; import access from '../auth/operations/access'; import forgotPassword from '../auth/operations/forgotPassword'; import init from '../auth/operations/init'; @@ -19,7 +20,7 @@ import deleteHandler from '../collections/operations/delete'; import findOne from '../globals/operations/findOne'; import globalUpdate from '../globals/operations/update'; -function bindOperations(ctx): void { +function bindOperations(ctx: Payload): void { ctx.operations = { collections: { create: create.bind(ctx), diff --git a/src/init/bindRequestHandlers.ts b/src/init/bindRequestHandlers.ts index d356eadc78..0f7eb51be9 100644 --- a/src/init/bindRequestHandlers.ts +++ b/src/init/bindRequestHandlers.ts @@ -18,8 +18,9 @@ import deleteHandler from '../collections/requestHandlers/delete'; import findOne from '../globals/requestHandlers/findOne'; import globalUpdate from '../globals/requestHandlers/update'; +import { Payload } from '../index'; -function bindRequestHandlers(ctx): void { +function bindRequestHandlers(ctx: Payload): void { ctx.requestHandlers = { collections: { create: create.bind(ctx), diff --git a/src/init/bindResolvers.ts b/src/init/bindResolvers.ts index b0d4c3dc83..59482bd6b7 100644 --- a/src/init/bindResolvers.ts +++ b/src/init/bindResolvers.ts @@ -17,8 +17,9 @@ import deleteResolver from '../collections/graphql/resolvers/delete'; import findOne from '../globals/graphql/resolvers/findOne'; import globalUpdate from '../globals/graphql/resolvers/update'; +import { Payload } from '../index'; -function bindResolvers(ctx): void { +function bindResolvers(ctx: Payload): void { ctx.graphQL = { resolvers: { collections: { diff --git a/src/mongoose/connect.ts b/src/mongoose/connect.ts index 8c58c113a9..b0db5b2b0a 100644 --- a/src/mongoose/connect.ts +++ b/src/mongoose/connect.ts @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import mongoose from 'mongoose'; import Logger from '../utilities/logger'; + const logger = Logger(); const connectMongoose = async (url: string) => { diff --git a/src/webpack/getWebpackDevConfig.ts b/src/webpack/getWebpackDevConfig.ts index 8737060214..dc7565ca33 100644 --- a/src/webpack/getWebpackDevConfig.ts +++ b/src/webpack/getWebpackDevConfig.ts @@ -1,12 +1,12 @@ import HtmlWebpackPlugin from 'html-webpack-plugin'; import path from 'path'; import webpack, { Configuration } from 'webpack'; -import { Config } from '../config/types'; import babelConfig from '../babel.config'; +import { PayloadConfig } from '../config/types'; const mockModulePath = path.resolve(__dirname, './mocks/emptyModule.js'); -export default (config: Config): Configuration => { +export default (config: PayloadConfig): Configuration => { let webpackConfig: Configuration = { entry: { main: [ diff --git a/src/webpack/init.ts b/src/webpack/init.ts index aa0448e537..00926604f4 100644 --- a/src/webpack/init.ts +++ b/src/webpack/init.ts @@ -3,11 +3,12 @@ import express, { Router } from 'express'; import webpackDevMiddleware from 'webpack-dev-middleware'; import webpackHotMiddleware from 'webpack-hot-middleware'; import getWebpackDevConfig from './getWebpackDevConfig'; +import { PayloadConfig } from '../config/types'; const router = express.Router(); -function initWebpack(): Router { - const webpackDevConfig = getWebpackDevConfig(this.config); +function initWebpack(config: PayloadConfig): Router { + const webpackDevConfig = getWebpackDevConfig(config); const compiler = webpack(webpackDevConfig); router.use(webpackDevMiddleware(compiler, { diff --git a/yarn.lock b/yarn.lock index 596f384af2..098e8a41da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1957,7 +1957,7 @@ "@types/passport-local-mongoose@^4.0.13": version "4.0.13" - resolved "https://registry.yarnpkg.com/@types/passport-local-mongoose/-/passport-local-mongoose-4.0.13.tgz#0edc3aedcc82a70b7e461efc2dc85f42ccb80aa3" + resolved "https://registry.npmjs.org/@types/passport-local-mongoose/-/passport-local-mongoose-4.0.13.tgz#0edc3aedcc82a70b7e461efc2dc85f42ccb80aa3" integrity sha512-tjVfcyFXO+EIzjTg6DHm+Wq9LwftqDv0kQ/IlLqFHBtZ6gKMy5pLKdr4g62VGEJsgPX8F9UGb9inDREQ2tOpEw== dependencies: "@types/passport-local" "*"