obrigado-react-admin-backend-utils
Version:
Helper utilities for react-admin graphql backend.
432 lines (414 loc) • 15.3 kB
text/typescript
import {
Arg,
Authorized,
ClassType, Ctx,
Field,
Int,
Mutation,
ObjectType,
Query,
Resolver,
} from 'type-graphql'
import {
BaseEntity,
createQueryBuilder,
EntityMetadata, EntitySchema, getRepository,
In,
ObjectLiteral,
SelectQueryBuilder,
ObjectType as TypeOrmObjectType
} from 'typeorm'
import { ApolloError } from 'apollo-server-errors'
import {EntityUpdateHelper, EntityUpdateHelperOptions} from './EntityUpdateHelper'
import {GQLReactAdminListParams} from "./types/GQLReactAdminListParams";
import {GQLReactAdminGetManyReferenceParams} from "./types/GQLReactAdminGetManyReferenceParams";
import {IdsList} from "./types/IdsList";
import {ReactAdminDataProvider} from "./types/ReactAdminDataProvider";
import {ReturnTypeFuncValue} from "type-graphql/dist/decorators/types"
export function createBaseCrudResolver<
ReturnType extends ReturnTypeFuncValue,
CreateType extends ReturnTypeFuncValue ,
EntityType extends ClassType
>(objectTypeCls: ClassType<ReturnType>, inputTypeCls: ClassType<CreateType>, ORMEntity: EntityType, updateHelperOptions?:Partial<EntityUpdateHelperOptions>):ClassType<ReactAdminDataProvider<ReturnType,CreateType,CreateType>> {
console.warn("createBaseCrudResolver is deprececated")
return createAdminResolver({
create:inputTypeCls,
update:inputTypeCls,
return:objectTypeCls,
entity:ORMEntity,
updateHelperOptions
})
}
/**
* Generates base class for your resolver
* @param config Resolver config
*/
export function createAdminResolver<ReturnType extends ReturnTypeFuncValue,
CreateType extends ReturnTypeFuncValue,
UpdateType extends ReturnTypeFuncValue,
EntityType extends EntitySchema<any>>(config:{
/** GQL type which resolver returns*/
return:ReturnType,
/** name part of all resolver methods*/
name?:string,
/** GQL input type for entity creation*/
create:ClassType<CreateType>,
/** GQL input type for entity update,optional*/
update?: ClassType<UpdateType>,
/** TypeOrm Entity class*/
entity:ClassType,
updateHelperOptions?:Partial<EntityUpdateHelperOptions>
}): ClassType<ReactAdminDataProvider<EntityType,CreateType,UpdateType extends undefined?CreateType:UpdateType>>{
const ORMEntity=config.entity
const ReturnGQLClass=config.return
const CreateGQLClass=config.create
const UpdateGQLClass=config.update || config.create
// @ts-ignore
const suffix =config.name || ORMEntity.name
const updateHelperOptions=config.updateHelperOptions
//@ts-ignore
let entityAlias = ORMEntity.name.toLowerCase()
(`${suffix}List`)
class OutList {
(type => [ReturnGQLClass], { nullable: true })
data: any
(type => Int)
total: number
}
({ isAbstract: true })
class BaseResolver extends ReactAdminDataProvider<EntityType,CreateType,UpdateType>{
('admin')
(type => OutList, {
name: `admin${suffix}List`,
})
// GET LIST
async getListQuery(
('params', type => GQLReactAdminListParams)
params: GQLReactAdminListParams,
() context:any
) {
return this.getList(params,context)
}
async getList( params: GQLReactAdminListParams, () context:any){
const metadata =getRepository( ORMEntity)
.metadata
let query =getRepository( ORMEntity).createQueryBuilder( entityAlias)
if (params.filter) {
this.applyFilterToQuery(query, params, metadata,context)
}
await this.alterGetListQuery(query, params)
let total = await query.getCount()
if (params.pagination) {
query
.take(params.pagination.perPage)
.skip(
params.pagination.perPage *
(params.pagination.page - 1),
)
}
if (params.sort) {
query.orderBy(
`${entityAlias}.${params.sort.field}`,
//@ts-ignore
params.sort.order,
)
}
let data = await query.getMany()
// @ts-ignore
return { data: data || [], total }
}
// GET ONE
('admin')
(type => ReturnGQLClass, { name: `admin${suffix}GetOne` })
async getOneQuery(
('id')
id: string,
() context:any
) {
return this.getOne(id,context)
}
async getOne(id:string,context:any)
{
let where:ObjectLiteral={}
where[this.primaryKey]=id
// @ts-ignore
return await getRepository(ORMEntity).findOne({ where })
}
// GET_MANY
('admin')
(type => [ReturnGQLClass], {
name: `admin${suffix}GetMany`,
nullable: true,
})
async getManyQuery(
('ids', type => [Int])
ids: number[],
() context:any
){
return this.getMany(ids,context)
}
async getMany( ids: number[],context:any){
let where:ObjectLiteral={}
where[this.primaryKey]=In(ids)
// @ts-ignore
return await getRepository(ORMEntity).find({where})
}
// GET_MANY_REFERENCE
('admin')
(type => OutList, {
name: `admin${suffix}GetManyReference`,
nullable: true,
})
async getManyReferenceQuery(
('params', type => GQLReactAdminGetManyReferenceParams)
params: GQLReactAdminGetManyReferenceParams,
() context:any
) {
return this.getManyReference(params,context)
}
async getManyReference(params: GQLReactAdminGetManyReferenceParams,context:any){
let query =getRepository(ORMEntity).createQueryBuilder('entity').where(
`entity.${params.target}=:id`,
{id:params.id},
)
let total = await query.getCount()
if (params.pagination) {
query
.take(params.pagination.perPage)
.skip(
params.pagination.perPage *
(params.pagination.page - 1),
)
}
if (params.sort) {
//@ts-ignore
query.orderBy(params.sort.field, params.sort.order)
}
return await { total, data: await query.getMany() }
}
// UPDATE
('admin')
(type => ReturnGQLClass, { name: `admin${suffix}Update` })
async updateMutation(
('id', type => Int)
id: number,
('data', type => UpdateGQLClass)
data: UpdateType,
() context:any
) {
return await this.update(id,data,context)
}
async update(id: number, data: UpdateType, context:any) {
let where:ObjectLiteral={}
where[this.primaryKey]=id
let entity = await getRepository(ORMEntity).findOne({ where })
if (!entity)
throw new ApolloError(
'Entity not found for id ' + id,
'NOT_FOUND',
)
await EntityUpdateHelper.update(entity, data,updateHelperOptions)
await getRepository(ORMEntity).save(entity)
return entity
}
//UPDATE_MANY
('admin')
(type => IdsList, { name: `admin${suffix}UpdateMany` })
async updateManyMutation(
('ids', type => [Int])
ids: number[],
('data', type => UpdateGQLClass)
data: UpdateType,
() context:any
) {
return this.updateMany(ids,data,context)
}
async updateMany( ids: number[],data: UpdateType,context:any){
let list = await getRepository(ORMEntity).createQueryBuilder( entityAlias)
.whereInIds(ids)
.getMany()
for (let entity of list) {
await EntityUpdateHelper.update(entity, data,updateHelperOptions)
await getRepository(ORMEntity).save(entity)
}
return { ids }
}
//CREATE
('admin')
(type => ReturnGQLClass, { name: `admin${suffix}Create` })
async createMutation(('data', type => CreateGQLClass) data: CreateType, () context:any) {
return await this.create(data,context)
}
async create( data: CreateType, context:any) {
let entity = getRepository(ORMEntity).create()
await EntityUpdateHelper.update(entity, data,updateHelperOptions)
await getRepository(ORMEntity).save(entity)
return entity
}
// DELETE
('admin')
(type => ReturnGQLClass, { name: `admin${suffix}Delete` })
async deleteMutation(
('id', type => Int)
id: number,
() context:any
) {
return this.delete(id,context)
}
async delete(id:number,context:any){
// @ts-ignore
const entity = await validateEntityRelations(ORMEntity, id,this.primaryKey)
let clonedEntity={...entity}
// @ts-ignore
await getRepository(ORMEntity).remove(entity)
return clonedEntity
}
// DELETE_MANY
('admin')
(type => IdsList, { name: `admin${suffix}DeleteMany` })
async deleteManyMutation(
('ids', type => [Int])
ids: number[],
() context:any
) {
return this.deleteMany(ids,context)
}
async deleteMany( ids: number[],context:any){
let errors:any[] = []
let removedIds:any[] = []
for (let id of ids) {
try {
// @ts-ignore
let entity = await validateEntityRelations(ORMEntity, id,this.primaryKey)
//@ts-ignore
await getRepository(ORMEntity).remove(entity)
removedIds.push(id)
} catch (e) {
errors.push(e.message)
}
}
if (errors.length > 0) {
throw new ApolloError(errors.join(';'), 'DELETION_FAILED')
}
return { ids: removedIds }
}
async alterGetListQuery(
qb: SelectQueryBuilder<EntityType>,
params: GQLReactAdminListParams,
) {}
applyFilterToQuery(
qb: SelectQueryBuilder<any>,
params: GQLReactAdminListParams,
metadata: EntityMetadata,
context:any
) {
if (params.filter) {
let columnNames = metadata.columns.map(c => c.propertyName)
for (let f of params.filter) {
if (columnNames.includes(f.field)) {
let value = {} as ObjectLiteral
value[f.field] = f.value
qb.andWhere(
`${entityAlias}.${f.field}=:${f.field}`,
value,
)
} else if (f.field === 'q') {
// build full text query
let ftColumnNames = [] as string[]
for (let ind of metadata.indices) {
if (ind.isFulltext) {
for (let column of ind.columns) {
ftColumnNames.push(
`${entityAlias}.${column.propertyName}`,
)
}
}
}
let searchValue = f.value
const specialCharacters = ['+', '*', '@', '%', '-', '(', ')', '"']
specialCharacters.forEach(char => {
searchValue = searchValue.replace(char, '')
})
searchValue.length === 0 ? searchValue : searchValue = `${searchValue}*`
if (ftColumnNames.length > 0)
qb.andWhere(
`match(${ftColumnNames.join(
',',
)}) against (:ftQuery IN BOOLEAN MODE)`,
{ ftQuery: searchValue },
)
}
}
}
}
get primaryKey(){
const metadata = getRepository(ORMEntity).metadata
return metadata.primaryColumns[0].databaseName
}
}
//@ts-ignore
return BaseResolver
}
export async function validateEntityRelations<ORM extends BaseEntity>(
entityClass: any,
id: number,
primaryKey:string
): Promise<ORM> {
const metadata =getRepository(entityClass).metadata as EntityMetadata
let errors:any[] = []
let rQuery = await createQueryBuilder(entityClass, 'entity').where(
`entity.${primaryKey}=:id`,
{ id },
)
for (let r of metadata.relations) {
if (
r.onDelete !== 'CASCADE' && !r.isCascadeRemove
&& !r.isManyToOne
) {
rQuery.loadRelationCountAndMap(
`entity.${r.propertyName}_count`,
`entity.${r.propertyName}`,
)
}
}
let dataCounts = await rQuery.getOne()
for (let r of metadata.relations) {
//@ts-ignore
if (
!r.isCascadeRemove &&
dataCounts &&
// @ts-ignore
dataCounts[`${r.propertyName}_count`] > 0
) {
//@ts-ignore
let count = dataCounts[`${r.propertyName}_count`]
//@ts-ignore
errors.push(`${r.type.name} (${count})`)
}
}
if (errors.length > 0) {
throw new ApolloError(
`Entity ${id} has linked data : ${errors.join(
',',
)}, unlink it first`,
'DELETION_FAILED',
)
}
//@ts-ignore
return dataCounts
}
/*
import {GQLAdministrator} from "./types/GQLAdministrator"
import {GQLAdministratorInput} from "./types/GQLAdministratorInput"
import {Administrator} from "./models/Administrator"
const ZZZ=createAdminResolver({
return:GQLAdministrator,
create:GQLAdministratorInput,
update:GQLAdministrator,
entity:Administrator
})
class YYYY extends ZZZ{
async update(id: number, data: GQLAdministrator extends undefined ? GQLAdministratorInput : GQLAdministrator, context?: any): Promise<any> {
return super.update(id, data, context)
}
}*/