obrigado-react-admin-backend-utils
Version:
Helper utilities for react-admin graphql backend.
297 lines • 11.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const typeorm_1 = require("typeorm");
const isObject_1 = require("./utils/isObject");
const apollo_server_errors_1 = require("apollo-server-errors");
class EntityUpdateHelper {
constructor() { }
static async update(entity, data, options) {
const defaultOptions = { ignore: [] };
let helper = new EntityUpdateHelper();
helper.entity = entity;
//@ts-ignore
helper.EntityClass = entity.constructor;
helper.data = data;
helper.options = Object.assign(defaultOptions, options || {});
//@ts-ignore
return helper._updateEntity();
}
async _updateEntity() {
let { EntityClass, entity, data, options } = this;
const metadata = typeorm_1.getRepository(EntityClass).metadata;
this.metadata = metadata;
let relations = {};
//@ts-ignore
let isNew = !this.entity.id;
for (let r of metadata.relations) {
relations[r.propertyName] = r;
}
let columnNames = this.metadata.columns.map(c => c.propertyName);
this.relations = relations;
//@ts-ignore
for (let p in data) {
if (options && options.ignore && options.ignore.includes(p))
continue;
let fieldName = this.getFieldName(p);
if (p == 'id')
continue;
//handle file upl oad
if (isFileInput(data[p])) {
let fileData = data[p];
if (fileData.skip)
continue;
if (this.options && this.options.fileHandler) {
//@ts-ignore
if (entity[p]) {
//@ts-ignore
await this.options.fileHandler.deleteFile(entity[p]);
//@ts-ignore
entity[p] = null;
}
if (fileData.file_name == null && fileData.body == null)
continue;
//@ts-ignore
entity[p] = await this.options.fileHandler.saveFile(fileData);
}
else {
throw new Error("Please specify file handler in options");
}
continue;
}
//update to many relation
if (p.endsWith('_ids') && !this.getRelation(fieldName).isCascadeUpdate) {
if (isNew) {
throw new apollo_server_errors_1.ApolloError(`Unable to set non cascade relation ${this.metadata.name}.${p} for new entity. You need to save the entity first`, 'INVALID_DATA');
}
await this.updateRelatedEntitiesByIds(p, data[p]);
continue;
}
if (p.endsWith('_ids') && this.getRelation(fieldName).isCascadeUpdate) {
await this.updateRelatedEntitiesByIdsCascade(p, data[p]);
continue;
}
if (Array.isArray(data[p])) {
if (this.getRelation(fieldName).isCascadeUpdate) {
await this.updateRelationsEntitiesCascade(p, data[p]);
}
else {
if (isNew) {
throw new apollo_server_errors_1.ApolloError(`Unable to set non cascade relation ${this.metadata.name}.${p} for new entity. You need to save the entity first`, 'INVALID_DATA');
}
await this.updateRelationsEntities(p, data[p]);
}
continue;
}
if (isObject_1.isObject(data[p])) {
if (this.getRelation(fieldName).isCascadeUpdate) {
//@ts-ignore
entity[p] = data[p];
}
else {
if (isNew) {
throw new apollo_server_errors_1.ApolloError(`Unable to set non cascade relation ${this.metadata.name}.${p} for new entity. You need to save the entity first`, 'INVALID_DATA');
}
await this.updateRelatedEntity(p, data[p]);
}
continue;
}
//set single property
if (!columnNames.includes(p)) {
throw new apollo_server_errors_1.ApolloError(`Entity ${this.metadata.name}.${p} doesn't have column ${p}`, 'INVALID_DATA');
}
//@ts-ignore
entity[p] = data[p];
}
}
async updateRelatedEntitiesByIds(field, ids) {
let fieldName = this.getFieldName(field);
//update relation
let currentRelations = await typeorm_1.createQueryBuilder()
.relation(this.EntityClass, fieldName)
.of(this.entity)
.loadMany();
let newIds = ids.map((id) => parseInt(id));
let currentIds = currentRelations.map(r => parseInt(r.id));
// id to add
let idsToAdd = [];
for (let id of newIds) {
if (!currentIds.includes(id)) {
idsToAdd.push(id);
}
}
let idsToRemove = [];
for (let id of currentIds) {
if (!newIds.includes(id)) {
idsToRemove.push(id);
}
}
if (idsToAdd.length > 0)
await typeorm_1.createQueryBuilder()
.relation(this.EntityClass, fieldName)
.of(this.entity)
.add(idsToAdd.map(id => ({ id })));
if (idsToRemove.length > 0)
await typeorm_1.createQueryBuilder()
.relation(this.EntityClass, fieldName)
.of(this.entity)
.remove(idsToRemove.map(id => ({ id })));
}
async updateRelatedEntitiesByIdsCascade(field, ids) {
let fieldName = this.getFieldName(field);
let relation = this.getRelation(fieldName);
//update to One relation
this.setEntityRelationValue(fieldName, ids.map(id => {
//@ts-ignore
let newRelatedEntity = new relation.type();
newRelatedEntity.id = id;
return newRelatedEntity;
}));
}
async updateRelationsEntitiesCascade(field, value) {
let relatedEntities;
//@ts-ignore
if (this.entity[field] instanceof Promise) {
//@ts-ignore
relatedEntities = await this.entity[field];
}
else {
//@ts-ignore
relatedEntities = this.entity[field];
}
relatedEntities = relatedEntities ? relatedEntities : [];
let newIds = value.map((item) => parseInt(item.id));
let currentIds = relatedEntities.map((r) => parseInt(r.id));
let dataMap = this.arrayToObject(value);
//remove entities
relatedEntities = relatedEntities.filter((entity) => newIds.includes(parseInt(entity.id)));
//update
for (let entity of relatedEntities) {
await EntityUpdateHelper.update(entity, dataMap[entity.id]);
}
//add new one
for (let entity of value) {
if (!currentIds.includes(entity.id)) {
relatedEntities.push(entity);
}
}
this.setEntityRelationValue(field, relatedEntities);
}
async updateRelationsEntities(field, value) {
let relation = this.getRelation(field);
let relatedEntities = await typeorm_1.createQueryBuilder()
.relation(this.EntityClass, field)
.of(this.entity)
.loadMany();
relatedEntities = relatedEntities ? relatedEntities : [];
let newIds = value.map((item) => parseInt(item.id));
let currentIds = relatedEntities.map(r => parseInt(r.id));
// remove entities
relatedEntities.filter(relatedEntity => {
return newIds.includes(parseInt(relatedEntity.id));
});
let idsToRemove = [];
for (let id of currentIds) {
if (!newIds.includes(id)) {
idsToRemove.push(id);
}
}
if (idsToRemove.length > 0)
await typeorm_1.createQueryBuilder(relation.type, 'r')
.whereInIds(idsToRemove)
.delete();
//update current
let dataToUpdate = this.arrayToObject(value);
for (let relatedEntity of relatedEntities) {
await EntityUpdateHelper.update(relatedEntity, dataToUpdate[relatedEntity.id]);
await relatedEntity.save();
}
//add new one,
for (let id of newIds) {
if (!currentIds.includes(id)) {
//@ts-ignore
let newRelatedEntity = new relation.type();
await EntityUpdateHelper.update(newRelatedEntity, dataToUpdate[id]);
if (relation.inverseSidePropertyPath) {
throw new Error(
//@ts-ignore
`Please add inverse relation property for ${this.metadata.name} inside ${relation.type.name}`);
}
newRelatedEntity[relation.inverseSidePropertyPath] = this.entity;
await newRelatedEntity.save();
}
}
}
async updateRelatedEntity(field, value) {
let relation = this.getRelation(field);
let relatedEntity = await typeorm_1.createQueryBuilder()
.relation(this.EntityClass, field)
.of(this.entity)
.loadOne();
if (!relatedEntity) {
//@ts-ignore
relatedEntity = new relation.type();
await EntityUpdateHelper.update(relatedEntity, value);
if (!relation.inverseSidePropertyPath) {
throw new Error(
//@ts-ignore
`Please add inverse relation property for ${this.metadata.name}.${field} inside ${relation.type.name}`);
}
relatedEntity[relation.inverseSidePropertyPath] = this.entity;
await relatedEntity.save();
}
else {
await EntityUpdateHelper.update(relatedEntity, value);
}
this.setEntityRelationValue(field, relatedEntity);
}
setEntityRelationValue(field, value) {
//@ts-ignore
if (this.entity[field] instanceof Promise) {
//@ts-ignore
this.entity[field] = Promise.resolve(value);
}
else {
//@ts-ignore
this.entity[field] = value;
}
}
arrayToObject(data, key = 'id') {
let out = {};
for (let item of data) {
out[item[key]] = Object.assign({}, item);
}
return out;
}
getFieldName(f) {
if (f.endsWith('_file')) {
return f.substr(0, f.length - 5);
}
if (f.endsWith('_id')) {
return f.substr(0, f.length - 3);
}
if (f.endsWith('_ids')) {
return f.substr(0, f.length - 4);
}
return f;
}
getRelation(fieldName) {
if (this.relations[fieldName]) {
return this.relations[fieldName];
}
throw new Error(`Unable to find relation for ${fieldName} in ${this.metadata.name}`);
}
}
exports.EntityUpdateHelper = EntityUpdateHelper;
function isFileInput(obj) {
if (!obj)
return false;
if (typeof obj !== 'object')
return false;
const fileProps = ['body', 'file_name', 'skip'];
for (let p in obj) {
if (!fileProps.includes(p))
return false;
}
return !(obj.body === undefined && obj.skip === undefined);
}
//# sourceMappingURL=EntityUpdateHelper.js.map