cleanup in payload init

This commit is contained in:
Dan Ribbens
2020-11-25 17:36:51 -05:00
parent d09fba47f1
commit 70a84d90ad
20 changed files with 137 additions and 142 deletions

View File

@@ -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;

View File

@@ -1,22 +1,33 @@
/* eslint-disable no-use-before-define */
/* eslint-disable @typescript-eslint/no-explicit-any */
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';
interface PayloadModel extends PaginateModel<Document>, PassportLocalModel<Document>{}
export type Collection = {
Model: PayloadModel;
config: CollectionConfig;
};
export type CollectionConfig = DeepRequired<PayloadCollectionConfig>
export type PayloadCollectionConfig = {
slug: string;
labels: {
labels?: {
singular: string;
plural: string;
};
fields: Field[];
admin: {
useAsTitle: string;
defaultColumns: string[];
components: any;
admin?: {
useAsTitle?: string;
defaultColumns?: string[];
components?: any;
};
hooks: {
hooks?: {
beforeOperation?: BeforeOperationHook[];
beforeValidate?: BeforeValidateHook[];
beforeChange?: BeforeChangeHook[];
@@ -28,27 +39,28 @@ export type Collection = {
beforeLogin?: BeforeLoginHook[];
afterLogin?: AfterLoginHook[];
afterForgotPassword?: AfterForgotPasswordHook[];
afterError?: AfterErrorHook;
};
access: {
create: Access;
read: Access;
update: Access;
delete: Access;
admin: Access;
unlock: Access;
access?: {
create?: Access;
read?: Access;
update?: Access;
delete?: Access;
admin?: Access;
unlock?: Access;
};
auth?: {
tokenExpiration: number;
verify:
tokenExpiration?: number;
verify?:
| boolean
| { generateEmailHTML: string; generateEmailSubject: string };
maxLoginAttempts: number;
lockTime: number;
useAPIKey: boolean;
cookies:
maxLoginAttempts?: number;
lockTime?: number;
useAPIKey?: boolean;
cookies?:
| {
secure: boolean;
sameSite: string;
secure?: boolean;
sameSite?: string;
domain?: string;
}
| boolean;
@@ -57,10 +69,10 @@ export type Collection = {
generateEmailSubject?: (args?: { req?: PayloadRequest }) => string,
}
};
upload: {
imageSizes: ImageSize[];
staticURL: string;
staticDir: string;
upload?: {
imageSizes?: ImageSize[];
staticURL?: string;
staticDir?: string;
adminThumbnail?: string;
};
};
@@ -144,3 +156,5 @@ export type AfterLoginHook = (args?: {
export type AfterForgotPasswordHook = (args?: {
args?: any;
}) => any;
export type AfterErrorHook = (args?: { [p: string]: any }, response?: any) => any;

View File

@@ -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;

View File

@@ -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);

View File

@@ -3,7 +3,7 @@ import { DeepRequired } from 'ts-essentials';
import { Transporter } from 'nodemailer';
import SMTPConnection from 'nodemailer/lib/smtp-connection';
import { GraphQLType } from 'graphql';
import { Collection } from '../collections/config/types';
import { PayloadCollectionConfig } from '../collections/config/types';
import { Global } from '../globals/config/types';
import { PayloadRequest } from '../express/types/payloadRequest';
import InitializeGraphQL from '../graphql';
@@ -29,7 +29,7 @@ export type InitOptions = {
license?: string;
email?: EmailOptions;
local?: boolean;
onInit?: () => void;
onInit?: (Payload) => void;
};
export type SendEmailOptions = {
@@ -58,13 +58,14 @@ export type PayloadConfig = {
disable?: boolean;
indexHTML?: string;
};
collections?: Collection[];
collections?: PayloadCollectionConfig[];
globals?: Global[];
serverURL: string;
cookiePrefix?: string;
csrf?: string[];
cors?: string[];
publicENV?: { [key: string]: string };
compression?: { [key: string]: string };
routes?: {
api?: string;
admin?: string;

View File

@@ -2,7 +2,7 @@ import httpStatus from 'http-status';
import APIError from './APIError';
class InvalidConfiguration extends APIError {
constructor(message: string, results: any) {
constructor(message: string, results: any = {}) {
super(message, httpStatus.INTERNAL_SERVER_ERROR, results);
}
}

View File

@@ -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));
}
}
}

View File

@@ -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];

View File

@@ -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);
}
});
}

View File

@@ -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<any> | null => {
const buildModel = (config: PayloadConfig): mongoose.PaginateModel<any> | null => {
if (config.globals && config.globals.length > 0) {
const globalsSchema = new mongoose.Schema({}, { discriminatorKey: 'globalType', timestamps: true });

View File

@@ -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);
}
}
}

View File

@@ -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',
},

View File

@@ -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.',
@@ -123,54 +118,27 @@ export class Payload {
this.mongoURL = options.mongoURL;
this.local = options.local;
this.config = loadConfig();
if (typeof this.config.paths === 'undefined') this.config.paths = {};
// this.collections = {};
bindOperations(this);
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);
@@ -183,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 && this.config.rateLimit.trustProxy) {
this.express.set('trust proxy', 1);
}
this.initAdmin();
initAdmin(this);
this.router.get('/access', this.requestHandlers.collections.auth.access);
@@ -197,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);
@@ -214,13 +184,13 @@ export class Payload {
if (typeof options.onInit === 'function') options.onInit(this);
}
async sendEmail(message: Message): Promise<any> {
sendEmail = async (message: Message): Promise<any> => {
const email = await this.email;
const result = email.transport.sendMail(message);
return result;
}
async getMockEmailCredentials(): Promise<TestAccount> {
getMockEmailCredentials = async (): Promise<TestAccount> => {
const email = await this.email as MockEmailHandler;
return email.account;
}

View File

@@ -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),

View File

@@ -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),

View File

@@ -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: {

View File

@@ -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) => {

View File

@@ -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: [

View File

@@ -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, {