merges and WIP - field types
This commit is contained in:
@@ -45,6 +45,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
name: 'apiKey',
|
||||
type: 'text',
|
||||
access: {
|
||||
read: ({ req: { user } }) => {
|
||||
if (checkRole(['admin'], user)) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Document>, PassportLocalModel<Document>{}
|
||||
|
||||
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<typeof schema>
|
||||
|
||||
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<PayloadCollectionConfig>
|
||||
|
||||
export type ImageSize = {
|
||||
name: string,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<typeof schema> & {
|
||||
type PayloadConfigFromSchema = joi.extractType<typeof schema>
|
||||
|
||||
export interface PayloadConfig extends PayloadConfigFromSchema {
|
||||
graphQL: {
|
||||
mutations: {
|
||||
[key: string]: unknown
|
||||
@@ -64,6 +66,9 @@ export type PayloadConfig = joi.extractType<typeof schema> & {
|
||||
disablePlaygroundInProduction: boolean;
|
||||
},
|
||||
email: EmailOptions,
|
||||
};
|
||||
hooks: {
|
||||
afterError: (err: Error, res: Response) => void,
|
||||
}
|
||||
}
|
||||
|
||||
export type Config = DeepRequired<PayloadConfig>
|
||||
|
||||
@@ -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}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
84
src/index.ts
84
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<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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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" "*"
|
||||
|
||||
Reference in New Issue
Block a user