Last active
November 2, 2020 08:23
-
-
Save hendrikniemann/83648da05161a36e84c0ee4ab61ad605 to your computer and use it in GitHub Desktop.
React Admin + Postgraphile Data Provider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO: Skip tables without an id field? | |
module.exports = function LoadByIdsPlugin(builder) { | |
builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => { | |
const { | |
scope: { isPgCondition, pgIntrospection: table }, | |
fieldWithHooks, | |
} = context; | |
if (!isPgCondition || !table || table.kind !== 'class') { | |
return fields; | |
} | |
const conditionFieldSpec = { | |
type: new build.graphql.GraphQLList( | |
new build.graphql.GraphQLNonNull(build.graphql.GraphQLInt), | |
), | |
description: | |
'Filters this list by checking if the id of the entity is inside of this array of ids.', | |
}; | |
return build.extend(fields, { | |
ids: fieldWithHooks('ids', conditionFieldSpec, { | |
addPgTableCondition: { | |
schemaName: table.namespaceName, | |
tableName: table.name, | |
conditionFieldSpec, | |
conditionFieldName: 'ids', | |
}, | |
}), | |
}); | |
}); | |
builder.hook('GraphQLObjectType:fields:field:args', (args, build, context) => { | |
const { pgSql: sql } = build; | |
const { | |
scope: { | |
isPgFieldConnection, | |
pgFieldIntrospection: procOrTable, | |
pgFieldIntrospectionTable: tableIfProc, | |
}, | |
addArgDataGenerator, | |
} = context; | |
const table = tableIfProc || procOrTable; | |
if (!isPgFieldConnection || !table || table.kind !== 'class') { | |
return args; | |
} | |
addArgDataGenerator(function conditionSQLBuilder({ condition }) { | |
if (!condition) { | |
return {}; | |
} | |
const { ids } = condition; | |
return { | |
pgQuery: queryBuilder => { | |
queryBuilder.where(sql.fragment`id IN (${sql.join(ids.map(sql.value), ', ')})`); | |
}, | |
}; | |
}); | |
return args; | |
}); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { gql } from 'apollo-boost'; | |
import { camelCase, constantCase } from 'change-case'; | |
import { GET_LIST, GET_ONE, GET_MANY, CREATE, UPDATE, DELETE } from 'react-admin'; | |
import { plural } from 'pluralize'; | |
export default function buildQuery(introspectionResults) { | |
return (raFetchType, resourceName, params) => { | |
const gqlType = introspectionResults.types.find( | |
type => | |
type.name.toLowerCase() === resourceName.toLowerCase() || | |
type.name.toLowerCase() + 's' === resourceName.toLowerCase(), | |
); | |
if (!gqlType) { | |
const error = new Error(`Resource ${resourceName} not found.`); | |
// @ts-ignore | |
error.status = 404; | |
throw error; | |
} | |
const queryFields = gqlType.fields.filter(isSimpleField); | |
switch (raFetchType) { | |
case GET_LIST: | |
return { | |
query: gql` | |
query reactAdminGetList${gqlType.name}( | |
$first: Int, | |
$offset: Int, | |
$orderBy: [${getOrderByTypeName(gqlType.name)}!], | |
) { | |
data: all${plural( | |
gqlType.name, | |
)}(first: $first, offset: $offset, orderBy: $orderBy) { | |
edges { | |
node { | |
${queryFields.map(field => field.name).join(' ')} | |
} | |
} | |
totalCount | |
} | |
} | |
`, | |
variables: { | |
offset: (params.pagination.page - 1) * params.pagination.perPage, | |
first: params.pagination.perPage, | |
orderBy: [constantCase(params.sort.field) + '_' + params.sort.order.toUpperCase()], | |
}, | |
parseResponse: response => ({ | |
data: response.data.data.edges.map(edge => edge.node), | |
total: response.data.data.totalCount, | |
}), | |
}; | |
case GET_ONE: | |
return { | |
query: gql` | |
query reactAdminGetOne${gqlType.name}($id: Int!) { | |
data: ${camelCase(gqlType.name)}ById(id: $id) { | |
${queryFields.map(field => field.name).join(' ')} | |
} | |
} | |
`, | |
variables: { id: parseInt(params.id, 10) }, | |
parseResponse: response => response.data, | |
}; | |
case GET_MANY: | |
return { | |
query: gql` | |
query reactAdminGetMany${gqlType.name}($ids: [Int!]!) { | |
data: all${plural(gqlType.name)}(condition: { ids: $ids }) { | |
edges { | |
node { | |
${queryFields.map(field => field.name).join(' ')} | |
} | |
} | |
totalCount | |
} | |
} | |
`, | |
variables: { | |
ids: params.ids.map(id => parseInt(id, 10)), | |
}, | |
parseResponse: response => { | |
const results = response.data.data.edges.map(edge => edge.node); | |
const data = params.ids.map(id => results.find(res => res.id === parseInt(id, 10))); | |
return { data }; | |
}, | |
}; | |
case CREATE: | |
return { | |
query: gql` | |
mutation reactAdminCreate${gqlType.name}($input: Create${gqlType.name}Input!) { | |
result: create${gqlType.name}(input: $input) { | |
entity: ${camelCase(gqlType.name)} { | |
${queryFields.map(field => field.name).join(' ')} | |
} | |
} | |
} | |
`, | |
variables: { input: { [camelCase(gqlType.name)]: params.data } }, | |
parseResponse: response => ({ data: response.data.result.entity }), | |
}; | |
case UPDATE: | |
const { id, __typename, createdAt, updatedAt, ...patch } = params.data; | |
patch.updatedAt = new Date().toISOString(); | |
return { | |
query: gql` | |
mutation reactAdminUpdate${gqlType.name}($input: Update${gqlType.name}ByIdInput!) { | |
result: update${gqlType.name}ById(input: $input) { | |
entity: ${camelCase(gqlType.name)} { | |
${queryFields.map(field => field.name).join(' ')} | |
} | |
} | |
} | |
`, | |
variables: { input: { id, [camelCase(gqlType.name) + 'Patch']: patch } }, | |
parseResponse: response => ({ data: response.data.result.entity }), | |
}; | |
case DELETE: | |
return { | |
query: gql` | |
mutation reactAdminDelete${gqlType.name}($id: Int!) { | |
result: delete${gqlType.name}ById(input: { id: $id }) { | |
id: deleted${gqlType.name}Id | |
} | |
} | |
`, | |
variables: { id: parseInt(params.id, 10) }, | |
parseResponse: response => { | |
if (response.data.result.id === null) { | |
const error = new Error('Could not delete resource. Unknown Error'); | |
error.status = 500; | |
throw error; | |
} | |
return { data: null }; | |
}, | |
}; | |
default: { | |
const error = new Error(`Fetch type ${raFetchType} not supported for ${resourceName}.`); | |
// @ts-ignore | |
error.status = 404; | |
throw error; | |
} | |
} | |
}; | |
} | |
function isSimpleField(field) { | |
if (field.name === 'nodeId') { | |
return false; | |
} | |
// if (field.name.endsWith('Id')) { | |
// return false; | |
// } | |
return ( | |
field.type.kind === 'ENUM' || | |
field.type.kind === 'SCALAR' || | |
(field.type.kind === 'NON_NULL' && | |
(field.type.ofType.kind === 'ENUM' || field.type.ofType.kind === 'SCALAR')) | |
); | |
} | |
function getOrderByTypeName(typename) { | |
return plural(typename) + 'OrderBy'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment