UNPKG

graphql-auto-generating-cms

Version:

This module will use your existed graphQL schema to generate simple for use CMS in a couple minutes!

374 lines (370 loc) 11.7 kB
import formidable from 'formidable'; import fs from 'fs-extra'; import util from 'util'; import { parse } from 'graphql'; const applyRules = function funcApplyRules(args) { const { shape, rules } = args; const response = rules ? { ...rules } : { ...shape }; if (rules) { Object.keys(shape).forEach((key) => { if (typeof (shape[key]) === 'object') { if (!response[key]) { response[key] = shape[key]; } else { response[key] = applyRules({ shape: shape[key], rules: response[key] }); } } else if (!response[key] && typeof (response[key]) !== 'boolean') { response[key] = shape[key]; } }); } Object.keys(response).forEach((key) => { if (key === 'resolvers') { Object.keys(response[key]).forEach((resolver) => { if (typeof (response[key][resolver].allowed) === 'boolean' && !response[key][resolver].allowed) { delete response[key][resolver]; } }); } }); return response; }; const getResolverName = function funcGetResolverName(args) { const { typeName, method, rules } = args; let resolverName = ''; if ( rules && rules[typeName] && rules[typeName].resolvers && rules[typeName].resolvers[method] && rules[typeName].resolvers[method].resolver ) { resolverName = rules[typeName].resolvers[method].resolver; } else { resolverName = `${typeName}_${method}`; } return resolverName; }; const findResolverArgs = function funcFindResolverArgs(args) { const { typeName, method, fields, rules } = args; const respond = {}; let tmpObj = {}; if ( rules && rules[typeName] && rules[typeName].resolvers && rules[typeName].resolvers[method] && rules[typeName].resolvers[method].resolver ) { tmpObj = fields.find(obj => obj.name.value === rules[typeName].resolvers[method].resolver); } else { tmpObj = fields.find(obj => obj.name.value === `${typeName}_${method}`); } if (tmpObj && tmpObj.arguments) { tmpObj.arguments.forEach((argObj) => { respond[argObj.name.value] = argObj.type.kind === 'NonNullType' ? `${argObj.type.type.name.value}!` : argObj.type.name.value; }); } return respond; }; const checkMethodPermission = function funcCheckMethodPermission(args) { const { typeName, method, mutations, rules } = args; let hasMethod = !!mutations.fields.find(obj => obj.name.value === `${typeName}_${method}`); if ( rules && hasMethod && rules[typeName] && rules[typeName].resolvers && rules[typeName].resolvers[method] && (rules[typeName].resolvers[method].allowed || typeof (rules[typeName].resolvers[method].allowed) === 'boolean') ) { hasMethod = rules[typeName].resolvers[method].allowed; } return hasMethod; }; const getListHeader = function funcGetListHeader(args) { const { shape } = args; const response = { ...shape }; let id; let title; Object.keys(response).forEach((key) => { id = Object.keys(response[key].fields).find(i => i === 'id' || i === '_id'); title = Object.keys(response[key].fields)[1]; response[key].listHeader = { id: [id], title: [title] }; }); return response; }; const getTypeListData = function funcGetTypeListData(args) { const { schema, typeName, rules } = args; const Queries = schema.definitions.find(obj => obj.name.value === 'Query'); const Mutations = schema.definitions.find(obj => obj.name.value === 'Mutation'); return { propTypeName: typeName, label: false, resolvers: { find: { resolver: getResolverName({ rules, typeName, method: 'find' }), args: findResolverArgs({ rules, typeName, method: 'find', fields: Queries.fields }), }, create: { resolver: getResolverName({ rules, typeName, method: 'create' }), args: findResolverArgs({ rules, typeName, method: 'create', fields: Mutations.fields }), }, }, }; }; const hasNestedFields = function funcHasNestedFields(propType) { switch (propType.toLowerCase()) { case 'boolean': return false; case 'string': return false; case 'int': return false; case 'float': return false; case 'number': return false; default: return true; } }; const isListOfType = function funcIsListOfType(typeValue) { switch (typeValue) { case 'Int': return false; case 'Float': return false; case 'Boolean': return false; case 'String': return false; default: return true; } }; const resolveInputType = function funcResolveInputType(scalarType) { switch (scalarType || scalarType.slice(0, -1)) { case 'Int': return 'number'; case 'Float': return 'number'; case 'Boolean': return 'checkbox'; case 'String': return 'text'; default: return 'text'; } }; const resolveInputControl = function funcResolveInputControl(scalarType) { switch (scalarType || scalarType.slice(0, -1)) { case 'Int': return 'input'; case 'Float': return 'input'; case 'Boolean': return 'input'; case 'String': return 'input'; default: return 'input'; } }; const getFields = function funcGetFields(args) { const { schema, typeName, rules } = args; const typeObject = schema.definitions.find(obj => obj.name.value === typeName); const result = {}; typeObject.fields.forEach((prop) => { if (prop && prop.type && prop.type.kind !== 'ListType') { if ( prop.name && prop.name.value && prop.type.name && prop.type.name.value && prop.name.value !== 'Mutation' && prop.name.value !== 'Query' ) { const has = hasNestedFields(prop.type.name.value); result[prop.name.value] = { label: prop.name.value, fieldType: prop.type.name.value, inputType: has ? 'String' : resolveInputType(prop.type.name.value), inputControl: resolveInputControl(prop.type.name.value), disabled: false, exclude: false, list: false, nestedFields: has ? getFields({ schema, rules, typeName: prop.type.name.value }) : false, }; } } else if ( prop && prop.type && prop.type.type && prop.type.type.name && prop.type.type.name.value && isListOfType(prop.type.type.name.value) && prop.name.value !== 'Mutation' && prop.name.value !== 'Query' ) { result[prop.name.value] = { label: prop.name.value, fieldType: prop.type.type.name.value, inputType: false, inputControl: 'selection', disabled: false, exclude: false, list: getTypeListData({ schema, rules, typeName: prop.type.type.name.value }), nestedFields: getFields({ schema, typeName: prop.type.type.name.value }), }; } }); return result; }; const fixPath = function funcFixPath(string) { let response = ''; if (string.slice(0, 1) === '/' || string.slice(0, 1) === '.') { response = string; } else { response = `/${string}`; } if (response.slice(-1) === '/') { response = response.slice(0, -1); } return response; }; const graphqlCMS = function funcGraphqlCMS(args) { const { schema, rules, exclude, uploadRoot } = args; const Mutations = schema.definitions.find(obj => obj.name.value === 'Mutation'); const Queries = schema.definitions.find(obj => obj.name.value === 'Query'); let shape = {}; Queries.fields.forEach((method) => { const methodTypeName = method.type && method.type.type && method.type.type.name && method.type.type.name.value ? method.type.type.name.value : false; if ( methodTypeName && Mutations.fields.find(obj => obj.name.value.split('_')[0] === methodTypeName) && (!exclude || !exclude.find(type => type === methodTypeName)) ) { shape[methodTypeName] = { uploadRoot, label: methodTypeName, listHeader: false, resolvers: { find: { resolver: getResolverName({ rules, typeName: methodTypeName, method: 'find' }), args: findResolverArgs({ rules, typeName: methodTypeName, method: 'find', fields: Queries.fields, }), allowed: true, }, create: { resolver: getResolverName({ rules, typeName: methodTypeName, method: 'create' }), args: findResolverArgs({ rules, typeName: methodTypeName, method: 'create', fields: Mutations.fields, }), allowed: checkMethodPermission({ rules, typeName: methodTypeName, method: 'create', mutations: Mutations, }), }, update: { resolver: getResolverName({ rules, typeName: methodTypeName, method: 'update' }), args: findResolverArgs({ rules, typeName: methodTypeName, method: 'update', fields: Mutations.fields, }), allowed: checkMethodPermission({ rules, typeName: methodTypeName, method: 'update', mutations: Mutations, }), }, remove: { resolver: getResolverName({ rules, typeName: methodTypeName, method: 'remove' }), args: findResolverArgs({ rules, typeName: methodTypeName, method: 'remove', fields: Mutations.fields, }), allowed: checkMethodPermission({ rules, typeName: methodTypeName, method: 'remove', mutations: Mutations, }), }, }, fields: getFields({ schema, rules, typeName: methodTypeName }), }; } }); shape = getListHeader({ shape }); return shape; }; const fileUploadingMiddleware = function funcFileUploadingMiddleware(req, res, uploadRoot) { const form = new formidable.IncomingForm(); form.parse(req, (err, fields, files) => { res.end(util.inspect({ fields, files })); }); form.on('error', (err) => { if (err) throw new Error(err); }); form.on('end', function () { const tempPath = this.openedFiles[0].path; const fileName = this.openedFiles[0].name.split(',')[0]; const folderPath = fixPath(this.openedFiles[0].name.split(',')[1]); fs.copy(tempPath, `${uploadRoot}${folderPath}/${fileName}`, (err) => { if (err) throw new Error(err); }); }); }; // export declared only for test cases export { applyRules, getResolverName, findResolverArgs, checkMethodPermission, getListHeader, getTypeListData, getFields, resolveInputType, resolveInputControl, hasNestedFields, isListOfType, fixPath, graphqlCMS, fileUploadingMiddleware, }; export default function (config) { const schema = config.schema ? parse(config.schema) : false; if (!schema) { throw new Error('you have to provide your PRINTED schema in config object "{schema: myPrintedSchema}"'); } const rules = config.rules ? config.rules : false; const exclude = config.exclude ? config.exclude : false; const uploadRoot = config.uploadRoot ? fixPath(config.uploadRoot) : false; return (req, res) => { if (req.method.toLowerCase() === 'get') { res.send(applyRules({ rules, shape: graphqlCMS({ schema, rules, exclude, uploadRoot }) })); } else if (req.method.toLowerCase() === 'post') { fileUploadingMiddleware.call(this, req, res, uploadRoot); } }; }