nocobase/packages/database-next/src/update-guard.ts

173 lines
4.7 KiB
TypeScript
Raw Normal View History

feat: database next (#130) * FIX: database test with sqlite * more types * filter test * split filter parser * filter test * filter test: hasMany * define inverse association for belongsTo & hasMany * chore: console.log * repository count method * chore: Collection * repository filter & appends & fields & expect * repository: sort option * chore: test * add: test * find & findAndCount * chore: test * database-next: update guard * database-next: update guard associationKeysToBeUpdate * chore: comment * update-guard OneToOne Association * has one repository * support through table value * belongs to many repository * has many repository * has many repository find * fix: has many find and count * clean code * add count method * chore: multiple relation * chore: single relation * repository find * relation repository builder * repository count * repository count test * fix test * close db afterEach test * sort with associations * repository update * has many repository: destroy * belongs to many repository: destroy * add transaction decorator * belongs to many with transaction * has many with transaction * clean types * clean types * clean types * repository transaction * fix test * single relation repository with transaction * single relation repository with transaction * fix: test * fix: option parser fields append * fix: typo * fix: string type * fix: import * collection field methods * cleanup * collection sync * fix: import * fix: test * collection update field * collection update options * database hook * database test * database event test * update database event * add async emmit mixin * async model event * database import * fix: model hook type * fix: collection event * recall model.init on collection update * skip redefine collection test * skip collection model update * add model hook class * global model event support * chore * chore * change utils import * add field types * database import * more import test * test case * fix: through model init... * bugfix * fix * update database import * collection sync by foreachModel * fix collection model sync * update * add field types * custom operator * sqlite array field * postgresql array field * array query escape * mysql array operators * date operators * array field sqlite fix * association operator * date operator empty & notEmpty * fix: fields import * fix array field nested association * filter parse prepare * fix test * string field empty * add date operator test * field option types * fix typo * fix: operator name conflict * rename function Co-authored-by: Chareice <chareice@live.com>
2021-12-06 13:12:54 +00:00
import { flatten } from 'flat';
import lodash, { keys } from 'lodash';
import { Collection } from './collection';
import { BelongsTo, HasOne, Model, ModelCtor } from 'sequelize';
import { AssociationKeysToBeUpdate, BlackList, WhiteList } from './repository';
type UpdateValueItem = string | number | UpdateValues;
type UpdateValues = {
[key: string]: UpdateValueItem | Array<UpdateValueItem>;
};
export class UpdateGuard {
model: ModelCtor<any>;
private associationKeysToBeUpdate: AssociationKeysToBeUpdate;
private blackList: BlackList;
private whiteList: WhiteList;
setModel(model: ModelCtor<any>) {
this.model = model;
}
setAssociationKeysToBeUpdate(
associationKeysToBeUpdate: AssociationKeysToBeUpdate,
) {
this.associationKeysToBeUpdate = associationKeysToBeUpdate;
}
setWhiteList(whiteList: WhiteList) {
this.whiteList = whiteList;
}
setBlackList(blackList: BlackList) {
this.blackList = blackList;
}
/**
* Sanitize values by whitelist blacklist
* @param values
*/
sanitize(values: UpdateValues) {
values = lodash.clone(values);
if (!this.model) {
throw new Error('please set model first');
}
const associations = this.model.associations;
const associationsValues = lodash.pick(values, Object.keys(associations));
// build params of association update guard
const listOfAssociation = (list, association) => {
if (list) {
list = list
.filter((whiteListKey) => whiteListKey.startsWith(`${association}.`))
.map((whiteListKey) => whiteListKey.replace(`${association}.`, ''));
if (list.length == 0) {
return undefined;
}
return list;
}
return undefined;
};
// sanitize association values
Object.keys(associationsValues).forEach((association) => {
let associationValues = associationsValues[association];
const filterAssociationToBeUpdate = (value) => {
const associationKeysToBeUpdate = this.associationKeysToBeUpdate || [];
if (associationKeysToBeUpdate.includes(association)) {
return value;
}
const associationObj = associations[association];
const associationKeyName =
associationObj.associationType == 'BelongsTo' ||
associationObj.associationType == 'HasOne'
? (<any>associationObj).targetKey
: associationObj.target.primaryKeyAttribute;
if (value[associationKeyName]) {
return lodash.pick(value, [
associationKeyName,
...Object.keys(associationObj.target.associations),
]);
}
return value;
};
const sanitizeValue = (value) => {
const associationUpdateGuard = new UpdateGuard();
associationUpdateGuard.setModel(associations[association].target);
['whiteList', 'blackList', 'associationKeysToBeUpdate'].forEach(
(optionKey) => {
associationUpdateGuard[`set${lodash.upperFirst(optionKey)}`](
listOfAssociation(this[optionKey], association),
);
},
);
return associationUpdateGuard.sanitize(
filterAssociationToBeUpdate(value),
);
};
if (Array.isArray(associationValues)) {
associationValues = associationValues.map((value) => {
if (typeof value == 'string' || typeof value == 'number') {
return value;
} else {
return sanitizeValue(value);
}
});
} else if (
typeof associationValues === 'object' &&
associationValues !== null
) {
associationValues = sanitizeValue(associationValues);
}
// set association values to sanitized value
values[association] = associationValues;
});
let valuesKeys = Object.keys(values);
// handle whitelist
if (this.whiteList) {
valuesKeys = valuesKeys.filter((valueKey) => {
return (
this.whiteList.findIndex((whiteKey) => {
const keyPaths = whiteKey.split('.');
return keyPaths[0] === valueKey;
}) !== -1
);
});
}
// handle blacklist
if (this.blackList) {
valuesKeys = valuesKeys.filter(
(valueKey) => !this.blackList.includes(valueKey),
);
}
const result = valuesKeys.reduce((obj, key) => {
lodash.set(obj, key, values[key]);
return obj;
}, {});
return result;
}
static fromOptions(model, options) {
const guard = new UpdateGuard();
guard.setModel(model);
guard.setWhiteList(options.whitelist);
guard.setBlackList(options.blacklist);
guard.setAssociationKeysToBeUpdate(options.updateAssociationValues);
return guard;
}
}