tachybase_todo/packages/core/database/src/update-guard.ts
ChengLei Shao 0e0eb6432e
feat: provide the underscored option for the database (#1366)
* feat: underscored options

* feat: underscored using hook

* feat: database underscored options

* feat: underscored env

* fix: collectionExistsInDb

* fix: test

* fix: nocobase install

* fix: test

* fix: belongsTo association

* fix: test of underscored

* chore: console.log

* fix: list action test

* fix: dump test

* chore: snakeCase algo

* fix: underscored field create

* fix: underscored env

* fix(acl): custom appends merge strategy (#1416)

* Update index.md

* fix(plugin-workflow): use promise to request (#1426)

* Update index.md

* Update collection.md

* Update index.md

* Update index.md

* Update collection.md

* Update field.md

* Update repository.md

* Update has-one-repository.md

* Update has-many-repository.md

* Update belongs-to-many-repository.md

* Update index.md

* chore: translate 'Add tab' in page header (#1424)

* fix: test

* fix: workflow test

* fix: underscored with inherits

* fix: underscored test

* fix:  process.env.DB_UNDERSCORED

* fix: process.env.DB_UNDERSCORED === 'true'

* fix: test

* fix: pg test

* fix: underscored table name

* feat: tableName & fieldName conflict check

* fix: test

* fix: underscored index

* fix: update field unique index

* fix: sync default value

* fix: collection manager create field

* chore: field sync

* fix: pg test

* chore: test

* fix: test

* chore: default constraint name

* chore: syncUniqueIndex

* feat: field destory before check

* feat: field type check

* fix: test

* fix: test

* fix: improve

* fix: should destroy when fields refer to the same field

* fix: acl meta with underscored

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
2023-02-13 21:38:47 +08:00

174 lines
4.9 KiB
TypeScript

import lodash from 'lodash';
import { ModelStatic } from 'sequelize';
import { Model } from './model';
import { AssociationKeysToBeUpdate, BlackList, WhiteList } from './repository';
type UpdateValueItem = string | number | UpdateValues;
type UpdateValues = {
[key: string]: UpdateValueItem | Array<UpdateValueItem>;
};
type UpdateAction = 'create' | 'update';
export class UpdateGuard {
model: ModelStatic<any>;
action: UpdateAction;
underscored: boolean;
private associationKeysToBeUpdate: AssociationKeysToBeUpdate;
private blackList: BlackList;
private whiteList: WhiteList;
setAction(action: UpdateAction) {
this.action = action;
}
setModel(model: ModelStatic<any>) {
this.model = model;
}
setAssociationKeysToBeUpdate(associationKeysToBeUpdate: AssociationKeysToBeUpdate) {
if (this.action == 'create') {
this.associationKeysToBeUpdate = Object.keys(this.model.associations);
} else {
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;
});
if (values instanceof Model) {
return values;
}
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.setAction(lodash.get(options, 'action', 'update'));
guard.setAssociationKeysToBeUpdate(options.updateAssociationValues);
if (options.underscored) {
guard.underscored = options.underscored;
}
return guard;
}
}