diff --git a/.babelrc b/.babelrc index 3f5e85b017..318344bd28 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,15 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react"], - "plugins": ["@babel/plugin-proposal-class-properties"] + "presets": [ + "@babel/preset-env", + "@babel/preset-react" + ], + "plugins": [ + "@babel/plugin-proposal-class-properties", + [ + "@babel/plugin-transform-runtime", + { + "regenerator": true + } + ] + ] } diff --git a/demo/Page/Page.resolvers.js b/demo/Page/Page.resolvers.js new file mode 100644 index 0000000000..3f887c0ad5 --- /dev/null +++ b/demo/Page/Page.resolvers.js @@ -0,0 +1,21 @@ +import Page from './Page.model'; +import { modelById } from '../../src/resolvers'; + +export default { + Query: { + page: async (parent, args) => { + + const query = { + Model: Page, + id: args.id, + locale: args.locale, + fallback: args['fallbackLocale'] + } + + return await modelById(query); + }, + pages: async (parent, args) => { + + } + } +} diff --git a/demo/Page/Page.types.js b/demo/Page/Page.types.js new file mode 100644 index 0000000000..9881f9bc6b --- /dev/null +++ b/demo/Page/Page.types.js @@ -0,0 +1,20 @@ +export default ` + type Page { + id: String + title: String + content: String + metaTitle: String + metaDesc: String + createdAt: String + updatedAt: String + } + + type Query { + page(id: String!, locale: String): Page + pages: [Page] + } + + type Mutation { + addPage(title: String, content: String): Page + } +`; diff --git a/demo/app.js b/demo/app.js index 4f4cfaa1bf..59017c9759 100644 --- a/demo/app.js +++ b/demo/app.js @@ -1,11 +1,12 @@ import express from 'express'; import payload from '../src'; + import User from './User/User.model'; import payloadConfig from './payload.config'; - import { authRoutes } from './Auth/Auth.routes'; import { userRoutes } from './User/User.routes'; import { pageRoutes } from './Page/Page.routes'; +import schema from '../demo/graphql'; const router = express.Router({}); // eslint-disable-line new-cap @@ -16,6 +17,7 @@ payload.init({ app: app, user: User, router: router, + graphQLSchema: schema, cors: ['http://localhost:8080', 'http://localhost:8081'] }); diff --git a/demo/graphql/index.js b/demo/graphql/index.js new file mode 100644 index 0000000000..ea34ebc30d --- /dev/null +++ b/demo/graphql/index.js @@ -0,0 +1,7 @@ +import { makeExecutableSchema } from 'graphql-tools'; +import typeDefs from './types'; +import resolvers from './resolvers'; + +const schema = makeExecutableSchema({ typeDefs, resolvers }); + +export default schema; diff --git a/demo/graphql/resolvers.js b/demo/graphql/resolvers.js new file mode 100644 index 0000000000..294e374490 --- /dev/null +++ b/demo/graphql/resolvers.js @@ -0,0 +1,7 @@ +import { mergeResolvers } from 'merge-graphql-schemas'; + +import Page from '../Page/Page.resolvers'; + +const resolvers = [Page]; + +export default mergeResolvers(resolvers); diff --git a/demo/graphql/types.js b/demo/graphql/types.js new file mode 100644 index 0000000000..375296eaae --- /dev/null +++ b/demo/graphql/types.js @@ -0,0 +1,7 @@ +import { mergeTypes } from 'merge-graphql-schemas'; + +import Page from '../Page/Page.types'; + +const types = [Page]; + +export default mergeTypes(types, { all: true }); diff --git a/demo/init.js b/demo/init.js index b703b647a6..2dcd07aabd 100644 --- a/demo/init.js +++ b/demo/init.js @@ -1,5 +1,5 @@ require('@babel/register')({ - ignore: [ /(node_modules)/ ] + ignore: [/(node_modules)/] }); require('./app'); diff --git a/demo/payload.config.json b/demo/payload.config.json index 42d4425f10..6de4253283 100644 --- a/demo/payload.config.json +++ b/demo/payload.config.json @@ -44,5 +44,9 @@ "width": 16, "height": 16 } - ] + ], + "graphQL": { + "path": "/graphql", + "graphiql": true + } } diff --git a/package.json b/package.json index a94762a510..f8dffe17b8 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,17 @@ "body-parser": "^1.18.3", "dotenv": "^6.0.0", "express-fileupload": "^1.1.1-alpha.1", + "express-graphql": "^0.7.1", "express-validation": "^1.0.2", + "file-loader": "^1.1.11", + "graphql": "^14.2.1", + "graphql-tools": "^4.0.4", "http-status": "^1.2.0", "ignore-styles": "^5.0.1", "image-size": "^0.7.1", "joi": "^13.6.0", "jsonwebtoken": "^8.4.0", + "merge-graphql-schemas": "^1.5.8", "method-override": "^3.0.0", "mkdirp": "^0.5.1", "passport": "^0.4.0", @@ -53,9 +58,11 @@ "devDependencies": { "@babel/core": "^7.2.0", "@babel/plugin-proposal-class-properties": "^7.2.1", + "@babel/plugin-transform-runtime": "^7.4.3", "@babel/preset-env": "^7.2.0", "@babel/preset-react": "^7.0.0", "@babel/register": "^7.0.0", + "@babel/runtime": "^7.4.3", "autoprefixer": "^9.0.1", "axios": "^0.18.0", "babel-core": "^7.0.0-bridge.0", diff --git a/src/index.js b/src/index.js index 5b95c6db21..5c41b62508 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ import fileUpload from 'express-fileupload'; import mediaRoutes from './routes/Media.routes'; import config from '../demo/payload.config'; import locale from './middleware/locale'; +import expressGraphQL from 'express-graphql'; module.exports = { init: options => { @@ -54,7 +55,17 @@ module.exports = { options.app.use(methodOverride('X-HTTP-Method-Override')); options.app.use(express.urlencoded({ extended: true })); options.app.use(bodyParser.urlencoded({ extended: true })); - options.app.use(locale(config.localization)); + options.app.use(locale(options.config.localization)); options.app.use(options.router); + + if (options.config.graphQL && options.graphQLSchema) { + options.app.use( + options.config.graphQL.path, + expressGraphQL({ + schema: options.graphQLSchema, + graphiql: options.config.graphQL.graphiql, + }) + ); + } } }; diff --git a/src/requestHandlers/findOne.js b/src/requestHandlers/findOne.js index 2f4b340a11..af84692271 100644 --- a/src/requestHandlers/findOne.js +++ b/src/requestHandlers/findOne.js @@ -1,20 +1,18 @@ import httpStatus from 'http-status'; +import { modelById } from '../resolvers'; const findOne = (req, res) => { - req.model.findOne({ _id: req.params._id }, (err, doc) => { - if (err) - return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: err }); - if (!doc) - return res.status(httpStatus.NOT_FOUND).send('Not Found'); + const query = { + Model: req.model, + id: req.params._id, + locale: req.locale, + fallback: req.query['fallback-locale'] + } - if (req.locale) { - doc.setLocale(req.locale, req.query['fallback-locale']); - return res.json(doc.toJSON({ virtuals: true })); - } - - return res.json(doc); - }); + modelById(query) + .then(doc => res.json(doc)) + .catch(err => res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: err })); }; export default findOne; diff --git a/src/resolvers/index.js b/src/resolvers/index.js new file mode 100644 index 0000000000..95895a34be --- /dev/null +++ b/src/resolvers/index.js @@ -0,0 +1,5 @@ +import modelById from './modelById'; + +export { + modelById +} diff --git a/src/resolvers/modelById.js b/src/resolvers/modelById.js new file mode 100644 index 0000000000..c288192fc7 --- /dev/null +++ b/src/resolvers/modelById.js @@ -0,0 +1,23 @@ +const modelById = query => { + + return new Promise((resolve, reject) => { + query.Model.findOne({ _id: query.id }, (err, doc) => { + + if (err || !doc) { + return reject({ message: 'not found' }) + } + + let result = doc; + + if (query.locale) { + doc.setLocale(query.locale, query.fallback); + const json = doc.toJSON({ virtuals: true }); + result = json; + } + + resolve(result); + }) + }) +}; + +export default modelById;