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