From 618d6dc19b3efa2b5c062f0219560ba165f65dac Mon Sep 17 00:00:00 2001 From: James Date: Tue, 31 Dec 2019 17:38:23 -0500 Subject: [PATCH] singularizes Upload and User --- demo/collections/Upload.js | 43 +++++++++ demo/collections/index.js | 9 -- demo/globals/index.js | 4 - demo/init.js | 14 --- demo/payload.config.js | 12 ++- src/auth/baseFields.js | 11 +++ src/auth/init.js | 23 +++++ src/auth/passwordResets/config.js | 13 --- src/client/config/getWebpackDevConfig.js | 22 ++--- .../{registerSchema.js => buildSchema.js} | 17 +--- src/collections/registerRoutes.js | 20 ----- src/collections/upload.js | 0 src/collections/validate.js | 8 +- src/errors/ExistingUser.js | 7 -- src/errors/index.js | 1 - src/index.js | 88 +++++++++++++++---- src/mongoose/schema/buildSchema.js | 4 +- src/uploads/baseImageFields.js | 8 ++ src/uploads/baseUploadFields.js | 9 ++ src/uploads/images/config.js | 19 ---- src/uploads/images/model.js | 21 ----- src/uploads/model.js | 21 ----- src/uploads/routes.js | 12 +-- 23 files changed, 198 insertions(+), 188 deletions(-) create mode 100644 demo/collections/Upload.js delete mode 100644 demo/collections/index.js delete mode 100644 demo/globals/index.js create mode 100644 src/auth/baseFields.js create mode 100644 src/auth/init.js delete mode 100644 src/auth/passwordResets/config.js rename src/collections/{registerSchema.js => buildSchema.js} (64%) create mode 100644 src/collections/upload.js delete mode 100644 src/errors/ExistingUser.js create mode 100644 src/uploads/baseImageFields.js create mode 100644 src/uploads/baseUploadFields.js delete mode 100644 src/uploads/images/config.js delete mode 100644 src/uploads/images/model.js delete mode 100644 src/uploads/model.js diff --git a/demo/collections/Upload.js b/demo/collections/Upload.js new file mode 100644 index 0000000000..9f968d0fc6 --- /dev/null +++ b/demo/collections/Upload.js @@ -0,0 +1,43 @@ +const passportLocalMongoose = require('passport-local-mongoose'); +const payloadConfig = require('../payload.config'); +const userValidate = require('../User/User.validate'); + +module.exports = { + slug: 'uploads', + labels: { + singular: 'Upload', + plural: 'Uploads', + }, + useAsTitle: 'filename', + policies: { + create: (req, res, next) => { + return next(); + }, + read: (req, res, next) => { + return next(); + }, + update: (req, res, next) => { + return next(); + }, + destroy: (req, res, next) => { + return next(); + }, + }, + fields: [ + { + name: 'email', + label: 'Email Address', + type: 'input', + unique: true, + maxLength: 100, + required: true, + }, + { + name: 'role', + type: 'enum', + enum: payloadConfig.roles, + default: 'user', + }, + ], + timestamps: true, +}; diff --git a/demo/collections/index.js b/demo/collections/index.js deleted file mode 100644 index 95d3d27efb..0000000000 --- a/demo/collections/index.js +++ /dev/null @@ -1,9 +0,0 @@ -const Category = require('./Category'); -const User = require('./User'); -const Page = require('./Page'); - -module.exports = (payload) => { - payload.registerCollection(Page); - payload.registerCollection(User); - payload.registerCollection(Category); -}; diff --git a/demo/globals/index.js b/demo/globals/index.js deleted file mode 100644 index 39c3de4be3..0000000000 --- a/demo/globals/index.js +++ /dev/null @@ -1,4 +0,0 @@ -const Header = require('./Header'); -const Footer = require('./Footer'); - -module.exports = { Header, Footer }; diff --git a/demo/init.js b/demo/init.js index 4851dcddc6..7a77438737 100644 --- a/demo/init.js +++ b/demo/init.js @@ -3,8 +3,6 @@ const Payload = require('../'); const config = require('./payload.config'); const router = express.Router({}); // eslint-disable-line new-cap -const globals = require('./globals'); -const registerCollections = require('./collections'); const app = express(); @@ -14,18 +12,6 @@ const payload = new Payload({ router, }); -payload.registerGlobals(globals); - -registerCollections(payload); - -if (process.env.NODE_ENV !== 'production') { - router.use((req, res, next) => { - res.header('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization'); - next(); - }); -} - app.listen(config.port, () => { console.log(`listening on ${config.port}...`); }); diff --git a/demo/payload.config.js b/demo/payload.config.js index 604765f32b..2dc2bfc85a 100644 --- a/demo/payload.config.js +++ b/demo/payload.config.js @@ -1,7 +1,17 @@ const path = require('path'); +const Page = require('./collections/Page'); +const Category = require('./collections/Category'); +const User = require('./collections/User'); +const Upload = require('./collections/Upload'); +const Header = require('./globals/Header'); +const Footer = require('./globals/Footer'); module.exports = { disableAdmin: true, + collections: [Page, Category], + user: User, + upload: Upload, + globals: [Header, Footer], port: 3000, serverUrl: 'http://localhost:3000', cors: ['http://localhost', 'http://localhost:8080', 'http://localhost:8081'], @@ -14,7 +24,7 @@ module.exports = { compression: {}, paths: { scssOverrides: path.resolve(__dirname, 'client/scss/overrides.scss'), - customComponents: path.resolve(__dirname, 'client/components/custom'), + config: path.resolve(__dirname), }, mongoURL: 'mongodb://localhost/payload', localization: { diff --git a/src/auth/baseFields.js b/src/auth/baseFields.js new file mode 100644 index 0000000000..cd9770577f --- /dev/null +++ b/src/auth/baseFields.js @@ -0,0 +1,11 @@ +export default [{ + name: 'resetPasswordToken', + // TODO: how should we define a field a string that cannot be seen in the admin panel? + // using type: 'input' for now + type: 'input', +}, +{ + name: 'resetPasswordExpiration', + type: 'date', +}, +]; diff --git a/src/auth/init.js b/src/auth/init.js new file mode 100644 index 0000000000..d1c5a8d25d --- /dev/null +++ b/src/auth/init.js @@ -0,0 +1,23 @@ +import passport from 'passport'; +import AnonymousStrategy from 'passport-anonymous'; +import jwtStrategy from './jwt'; +import initRoutes from '../routes/init'; +import authRoutes from './routes'; + +const initUsers = (User, config, router) => { + passport.use(User.createStrategy()); + + const { user: userConfig } = config; + + if (userConfig.auth.strategy === 'jwt') { + passport.use(jwtStrategy(User)); + passport.serializeUser(User.serializeUser()); + passport.deserializeUser(User.deserializeUser()); + } + passport.use(new AnonymousStrategy.Strategy()); + + router.use('', initRoutes(User)); + router.use('', authRoutes(userConfig, User)); +}; + +export default initUsers; diff --git a/src/auth/passwordResets/config.js b/src/auth/passwordResets/config.js deleted file mode 100644 index c943a97491..0000000000 --- a/src/auth/passwordResets/config.js +++ /dev/null @@ -1,13 +0,0 @@ -export default { - fields: [{ - name: 'resetPasswordToken', - // TODO: how should we define a field a string that cannot be seen in the admin panel? - // using type: 'input' for now - type: 'input', - }, - { - name: 'resetPasswordExpiration', - type: 'date', - } - ] -} diff --git a/src/client/config/getWebpackDevConfig.js b/src/client/config/getWebpackDevConfig.js index dc38477f8b..8b5989352d 100644 --- a/src/client/config/getWebpackDevConfig.js +++ b/src/client/config/getWebpackDevConfig.js @@ -45,7 +45,7 @@ module.exports = (config) => { output: { path: '/', publicPath: '/static', - filename: '[name].js' + filename: '[name].js', }, devServer: { historyApiFallback: true, @@ -58,8 +58,8 @@ module.exports = (config) => { test: /\.js$/, exclude: /node_modules/, use: { - loader: 'babel-loader' - } + loader: 'babel-loader', + }, }, { // "oneOf" will traverse all following loaders until one will @@ -82,7 +82,7 @@ module.exports = (config) => { // to immediately apply all styles to the DOM. { test: /\.(scss|sass)$/, - use: getStyleLoaders({ importLoaders: 2 }, 'sass-loader') + use: getStyleLoaders({ importLoaders: 2 }, 'sass-loader'), }, // "file" loader makes sure those assets get served by WebpackDevServer. // When you `import` an asset, you get its (virtual) filename. @@ -100,14 +100,14 @@ module.exports = (config) => { name: 'static/media/[name].[hash:8].[ext]', }, }, - ] - } + ], + }, ], }, plugins: [ new HtmlWebpackPlugin({ template: './src/client/index.html', - filename: './index.html' + filename: './index.html', }), new webpack.HotModuleReplacementPlugin(), ], @@ -115,8 +115,8 @@ module.exports = (config) => { alias: { payload: path.resolve(__dirname, '../../'), scssOverrides: config.paths.scssOverrides, - customComponents: config.paths.customComponents - } - } - } + config: config.paths.config, + }, + }, + }; }; diff --git a/src/collections/registerSchema.js b/src/collections/buildSchema.js similarity index 64% rename from src/collections/registerSchema.js rename to src/collections/buildSchema.js index 73c4867da8..9ebf1eaafe 100644 --- a/src/collections/registerSchema.js +++ b/src/collections/buildSchema.js @@ -1,18 +1,12 @@ -import mongoose from 'mongoose'; import mongooseHidden from 'mongoose-hidden'; import paginate from 'mongoose-paginate-v2'; import autopopulate from 'mongoose-autopopulate'; import buildQueryPlugin from '../mongoose/buildQuery'; import localizationPlugin from '../localization/plugin'; -import passwordResetConfig from '../auth/passwordResets/config'; import buildSchema from '../mongoose/schema/buildSchema'; -const addSchema = (collection, config) => { - if (collection.auth) { - collection.fields.push(...passwordResetConfig.fields); - } - - const schema = buildSchema(collection.fields, config, { timestamps: collection.timestamps }); +const buildCollectionSchema = (collection, config, schemaOptions = {}) => { + const schema = buildSchema(collection.fields, config, { timestamps: collection.timestamps, ...schemaOptions }); schema.plugin(paginate) .plugin(buildQueryPlugin) @@ -26,10 +20,7 @@ const addSchema = (collection, config) => { }); } - return { - config: collection, - model: mongoose.model(collection.slug, schema), - }; + return schema; }; -export default addSchema; +export default buildCollectionSchema; diff --git a/src/collections/registerRoutes.js b/src/collections/registerRoutes.js index 777322a344..f0543f620b 100644 --- a/src/collections/registerRoutes.js +++ b/src/collections/registerRoutes.js @@ -1,8 +1,3 @@ -import passport from 'passport'; -import AnonymousStrategy from 'passport-anonymous'; -import jwtStrategy from '../auth/jwt'; -import initRoutes from '../routes/init'; -import authRoutes from '../auth/routes'; import { query, create, findOne, destroy, update, } from '../mongoose/requestHandlers'; @@ -11,21 +6,6 @@ import setModelLocaleMiddleware from '../localization/setModelLocale'; import { loadPolicy } from '../auth/loadPolicy'; const registerRoutes = ({ model, config }, router) => { - // register passport with model - if (config.auth) { - passport.use(model.createStrategy()); - - if (config.auth.strategy === 'jwt') { - passport.use(jwtStrategy(model)); - passport.serializeUser(model.serializeUser()); - passport.deserializeUser(model.deserializeUser()); - } - passport.use(new AnonymousStrategy.Strategy()); - - router.use('', initRoutes(model)); - router.use('', authRoutes(config, model)); - } - router.all(`/${config.slug}*`, bindModelMiddleware(model), setModelLocaleMiddleware()); diff --git a/src/collections/upload.js b/src/collections/upload.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/collections/validate.js b/src/collections/validate.js index c1de43a679..e306492817 100644 --- a/src/collections/validate.js +++ b/src/collections/validate.js @@ -1,10 +1,6 @@ -import { DuplicateCollection, MissingCollectionLabel, ExistingUser } from '../errors'; - -export default function validateCollection(collection, collections, userModel) { - if (collection.auth && userModel) { - throw new ExistingUser(); - } +import { DuplicateCollection, MissingCollectionLabel } from '../errors'; +export default function validateCollection(collection, collections) { if (!collection.labels.singular) { throw new MissingCollectionLabel(collection); } diff --git a/src/errors/ExistingUser.js b/src/errors/ExistingUser.js deleted file mode 100644 index d24b002fdb..0000000000 --- a/src/errors/ExistingUser.js +++ /dev/null @@ -1,7 +0,0 @@ -import { APIError } from './APIError'; - -export class ExistingUser extends APIError { - constructor(config) { - super(`Error when registering "${ config.labels.singular }": a user model already exists.`); - } -} diff --git a/src/errors/index.js b/src/errors/index.js index 38cafcec1c..08f3aafd5c 100644 --- a/src/errors/index.js +++ b/src/errors/index.js @@ -3,5 +3,4 @@ export { DuplicateCollection } from './DuplicateCollection'; export { DuplicateGlobal } from './DuplicateGlobal'; export { MissingCollectionLabel } from './MissingCollectionLabel'; export { MissingGlobalLabel } from './MissingGlobalLabel'; -export { ExistingUser } from './ExistingUser'; export { NotFound } from './NotFound'; diff --git a/src/index.js b/src/index.js index 8a1d353b7c..9b8031ff83 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,18 @@ +import mongoose from 'mongoose'; import connectMongoose from './init/connectMongoose'; import registerExpressMiddleware from './init/registerExpressMiddleware'; -import initUploads from './init/uploads'; import initPassport from './init/passport'; import initCORS from './init/cors'; +import initUploads from './init/uploads'; import initWebpack from './init/webpack'; +import initUserAuth from './auth/init'; +import baseUserFields from './auth/baseFields'; +import baseUploadFields from './uploads/baseUploadFields'; +import baseImageFields from './uploads/baseImageFields'; +import registerUploadRoutes from './uploads/routes'; import registerConfigRoute from './routes/config'; import validateCollection from './collections/validate'; -import registerCollectionSchema from './collections/registerSchema'; +import buildCollectionSchema from './collections/buildSchema'; import registerCollectionRoutes from './collections/registerRoutes'; import validateGlobals from './globals/validate'; import registerGlobalSchema from './globals/registerSchema'; @@ -16,27 +22,77 @@ class Payload { collections = {}; constructor(options) { + // Setup & inititalization + connectMongoose(options.config.mongoURL); + registerExpressMiddleware(options); + initPassport(options.app); + initUploads(options); + initCORS(options); + registerConfigRoute(options, this.getCollections, this.getGlobals); + + // Bind options, app, router this.config = options.config; this.app = options.app; this.router = options.router; - connectMongoose(options.config.mongoURL); - registerExpressMiddleware(options); - initUploads(options); - initPassport(this.app); - initCORS(options); - registerConfigRoute(options, this.getCollections, this.getGlobals); + // Register and bind required collections + this.registerUser(); + this.registerUpload(); - if (!this.config.disableAdmin) { - initWebpack(options); - } + // Register custom collections + this.config.collections.forEach((collection) => { + validateCollection(collection, this.collections); + + this.collections[collection.slug] = { + model: mongoose.model(collection.slug, buildCollectionSchema(collection, this.config)), + config: collection, + }; + + registerCollectionRoutes(this.collections[collection.slug], this.router); + }); + + // Register globals + this.registerGlobals(this.config.globals); + + // Enable client webpack + if (!this.config.disableAdmin) initWebpack(options); } - registerCollection = (collection) => { - validateCollection(collection, this.collections, this.User); - this.collections[collection.slug] = registerCollectionSchema(collection, this.config); - registerCollectionRoutes(this.collections[collection.slug], this.router); - }; + registerUser = () => { + this.config.user.fields.push(...baseUserFields); + const userSchema = buildCollectionSchema(this.config.user, this.config); + + this.User = mongoose.model(this.config.user.labels.singular, userSchema); + initUserAuth(this.User, this.config, this.router); + registerCollectionRoutes({ + model: this.User, + config: this.config.user, + }, this.router); + } + + registerUpload = () => { + const uploadSchema = buildCollectionSchema( + this.config.upload, + this.config, + { discriminatorKey: 'type' }, + baseUploadFields, + ); + + const imageSchema = buildCollectionSchema( + this.config.upload, + this.config, + { discriminatorKey: 'type' }, + { ...baseUploadFields, ...baseImageFields }, + ); + + this.Upload = mongoose.model(this.config.upload.labels.singular, uploadSchema); + this.Upload.discriminator('image', imageSchema); + + registerUploadRoutes({ + default: this.Upload, + image: imageSchema, + }, this.config, this.router); + } registerGlobals = (globals) => { validateGlobals(globals); diff --git a/src/mongoose/schema/buildSchema.js b/src/mongoose/schema/buildSchema.js index 4731fa5793..9b57dff9a7 100644 --- a/src/mongoose/schema/buildSchema.js +++ b/src/mongoose/schema/buildSchema.js @@ -2,8 +2,8 @@ import { Schema } from 'mongoose'; import fieldToSchemaMap from './fieldToSchemaMap'; import baseFields from './baseFields'; -const buildSchema = (configFields, config, options = {}) => { - const fields = { ...baseFields }; +const buildSchema = (configFields, config, options = {}, additionalBaseFields = {}) => { + const fields = { ...baseFields, ...additionalBaseFields }; const flexiblefields = []; configFields.forEach((field) => { diff --git a/src/uploads/baseImageFields.js b/src/uploads/baseImageFields.js new file mode 100644 index 0000000000..bed4fe6b53 --- /dev/null +++ b/src/uploads/baseImageFields.js @@ -0,0 +1,8 @@ +export default [{ + sizes: [{ + name: { type: String }, + height: { type: Number }, + width: { type: Number }, + _id: false, + }], +}]; diff --git a/src/uploads/baseUploadFields.js b/src/uploads/baseUploadFields.js new file mode 100644 index 0000000000..a7124f294b --- /dev/null +++ b/src/uploads/baseUploadFields.js @@ -0,0 +1,9 @@ +export default [ + { + name: 'filename', + label: 'Filename', + type: 'input', + unique: true, + required: true, + }, +]; diff --git a/src/uploads/images/config.js b/src/uploads/images/config.js deleted file mode 100644 index 2c4c2b4085..0000000000 --- a/src/uploads/images/config.js +++ /dev/null @@ -1,19 +0,0 @@ -export default { - fields: [ - { - name: 'sizes', - type: 'repeater', - id: false, - fields: [ - { - name: 'height', - type: 'number', - }, - { - name: 'width', - type: 'number', - }, - ], - } - ] -} diff --git a/src/uploads/images/model.js b/src/uploads/images/model.js deleted file mode 100644 index a5e6e8b490..0000000000 --- a/src/uploads/images/model.js +++ /dev/null @@ -1,21 +0,0 @@ -import mongoose from 'mongoose'; -import localizationPlugin from '../../localization/plugin'; - -const imageUploadModelLoader = (Upload, config) => { - const ImageSchema = new mongoose.Schema( - { - sizes: [{ - name: { type: String }, - height: { type: Number }, - width: { type: Number }, - _id: false, - }], - }, - ); - - ImageSchema.plugin(localizationPlugin, config.localization); - - return Upload.discriminator('image', ImageSchema); -}; - -export default imageUploadModelLoader; diff --git a/src/uploads/model.js b/src/uploads/model.js deleted file mode 100644 index 9dad013b8e..0000000000 --- a/src/uploads/model.js +++ /dev/null @@ -1,21 +0,0 @@ -import mongoose from 'mongoose'; -import paginate from 'mongoose-paginate-v2'; -import buildQueryPlugin from '../mongoose/buildQuery'; -import localizationPlugin from '../localization/plugin'; - -const uploadModelLoader = (config) => { - const UploadSchema = new mongoose.Schema({ - filename: { type: String }, - }, { - timestamps: true, - discriminatorKey: 'type', - }); - - UploadSchema.plugin(paginate); - UploadSchema.plugin(buildQueryPlugin); - UploadSchema.plugin(localizationPlugin, config.localization); - - return mongoose.model('Upload', UploadSchema); -}; - -export default uploadModelLoader; diff --git a/src/uploads/routes.js b/src/uploads/routes.js index b1e971b6d8..f822d95b88 100644 --- a/src/uploads/routes.js +++ b/src/uploads/routes.js @@ -2,21 +2,13 @@ import passport from 'passport'; import fileUpload from 'express-fileupload'; import { upload, update } from './requestHandlers'; import uploadMiddleware from './middleware'; -import uploadModelLoader from './model'; -import imageUploadModelLoader from './images/model'; import setModelLocaleMiddleware from '../localization/setModelLocale'; -const uploadRoutes = (config, app, router) => { - const Upload = uploadModelLoader(config); - const UploadModels = { - default: Upload, - image: imageUploadModelLoader(Upload, config), - }; - +const uploadRoutes = (models, config, router) => { router.all('/upload*', fileUpload(), passport.authenticate('jwt', { session: false }), - uploadMiddleware(config, UploadModels), + uploadMiddleware(config, models), setModelLocaleMiddleware()); router.route('/upload')