feat: GraphQL version collection resolvers
This commit is contained in:
@@ -11,10 +11,11 @@ import formatName from '../../graphql/utilities/formatName';
|
||||
import buildPaginatedListType from '../../graphql/schema/buildPaginatedListType';
|
||||
import { BaseFields } from './types';
|
||||
import { getCollectionIDType } from '../../graphql/schema/buildMutationInputType';
|
||||
import buildVersionWhereInputType from '../../graphql/schema/buildVersionWhereInputType';
|
||||
|
||||
function registerCollections(): void {
|
||||
const {
|
||||
// TODO: findVersions, findVersionByID, publishVersion
|
||||
findVersions, findVersionByID, publishVersion,
|
||||
create, find, findByID, deleteResolver, update,
|
||||
} = this.graphQL.resolvers.collections;
|
||||
|
||||
@@ -180,6 +181,35 @@ function registerCollections(): void {
|
||||
resolve: deleteResolver(collection),
|
||||
};
|
||||
|
||||
if (collection.config.versions) {
|
||||
collection.graphQL.versionType = this.buildVersionType(collection.graphQL.type);
|
||||
this.Query.fields[`version${formatName(singularLabel)}`] = {
|
||||
type: collection.graphQL.versionType,
|
||||
args: {
|
||||
id: { type: GraphQLString },
|
||||
},
|
||||
resolve: findVersionByID(collection),
|
||||
};
|
||||
this.Query.fields[`versions${pluralLabel}`] = {
|
||||
type: buildPaginatedListType(`versions${formatName(pluralLabel)}`, collection.graphQL.versionType),
|
||||
args: {
|
||||
where: { type: buildVersionWhereInputType(singularLabel, collection.config) },
|
||||
autosave: { type: GraphQLBoolean },
|
||||
page: { type: GraphQLInt },
|
||||
limit: { type: GraphQLInt },
|
||||
sort: { type: GraphQLString },
|
||||
},
|
||||
resolve: findVersions(collection),
|
||||
};
|
||||
this.Mutation.fields[`restoreVersion${formatName(singularLabel)}`] = {
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
args: {
|
||||
id: { type: GraphQLString },
|
||||
},
|
||||
resolve: publishVersion(collection),
|
||||
};
|
||||
}
|
||||
|
||||
if (collection.config.auth) {
|
||||
collection.graphQL.JWT = this.buildObjectType(
|
||||
formatName(`${slug}JWT`),
|
||||
|
||||
35
src/collections/graphql/resolvers/publishVersion.ts
Normal file
35
src/collections/graphql/resolvers/publishVersion.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { Response } from 'express';
|
||||
import { Collection } from '../../config/types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
|
||||
export type Resolver = (
|
||||
_: unknown,
|
||||
args: {
|
||||
locale?: string
|
||||
fallbackLocale?: string
|
||||
},
|
||||
context: {
|
||||
req: PayloadRequest,
|
||||
res: Response
|
||||
}
|
||||
) => Promise<Document>
|
||||
|
||||
export default function publishVersion(collection: Collection): Resolver {
|
||||
async function resolver(_, args, context) {
|
||||
if (args.locale) context.req.locale = args.locale;
|
||||
if (args.fallbackLocale) context.req.fallbackLocale = args.fallbackLocale;
|
||||
|
||||
const options = {
|
||||
collection,
|
||||
id: args.id,
|
||||
req: context.req,
|
||||
};
|
||||
|
||||
await this.operations.collections.publishVersion(options);
|
||||
return true;
|
||||
}
|
||||
|
||||
const findVersionByIDResolver = resolver.bind(this);
|
||||
return findVersionByIDResolver;
|
||||
}
|
||||
@@ -185,7 +185,6 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
}
|
||||
|
||||
let result: Document = doc.toJSON({ virtuals: true });
|
||||
// TODO: default status to 'draft';
|
||||
const verificationToken = result._verificationToken;
|
||||
|
||||
// custom id type reset
|
||||
|
||||
@@ -16,6 +16,7 @@ import update from '../collections/graphql/resolvers/update';
|
||||
import deleteResolver from '../collections/graphql/resolvers/delete';
|
||||
import findVersions from '../collections/graphql/resolvers/findVersions';
|
||||
import findVersionByID from '../collections/graphql/resolvers/findVersionByID';
|
||||
import publishVersion from '../collections/graphql/resolvers/publishVersion';
|
||||
|
||||
import findOne from '../globals/graphql/resolvers/findOne';
|
||||
import globalUpdate from '../globals/graphql/resolvers/update';
|
||||
@@ -29,6 +30,7 @@ export type GraphQLResolvers = {
|
||||
findVersions: typeof findVersions,
|
||||
findByID: typeof findByID,
|
||||
findVersionByID: typeof findVersionByID,
|
||||
publishVersion: typeof publishVersion,
|
||||
update: typeof update,
|
||||
deleteResolver: typeof deleteResolver,
|
||||
auth: {
|
||||
@@ -59,6 +61,7 @@ function bindResolvers(ctx: Payload): void {
|
||||
findVersions: findVersions.bind(ctx),
|
||||
findByID: findByID.bind(ctx),
|
||||
findVersionByID: findVersionByID.bind(ctx),
|
||||
publishVersion: publishVersion.bind(ctx),
|
||||
update: update.bind(ctx),
|
||||
deleteResolver: deleteResolver.bind(ctx),
|
||||
auth: {
|
||||
|
||||
@@ -13,7 +13,8 @@ import initCollections from '../collections/graphql/init';
|
||||
import initGlobals from '../globals/graphql/init';
|
||||
import initPreferences from '../preferences/graphql/init';
|
||||
import { GraphQLResolvers } from './bindResolvers';
|
||||
import buildWhereInputType from './schema/buildWhereInputType';
|
||||
import buildVersionType from './schema/buildVersionType';
|
||||
import { buildWhereInputType } from './schema/buildWhereInputType';
|
||||
import { SanitizedConfig } from '../config/types';
|
||||
|
||||
type GraphQLTypes = {
|
||||
@@ -46,6 +47,8 @@ class InitializeGraphQL {
|
||||
|
||||
buildPoliciesType: typeof buildPoliciesType;
|
||||
|
||||
buildVersionType: typeof buildVersionType;
|
||||
|
||||
initCollections: typeof initCollections;
|
||||
|
||||
initGlobals: typeof initGlobals;
|
||||
@@ -90,6 +93,7 @@ class InitializeGraphQL {
|
||||
this.buildWhereInputType = buildWhereInputType;
|
||||
this.buildObjectType = buildObjectType.bind(this);
|
||||
this.buildPoliciesType = buildPoliciesType.bind(this);
|
||||
this.buildVersionType = buildVersionType.bind(this);
|
||||
this.initCollections = initCollections.bind(this);
|
||||
this.initGlobals = initGlobals.bind(this);
|
||||
this.initPreferences = initPreferences.bind(this);
|
||||
|
||||
34
src/graphql/schema/buildInputObject.ts
Normal file
34
src/graphql/schema/buildInputObject.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
GraphQLInputFieldConfigMap,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInt,
|
||||
GraphQLList,
|
||||
GraphQLString,
|
||||
Thunk,
|
||||
} from 'graphql';
|
||||
|
||||
const buildInputObject = (name: string, fieldTypes: Thunk<GraphQLInputFieldConfigMap>): GraphQLInputObjectType => {
|
||||
return new GraphQLInputObjectType({
|
||||
name: `${name}_where`,
|
||||
fields: {
|
||||
...fieldTypes,
|
||||
OR: {
|
||||
type: new GraphQLList(new GraphQLInputObjectType({
|
||||
name: `${name}_where_or`,
|
||||
fields: fieldTypes,
|
||||
})),
|
||||
},
|
||||
AND: {
|
||||
type: new GraphQLList(new GraphQLInputObjectType({
|
||||
name: `${name}_where_and`,
|
||||
fields: fieldTypes,
|
||||
})),
|
||||
},
|
||||
page: { type: GraphQLInt },
|
||||
limit: { type: GraphQLInt },
|
||||
sort: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default buildInputObject;
|
||||
19
src/graphql/schema/buildVersionType.ts
Normal file
19
src/graphql/schema/buildVersionType.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { GraphQLBoolean, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql';
|
||||
import { DateTimeResolver } from 'graphql-scalars';
|
||||
import formatName from '../utilities/formatName';
|
||||
|
||||
const buildVersionType = (type: GraphQLObjectType): GraphQLObjectType => {
|
||||
return new GraphQLObjectType({
|
||||
name: formatName(`${type.name}Version`),
|
||||
fields: {
|
||||
id: { type: GraphQLString },
|
||||
parent: { type: GraphQLString },
|
||||
autosave: { type: GraphQLBoolean },
|
||||
version: { type },
|
||||
updatedAt: { type: new GraphQLNonNull(DateTimeResolver) },
|
||||
createdAt: { type: new GraphQLNonNull(DateTimeResolver) },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default buildVersionType;
|
||||
47
src/graphql/schema/buildVersionWhereInputType.ts
Normal file
47
src/graphql/schema/buildVersionWhereInputType.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
GraphQLBoolean,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
import { DateTimeResolver } from 'graphql-scalars';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import formatName from '../utilities/formatName';
|
||||
import withOperators from './withOperators';
|
||||
import { FieldAffectingData } from '../../fields/config/types';
|
||||
import buildInputObject from './buildInputObject';
|
||||
import { operators } from './operators';
|
||||
import { SanitizedCollectionConfig } from '../../collections/config/types';
|
||||
import { recursivelyBuildNestedPaths } from './recursivelyBuildNestedPaths';
|
||||
|
||||
const buildVersionWhereInputType = (singularLabel: string, parentCollection: SanitizedCollectionConfig): GraphQLInputObjectType => {
|
||||
const name = `version${formatName(singularLabel)}`;
|
||||
const fieldTypes = {
|
||||
id: { type: GraphQLString },
|
||||
autosave: { type: GraphQLBoolean },
|
||||
// TODO: test with custom id field types, may need to support number
|
||||
updatedAt: { type: DateTimeResolver },
|
||||
createdAt: { type: DateTimeResolver },
|
||||
parent: {
|
||||
type: withOperators(
|
||||
{ name: 'parent' } as FieldAffectingData,
|
||||
GraphQLJSON,
|
||||
name,
|
||||
[...operators.equality, ...operators.contains],
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
const versionFields = recursivelyBuildNestedPaths(name, {
|
||||
name: 'version',
|
||||
type: 'group',
|
||||
fields: parentCollection.fields,
|
||||
});
|
||||
|
||||
versionFields.forEach((versionField) => {
|
||||
fieldTypes[versionField.key] = versionField.type;
|
||||
});
|
||||
|
||||
return buildInputObject(name, fieldTypes);
|
||||
};
|
||||
|
||||
export default buildVersionWhereInputType;
|
||||
@@ -1,46 +1,22 @@
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
/* eslint-disable no-use-before-define */
|
||||
import {
|
||||
GraphQLBoolean,
|
||||
GraphQLEnumType,
|
||||
GraphQLFloat,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInt,
|
||||
GraphQLList,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
|
||||
import { DateTimeResolver, EmailAddressResolver } from 'graphql-scalars';
|
||||
import {
|
||||
optionIsObject,
|
||||
ArrayField,
|
||||
CheckboxField,
|
||||
CodeField,
|
||||
DateField,
|
||||
EmailField,
|
||||
Field,
|
||||
FieldWithSubFields,
|
||||
GroupField,
|
||||
NumberField,
|
||||
RadioField,
|
||||
RelationshipField,
|
||||
RichTextField,
|
||||
RowField,
|
||||
SelectField,
|
||||
TextareaField,
|
||||
TextField,
|
||||
UploadField,
|
||||
PointField,
|
||||
FieldAffectingData,
|
||||
fieldAffectsData,
|
||||
fieldHasSubFields,
|
||||
fieldIsPresentationalOnly,
|
||||
} from '../../fields/config/types';
|
||||
import formatName from '../utilities/formatName';
|
||||
import combineParentName from '../utilities/combineParentName';
|
||||
import withOperators from './withOperators';
|
||||
import { operators } from './operators';
|
||||
import buildInputObject from './buildInputObject';
|
||||
import { fieldToSchemaMap } from './fieldToSchemaMap';
|
||||
|
||||
// buildWhereInputType is similar to buildObjectType and operates
|
||||
// on a field basis with a few distinct differences.
|
||||
@@ -49,284 +25,13 @@ import withOperators from './withOperators';
|
||||
// 2. Relationships, groups, repeaters and flex content are not
|
||||
// directly searchable. Instead, we need to build a chained pathname
|
||||
// using dot notation so Mongo can properly search nested paths.
|
||||
const buildWhereInputType = (name: string, fields: Field[], parentName: string): GraphQLInputObjectType => {
|
||||
export const buildWhereInputType = (name: string, fields: Field[], parentName: string): GraphQLInputObjectType => {
|
||||
// This is the function that builds nested paths for all
|
||||
// field types with nested paths.
|
||||
const recursivelyBuildNestedPaths = (field: FieldWithSubFields & FieldAffectingData) => {
|
||||
const nestedPaths = field.fields.reduce((nestedFields, nestedField) => {
|
||||
if (!fieldIsPresentationalOnly(nestedField)) {
|
||||
const getFieldSchema = fieldToSchemaMap[nestedField.type];
|
||||
const nestedFieldName = fieldAffectsData(nestedField) ? `${field.name}__${nestedField.name}` : undefined;
|
||||
|
||||
if (getFieldSchema) {
|
||||
const fieldSchema = getFieldSchema({
|
||||
...nestedField,
|
||||
name: nestedFieldName,
|
||||
});
|
||||
|
||||
if (Array.isArray(fieldSchema)) {
|
||||
return [
|
||||
...nestedFields,
|
||||
...fieldSchema,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
...nestedFields,
|
||||
{
|
||||
key: nestedFieldName,
|
||||
type: fieldSchema,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return nestedFields;
|
||||
}, []);
|
||||
|
||||
return nestedPaths;
|
||||
};
|
||||
|
||||
const operators = {
|
||||
equality: ['equals', 'not_equals'],
|
||||
contains: ['in', 'not_in', 'all'],
|
||||
comparison: ['greater_than_equal', 'greater_than', 'less_than_equal', 'less_than'],
|
||||
geo: ['near'],
|
||||
};
|
||||
|
||||
const fieldToSchemaMap = {
|
||||
number: (field: NumberField) => {
|
||||
const type = GraphQLFloat;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, ...operators.comparison],
|
||||
),
|
||||
};
|
||||
},
|
||||
text: (field: TextField) => {
|
||||
const type = GraphQLString;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
email: (field: EmailField) => {
|
||||
const type = EmailAddressResolver;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
textarea: (field: TextareaField) => {
|
||||
const type = GraphQLString;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
richText: (field: RichTextField) => {
|
||||
const type = GraphQLJSON;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
code: (field: CodeField) => {
|
||||
const type = GraphQLString;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
radio: (field: RadioField) => ({
|
||||
type: withOperators(
|
||||
field,
|
||||
new GraphQLEnumType({
|
||||
name: `${combineParentName(parentName, field.name)}_Input`,
|
||||
values: field.options.reduce((values, option) => {
|
||||
if (optionIsObject(option)) {
|
||||
return {
|
||||
...values,
|
||||
[formatName(option.value)]: {
|
||||
value: option.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...values,
|
||||
[formatName(option)]: {
|
||||
value: option,
|
||||
},
|
||||
};
|
||||
}, {}),
|
||||
}),
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
}),
|
||||
date: (field: DateField) => {
|
||||
const type = DateTimeResolver;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, ...operators.comparison, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
point: (field: PointField) => {
|
||||
const type = GraphQLList(GraphQLFloat);
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, ...operators.comparison, ...operators.geo],
|
||||
),
|
||||
};
|
||||
},
|
||||
relationship: (field: RelationshipField) => {
|
||||
let type = withOperators(
|
||||
field,
|
||||
GraphQLString,
|
||||
parentName,
|
||||
[...operators.equality, ...operators.contains],
|
||||
);
|
||||
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
type = new GraphQLInputObjectType({
|
||||
name: `${combineParentName(parentName, field.name)}_Relation`,
|
||||
fields: {
|
||||
relationTo: {
|
||||
type: new GraphQLEnumType({
|
||||
name: `${combineParentName(parentName, field.name)}_Relation_RelationTo`,
|
||||
values: field.relationTo.reduce((values, relation) => ({
|
||||
...values,
|
||||
[formatName(relation)]: {
|
||||
value: relation,
|
||||
},
|
||||
}), {}),
|
||||
}),
|
||||
},
|
||||
value: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (field.hasMany) {
|
||||
return {
|
||||
type: new GraphQLList(type),
|
||||
};
|
||||
}
|
||||
|
||||
return { type };
|
||||
},
|
||||
upload: (field: UploadField) => ({
|
||||
type: withOperators(
|
||||
field,
|
||||
GraphQLString,
|
||||
parentName,
|
||||
[...operators.equality],
|
||||
),
|
||||
}),
|
||||
checkbox: (field: CheckboxField) => ({
|
||||
type: withOperators(
|
||||
field,
|
||||
GraphQLBoolean,
|
||||
parentName,
|
||||
[...operators.equality],
|
||||
),
|
||||
}),
|
||||
select: (field: SelectField) => ({
|
||||
type: withOperators(
|
||||
field,
|
||||
new GraphQLEnumType({
|
||||
name: `${combineParentName(parentName, field.name)}_Input`,
|
||||
values: field.options.reduce((values, option) => {
|
||||
if (typeof option === 'object' && option.value) {
|
||||
return {
|
||||
...values,
|
||||
[formatName(option.value)]: {
|
||||
value: option.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof option === 'string') {
|
||||
return {
|
||||
...values,
|
||||
[option]: {
|
||||
value: option,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return values;
|
||||
}, {}),
|
||||
}),
|
||||
parentName,
|
||||
[...operators.equality, ...operators.contains],
|
||||
),
|
||||
}),
|
||||
array: (field: ArrayField) => recursivelyBuildNestedPaths(field),
|
||||
group: (field: GroupField) => recursivelyBuildNestedPaths(field),
|
||||
row: (field: RowField) => field.fields.reduce((rowSchema, rowField) => {
|
||||
const getFieldSchema = fieldToSchemaMap[rowField.type];
|
||||
|
||||
if (getFieldSchema) {
|
||||
const rowFieldSchema = getFieldSchema(rowField);
|
||||
|
||||
if (fieldHasSubFields(rowField)) {
|
||||
return [
|
||||
...rowSchema,
|
||||
...rowFieldSchema,
|
||||
];
|
||||
}
|
||||
|
||||
if (fieldAffectsData(rowField)) {
|
||||
return [
|
||||
...rowSchema,
|
||||
{
|
||||
key: rowField.name,
|
||||
type: rowFieldSchema,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return rowSchema;
|
||||
}, []),
|
||||
};
|
||||
|
||||
const fieldTypes = fields.reduce((schema, field) => {
|
||||
if (!fieldIsPresentationalOnly(field) && !field.hidden) {
|
||||
const getFieldSchema = fieldToSchemaMap[field.type];
|
||||
const getFieldSchema = fieldToSchemaMap(parentName)[field.type];
|
||||
|
||||
if (getFieldSchema) {
|
||||
const fieldSchema = getFieldSchema(field);
|
||||
@@ -362,31 +67,5 @@ const buildWhereInputType = (name: string, fields: Field[], parentName: string):
|
||||
|
||||
const fieldName = formatName(name);
|
||||
|
||||
return new GraphQLInputObjectType({
|
||||
name: `${fieldName}_where`,
|
||||
fields: {
|
||||
...fieldTypes,
|
||||
OR: {
|
||||
type: new GraphQLList(new GraphQLInputObjectType({
|
||||
name: `${fieldName}_where_or`,
|
||||
fields: {
|
||||
...fieldTypes,
|
||||
},
|
||||
})),
|
||||
},
|
||||
AND: {
|
||||
type: new GraphQLList(new GraphQLInputObjectType({
|
||||
name: `${fieldName}_where_and`,
|
||||
fields: {
|
||||
...fieldTypes,
|
||||
},
|
||||
})),
|
||||
},
|
||||
page: { type: GraphQLInt },
|
||||
limit: { type: GraphQLInt },
|
||||
sort: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
return buildInputObject(fieldName, fieldTypes);
|
||||
};
|
||||
|
||||
export default buildWhereInputType;
|
||||
|
||||
256
src/graphql/schema/fieldToSchemaMap.ts
Normal file
256
src/graphql/schema/fieldToSchemaMap.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
import {
|
||||
GraphQLBoolean,
|
||||
GraphQLEnumType,
|
||||
GraphQLFloat,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLList,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
import { DateTimeResolver, EmailAddressResolver } from 'graphql-scalars';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import {
|
||||
ArrayField,
|
||||
CheckboxField,
|
||||
CodeField, DateField,
|
||||
EmailField, fieldAffectsData, fieldHasSubFields, GroupField,
|
||||
NumberField, optionIsObject, PointField,
|
||||
RadioField, RelationshipField,
|
||||
RichTextField, RowField, SelectField,
|
||||
TextareaField,
|
||||
TextField, UploadField,
|
||||
} from '../../fields/config/types';
|
||||
import withOperators from './withOperators';
|
||||
import { operators } from './operators';
|
||||
import combineParentName from '../utilities/combineParentName';
|
||||
import formatName from '../utilities/formatName';
|
||||
import { recursivelyBuildNestedPaths } from './recursivelyBuildNestedPaths';
|
||||
|
||||
export const fieldToSchemaMap = (parentName: string) => ({
|
||||
number: (field: NumberField) => {
|
||||
const type = GraphQLFloat;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, ...operators.comparison],
|
||||
),
|
||||
};
|
||||
},
|
||||
text: (field: TextField) => {
|
||||
const type = GraphQLString;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
email: (field: EmailField) => {
|
||||
const type = EmailAddressResolver;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
textarea: (field: TextareaField) => {
|
||||
const type = GraphQLString;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
richText: (field: RichTextField) => {
|
||||
const type = GraphQLJSON;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
code: (field: CodeField) => {
|
||||
const type = GraphQLString;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
radio: (field: RadioField) => ({
|
||||
type: withOperators(
|
||||
field,
|
||||
new GraphQLEnumType({
|
||||
name: `${combineParentName(parentName, field.name)}_Input`,
|
||||
values: field.options.reduce((values, option) => {
|
||||
if (optionIsObject(option)) {
|
||||
return {
|
||||
...values,
|
||||
[formatName(option.value)]: {
|
||||
value: option.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...values,
|
||||
[formatName(option)]: {
|
||||
value: option,
|
||||
},
|
||||
};
|
||||
}, {}),
|
||||
}),
|
||||
parentName,
|
||||
[...operators.equality, 'like'],
|
||||
),
|
||||
}),
|
||||
date: (field: DateField) => {
|
||||
const type = DateTimeResolver;
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, ...operators.comparison, 'like'],
|
||||
),
|
||||
};
|
||||
},
|
||||
point: (field: PointField) => {
|
||||
const type = GraphQLList(GraphQLFloat);
|
||||
return {
|
||||
type: withOperators(
|
||||
field,
|
||||
type,
|
||||
parentName,
|
||||
[...operators.equality, ...operators.comparison, ...operators.geo],
|
||||
),
|
||||
};
|
||||
},
|
||||
relationship: (field: RelationshipField) => {
|
||||
let type = withOperators(
|
||||
field,
|
||||
GraphQLString,
|
||||
parentName,
|
||||
[...operators.equality, ...operators.contains],
|
||||
);
|
||||
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
type = new GraphQLInputObjectType({
|
||||
name: `${combineParentName(parentName, field.name)}_Relation`,
|
||||
fields: {
|
||||
relationTo: {
|
||||
type: new GraphQLEnumType({
|
||||
name: `${combineParentName(parentName, field.name)}_Relation_RelationTo`,
|
||||
values: field.relationTo.reduce((values, relation) => ({
|
||||
...values,
|
||||
[formatName(relation)]: {
|
||||
value: relation,
|
||||
},
|
||||
}), {}),
|
||||
}),
|
||||
},
|
||||
value: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (field.hasMany) {
|
||||
return {
|
||||
type: new GraphQLList(type),
|
||||
};
|
||||
}
|
||||
|
||||
return { type };
|
||||
},
|
||||
upload: (field: UploadField) => ({
|
||||
type: withOperators(
|
||||
field,
|
||||
GraphQLString,
|
||||
parentName,
|
||||
[...operators.equality],
|
||||
),
|
||||
}),
|
||||
checkbox: (field: CheckboxField) => ({
|
||||
type: withOperators(
|
||||
field,
|
||||
GraphQLBoolean,
|
||||
parentName,
|
||||
[...operators.equality],
|
||||
),
|
||||
}),
|
||||
select: (field: SelectField) => ({
|
||||
type: withOperators(
|
||||
field,
|
||||
new GraphQLEnumType({
|
||||
name: `${combineParentName(parentName, field.name)}_Input`,
|
||||
values: field.options.reduce((values, option) => {
|
||||
if (typeof option === 'object' && option.value) {
|
||||
return {
|
||||
...values,
|
||||
[formatName(option.value)]: {
|
||||
value: option.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof option === 'string') {
|
||||
return {
|
||||
...values,
|
||||
[option]: {
|
||||
value: option,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return values;
|
||||
}, {}),
|
||||
}),
|
||||
parentName,
|
||||
[...operators.equality, ...operators.contains],
|
||||
),
|
||||
}),
|
||||
array: (field: ArrayField) => recursivelyBuildNestedPaths(parentName, field),
|
||||
group: (field: GroupField) => recursivelyBuildNestedPaths(parentName, field),
|
||||
row: (field: RowField) => field.fields.reduce((rowSchema, rowField) => {
|
||||
const getFieldSchema = fieldToSchemaMap(parentName)[rowField.type];
|
||||
|
||||
if (getFieldSchema) {
|
||||
const rowFieldSchema = getFieldSchema(rowField);
|
||||
|
||||
if (fieldHasSubFields(rowField)) {
|
||||
return [
|
||||
...rowSchema,
|
||||
...rowFieldSchema,
|
||||
];
|
||||
}
|
||||
|
||||
if (fieldAffectsData(rowField)) {
|
||||
return [
|
||||
...rowSchema,
|
||||
{
|
||||
key: rowField.name,
|
||||
type: rowFieldSchema,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return rowSchema;
|
||||
}, []),
|
||||
});
|
||||
6
src/graphql/schema/operators.ts
Normal file
6
src/graphql/schema/operators.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const operators = {
|
||||
equality: ['equals', 'not_equals'],
|
||||
contains: ['in', 'not_in', 'all'],
|
||||
comparison: ['greater_than_equal', 'greater_than', 'less_than_equal', 'less_than'],
|
||||
geo: ['near'],
|
||||
};
|
||||
42
src/graphql/schema/recursivelyBuildNestedPaths.ts
Normal file
42
src/graphql/schema/recursivelyBuildNestedPaths.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
FieldAffectingData,
|
||||
fieldAffectsData,
|
||||
fieldIsPresentationalOnly,
|
||||
FieldWithSubFields,
|
||||
} from '../../fields/config/types';
|
||||
import { fieldToSchemaMap } from './fieldToSchemaMap';
|
||||
|
||||
export const recursivelyBuildNestedPaths = (parentName: string, field: FieldWithSubFields & FieldAffectingData) => {
|
||||
const nestedPaths = field.fields.reduce((nestedFields, nestedField) => {
|
||||
if (!fieldIsPresentationalOnly(nestedField)) {
|
||||
const getFieldSchema = fieldToSchemaMap(parentName)[nestedField.type];
|
||||
const nestedFieldName = fieldAffectsData(nestedField) ? `${field.name}__${nestedField.name}` : undefined;
|
||||
|
||||
if (getFieldSchema) {
|
||||
const fieldSchema = getFieldSchema({
|
||||
...nestedField,
|
||||
name: nestedFieldName,
|
||||
});
|
||||
|
||||
if (Array.isArray(fieldSchema)) {
|
||||
return [
|
||||
...nestedFields,
|
||||
...fieldSchema,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
...nestedFields,
|
||||
{
|
||||
key: nestedFieldName,
|
||||
type: fieldSchema,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return nestedFields;
|
||||
}, []);
|
||||
|
||||
return nestedPaths;
|
||||
};
|
||||
@@ -16,7 +16,9 @@ let token;
|
||||
let postID;
|
||||
let versionID;
|
||||
|
||||
describe('GrahpQL Resolvers', () => {
|
||||
describe('GrahpQL Version Resolvers', () => {
|
||||
const title = 'autosave title';
|
||||
|
||||
beforeAll(async (done) => {
|
||||
const login = `
|
||||
mutation {
|
||||
@@ -39,7 +41,6 @@ describe('GrahpQL Resolvers', () => {
|
||||
|
||||
describe('Create', () => {
|
||||
it('should allow a new autosavePost to be created with draft status', async () => {
|
||||
const title = 'autosave title';
|
||||
const description = 'autosave description';
|
||||
|
||||
const query = `mutation {
|
||||
@@ -49,6 +50,7 @@ describe('GrahpQL Resolvers', () => {
|
||||
description
|
||||
createdAt
|
||||
updatedAt
|
||||
_status
|
||||
}
|
||||
}`;
|
||||
|
||||
@@ -57,44 +59,68 @@ describe('GrahpQL Resolvers', () => {
|
||||
const data = response.createAutosavePost;
|
||||
postID = data.id;
|
||||
|
||||
expect(typeof data._status).toBe('undefined');
|
||||
expect(data._status).toStrictEqual('draft');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Read', () => {
|
||||
it('should allow read of autosavePost versions', async () => {
|
||||
const updatedTitle = 'updated title';
|
||||
|
||||
// modify the post so it will create a new version
|
||||
// language=graphQL
|
||||
const update = `mutation {
|
||||
updateAutosavePost(id: "${postID}", data: {title: "${updatedTitle}"}) {
|
||||
title
|
||||
}
|
||||
}`;
|
||||
|
||||
await client.request(update);
|
||||
|
||||
// query the version
|
||||
// language=graphQL
|
||||
const query = `query {
|
||||
versionsAutosavePost(where: { parent: { equals: ${postID} } }) {
|
||||
id
|
||||
version {
|
||||
title
|
||||
versionsAutosavePosts(where: { parent: { equals: "${postID}" } }) {
|
||||
docs {
|
||||
id
|
||||
parent
|
||||
version {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
const response = await client.request(query);
|
||||
|
||||
const data = response.versionsAutosavePost;
|
||||
const data = response.versionsAutosavePosts;
|
||||
const doc = data.docs[0];
|
||||
versionID = doc.id;
|
||||
|
||||
versionID = data.docs[0].id;
|
||||
|
||||
expect(versionID).toBeDefined();
|
||||
expect(data.docs[0].version.title).toBeDefined();
|
||||
expect(doc.id).toBeDefined();
|
||||
expect(doc.parent).toStrictEqual(postID);
|
||||
expect(doc.version.title).toStrictEqual(title);
|
||||
});
|
||||
});
|
||||
|
||||
// describe('Restore', () => {
|
||||
// it('should allow a version to be restored', async () => {
|
||||
// // update a versionsPost
|
||||
// const query = `mutation {
|
||||
// restoreAutosavePost {
|
||||
// id
|
||||
// title
|
||||
// }`;
|
||||
// const response = await client.request(query);
|
||||
//
|
||||
// const data = response.versionsAutosavePost;
|
||||
// });
|
||||
// });
|
||||
describe('Restore', () => {
|
||||
it('should allow a version to be restored', async () => {
|
||||
// update a versionsPost
|
||||
const restore = `mutation {
|
||||
restoreVersionAutosavePost(id: "${versionID}")
|
||||
}`;
|
||||
|
||||
await client.request(restore);
|
||||
|
||||
const query = `query {
|
||||
AutosavePost(id: "${postID}") {
|
||||
title
|
||||
}
|
||||
}`;
|
||||
|
||||
const response = await client.request(query);
|
||||
const data = response.AutosavePost;
|
||||
expect(data.title).toStrictEqual(title);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user