ra-data-opencrud
Version:
A Prisma/GraphCMS data provider for react-admin
423 lines (375 loc) • 10.3 kB
text/typescript
import {
GET_LIST,
GET_ONE,
GET_MANY,
GET_MANY_REFERENCE,
CREATE,
UPDATE,
DELETE
} from 'react-admin';
import isObject from 'lodash/isObject';
import getFinalType from './utils/getFinalType';
import { computeFieldsToAddRemoveUpdate } from './utils/computeAddRemoveUpdate';
import {
PRISMA_CONNECT,
PRISMA_DISCONNECT,
PRISMA_UPDATE
} from './constants/mutations';
import {
IntrospectionInputObjectType,
IntrospectionObjectType,
IntrospectionType,
IntrospectionNamedTypeRef
} from 'graphql';
import { IntrospectionResult, Resource } from './constants/interfaces';
interface GetListParams {
filter: { [key: string]: any };
pagination: { page: number; perPage: number };
sort: { field: string; order: string };
}
//TODO: Object filter weren't tested yet
const buildGetListVariables = (introspectionResults: IntrospectionResult) => (
resource: Resource,
aorFetchType: string,
params: GetListParams
) => {
const filter = Object.keys(params.filter).reduce((acc, key) => {
if (key === 'ids') {
return { ...acc, id_in: params.filter[key] };
}
if (Array.isArray(params.filter[key])) {
const type = introspectionResults.types.find(
t => t.name === `${resource.type.name}WhereInput`
) as IntrospectionInputObjectType;
const inputField = type.inputFields.find(t => t.name === key);
if (!!inputField) {
return {
...acc,
[key]: { id_in: params.filter[key] }
};
}
}
if (isObject(params.filter[key])) {
const type = introspectionResults.types.find(
t => t.name === `${resource.type.name}WhereInput`
) as IntrospectionInputObjectType;
const filterSome = type.inputFields.find(t => t.name === `${key}_some`);
if (filterSome) {
const filter = Object.keys(params.filter[key]).reduce(
(acc, k: string) => ({
...acc,
[`${k}_in`]: params.filter[key][k] as string[]
}),
{} as { [key: string]: string[] }
);
return { ...acc, [`${key}_some`]: filter };
}
}
const parts = key.split('.');
if (parts.length > 1) {
if (parts[1] == 'id') {
const type = introspectionResults.types.find(
t => t.name === `${resource.type.name}WhereInput`
) as IntrospectionInputObjectType;
const filterSome = type.inputFields.find(
t => t.name === `${parts[0]}_some`
);
if (filterSome) {
return {
...acc,
[`${parts[0]}_some`]: { id: params.filter[key] }
};
}
return { ...acc, [parts[0]]: { id: params.filter[key] } };
}
const resourceField = (resource.type as IntrospectionObjectType).fields.find(
f => f.name === parts[0]
)!;
if ((resourceField.type as IntrospectionNamedTypeRef).name === 'Int') {
return { ...acc, [key]: parseInt(params.filter[key]) };
}
if ((resourceField.type as IntrospectionNamedTypeRef).name === 'Float') {
return { ...acc, [key]: parseFloat(params.filter[key]) };
}
}
return { ...acc, [key]: params.filter[key] };
}, {});
return {
skip: (params.pagination.page - 1) * params.pagination.perPage,
first: params.pagination.perPage,
orderBy: `${params.sort.field}_${params.sort.order}`,
where: filter
};
};
const findInputFieldForType = (
introspectionResults: IntrospectionResult,
typeName: string,
field: string
) => {
const type = introspectionResults.types.find(
t => t.name === typeName
) as IntrospectionInputObjectType;
if (!type) {
return null;
}
const inputFieldType = type.inputFields.find(t => t.name === field);
return !!inputFieldType ? getFinalType(inputFieldType.type) : null;
};
const inputFieldExistsForType = (
introspectionResults: IntrospectionResult,
typeName: string,
field: string
): boolean => {
return !!findInputFieldForType(introspectionResults, typeName, field);
};
const buildReferenceField = ({
inputArg,
introspectionResults,
typeName,
field,
mutationType
}: {
inputArg: { [key: string]: any };
introspectionResults: IntrospectionResult;
typeName: string;
field: string;
mutationType: string;
}) => {
const inputType = findInputFieldForType(
introspectionResults,
typeName,
field
);
const mutationInputType = findInputFieldForType(
introspectionResults,
inputType!.name,
mutationType
);
return Object.keys(inputArg).reduce((acc, key) => {
return inputFieldExistsForType(
introspectionResults,
mutationInputType!.name,
key
)
? { ...acc, [key]: inputArg[key] }
: acc;
}, {});
};
interface UpdateParams {
id: string;
data: { [key: string]: any };
previousData: { [key: string]: any };
}
const buildUpdateVariables = (introspectionResults: IntrospectionResult) => (
resource: Resource,
aorFetchType: String,
params: UpdateParams
) => {
return Object.keys(params.data).reduce(
(acc, key) => {
if (Array.isArray(params.data[key])) {
const inputType = findInputFieldForType(
introspectionResults,
`${resource.type.name}UpdateInput`,
key
);
if (!inputType) {
return acc;
}
//TODO: Make connect, disconnect and update overridable
//TODO: Make updates working
const {
fieldsToAdd,
fieldsToRemove /* fieldsToUpdate */
} = computeFieldsToAddRemoveUpdate(
params.previousData[`${key}Ids`],
params.data[`${key}Ids`]
);
return {
...acc,
data: {
...acc.data,
[key]: {
[PRISMA_CONNECT]: fieldsToAdd,
[PRISMA_DISCONNECT]: fieldsToRemove
//[PRISMA_UPDATE]: fieldsToUpdate
}
}
};
}
if (isObject(params.data[key])) {
const fieldsToUpdate = buildReferenceField({
inputArg: params.data[key],
introspectionResults,
typeName: `${resource.type.name}UpdateInput`,
field: key,
mutationType: PRISMA_CONNECT
});
// If no fields in the object are valid, continue
if (Object.keys(fieldsToUpdate).length === 0) {
return acc;
}
// Else, connect the nodes
return {
...acc,
data: {
...acc.data,
[key]: { [PRISMA_CONNECT]: { ...fieldsToUpdate } }
}
};
}
// Put id field in a where object
if (key === 'id' && params.data[key]) {
return {
...acc,
where: {
id: params.data[key]
}
};
}
const type = introspectionResults.types.find(
t => t.name === resource.type.name
) as IntrospectionObjectType;
const isInField = type.fields.find(t => t.name === key);
if (!!isInField) {
// Rest should be put in data object
return {
...acc,
data: {
...acc.data,
[key]: params.data[key]
}
};
}
return acc;
},
{} as { [key: string]: any }
);
};
interface CreateParams {
data: { [key: string]: any };
}
const buildCreateVariables = (introspectionResults: IntrospectionResult) => (
resource: Resource,
aorFetchType: string,
params: CreateParams
) =>
Object.keys(params.data).reduce(
(acc, key) => {
if (Array.isArray(params.data[key])) {
if (
!inputFieldExistsForType(
introspectionResults,
`${resource.type.name}CreateInput`,
key
)
) {
return acc;
}
return {
...acc,
data: {
...acc.data,
[key]: {
[PRISMA_CONNECT]: params.data[`${key}Ids`].map((id: string) => ({
id
}))
}
}
};
}
if (isObject(params.data[key])) {
const fieldsToConnect = buildReferenceField({
inputArg: params.data[key],
introspectionResults,
typeName: `${resource.type.name}CreateInput`,
field: key,
mutationType: PRISMA_CONNECT
});
// If no fields in the object are valid, continue
if (Object.keys(fieldsToConnect).length === 0) {
return acc;
}
// Else, connect the nodes
return {
...acc,
data: {
...acc.data,
[key]: { [PRISMA_CONNECT]: { ...fieldsToConnect } }
}
};
}
// Put id field in a where object
if (key === 'id' && params.data[key]) {
return {
...acc,
where: {
id: params.data[key]
}
};
}
const type = introspectionResults.types.find(
t => t.name === resource.type.name
) as IntrospectionObjectType;
const isInField = type.fields.find(t => t.name === key);
if (isInField) {
// Rest should be put in data object
return {
...acc,
data: {
...acc.data,
[key]: params.data[key]
}
};
}
return acc;
},
{} as { [key: string]: any }
);
export default (introspectionResults: IntrospectionResult) => (
resource: Resource,
aorFetchType: string,
params: any
) => {
switch (aorFetchType) {
case GET_LIST: {
return buildGetListVariables(introspectionResults)(
resource,
aorFetchType,
params
);
}
case GET_MANY:
return {
where: { id_in: params.ids }
};
case GET_MANY_REFERENCE: {
const parts = params.target.split('.');
return {
where: { [parts[0]]: { id: params.id } }
};
}
case GET_ONE:
return {
where: { id: params.id }
};
case UPDATE: {
return buildUpdateVariables(introspectionResults)(
resource,
aorFetchType,
params
);
}
case CREATE: {
return buildCreateVariables(introspectionResults)(
resource,
aorFetchType,
params
);
}
case DELETE:
return {
where: { id: params.id }
};
}
};