"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createCreateCheckers = exports.createRemoveCheckers = exports.translateCheckerInSyncContext = exports.translateCheckerInAsyncContext = void 0; const tslib_1 = require("tslib"); const assert_1 = tslib_1.__importDefault(require("assert")); const filter_1 = require("../store/filter"); const Exception_1 = require("../types/Exception"); const types_1 = require("../types"); const lodash_1 = require("../utils/lodash"); const action_1 = require("../actions/action"); function getFullProjection(entity, schema) { const { attributes } = schema[entity]; const projection = { id: 1, $$createAt$$: 1, $$updateAt$$: 1, $$deleteAt$$: 1, }; Object.keys(attributes).forEach((k) => Object.assign(projection, { [k]: 1, })); return projection; } /** * * @param checker 要翻译的checker * @param silent 如果silent,则row和relation类型的checker只会把限制条件加到查询上,而不报错(除掉create动作) * @returns */ function translateCheckerInAsyncContext(checker, schema) { const { entity, type } = checker; const when = 'before'; // 现在create的relation改成提前的expression检查了,原先是先插入再后检查,性能不行,而且select也需要实现前检查 switch (type) { case 'data': { const { checker: checkerFn } = checker; const fn = (async ({ operation }, context) => { const { data } = operation; await checkerFn(data, context); return 0; }); return { fn, when, }; } case 'row': { const { filter, errMsg, err, inconsistentRows } = checker; const fn = (async ({ operation }, context, option) => { const { filter: operationFilter, data, action, bornAt } = operation; const filter2 = typeof filter === 'function' ? await filter(operation, context, option) : filter; if (!filter2) { return 0; } if (['select', 'count', 'stat'].includes(action)) { operation.filter = operationFilter ? (0, filter_1.combineFilters)(entity, context.getSchema(), [operationFilter, filter2]) : filter2; return 0; } else { const checkSingle = async (f) => { if (await (0, filter_1.checkFilterContains)(entity, context, filter2, f, true)) { return; } if (inconsistentRows) { const { entity: entity2, selection: selection2 } = inconsistentRows; const rows2 = await context.select(entity2, selection2(operationFilter), { dontCollect: true, blockTrigger: true, }); const e = new (err || (Exception_1.OakRowInconsistencyException))(errMsg); e.addData(entity2, rows2, context.getSchema()); throw e; } else { // 可能会暴露隐私信息,暂时不能这样做。by Xc /* const rows2 = await context.select(entity, { data: getFullProjection(entity, context.getSchema()), filter: Object.assign({}, operationFilter, { $not: filter2, }) }, { dontCollect: true, blockTrigger: true, }); */ const e = new (err || (Exception_1.OakRowInconsistencyException))(errMsg); // e.addData(entity, rows2); throw e; } }; let operationFilter2 = operationFilter; if (action === 'create') { // 后台进行创建检查时,以传入的data为准 (0, assert_1.default)(data); if (data instanceof Array) { for (const d of data) { await checkSingle((0, filter_1.translateCreateDataToFilter)(schema, entity, d, !!bornAt)); } } else { await checkSingle((0, filter_1.translateCreateDataToFilter)(schema, entity, data, !!bornAt)); } return; } (0, assert_1.default)(operationFilter2, 'row类型的checker遇到了操作的filter未定义'); await checkSingle(operationFilter2); return 0; } }); return { fn, when, }; } case 'logical': case 'logicalData': { const { checker: checkerFn } = checker; const fn = (async ({ operation }, context, option) => { await checkerFn(operation, context, option); return 0; }); return { fn, when, }; } default: { (0, assert_1.default)(false); } } } exports.translateCheckerInAsyncContext = translateCheckerInAsyncContext; function translateCheckerInSyncContext(checker, schema) { const { entity, type } = checker; const when = 'before'; // 现在create的relation改成提前的expression检查了,原先是先插入再后检查,性能不行,而且select也需要实现前检查 switch (type) { case 'data': { const { checker: checkerFn } = checker; const fn = (operation, context) => checkerFn(operation.data, context); return { fn, when, }; } case 'row': { const { filter, errMsg, entity } = checker; const fn = (operation, context, option) => { const { filter: operationFilter, data, action, bornAt } = operation; const filter2 = typeof filter === 'function' ? filter(operation, context, option) : filter; if (!filter2) { return; } let operationFilter2 = operationFilter; if (action === 'create') { if (data) { // 前端的策略是,有data用data,无data用filter // 目前前端应该不可能制造出来createMultiple (0, assert_1.default)(!(data instanceof Array)); operationFilter2 = (0, filter_1.translateCreateDataToFilter)(schema, entity, data, !!bornAt); } } (0, assert_1.default)(!(filter2 instanceof Promise)); (0, assert_1.default)(operationFilter2, '定义了row类型的checker但却进行了无filter操作'); if ((0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter2, true)) { return; } const e = new Exception_1.OakRowInconsistencyException(errMsg || 'row checker condition illegal'); throw e; }; return { fn, when, }; } case 'logical': case 'logicalData': { const { checker: checkerFn } = checker; const fn = (operation, context, option) => { checkerFn(operation, context, option); }; return { fn, when, }; } default: { (0, assert_1.default)(false); } } } exports.translateCheckerInSyncContext = translateCheckerInSyncContext; /** * 对对象的删除,检查其是否会产生其他行上的空指针,不允许这种情况的出现 * @param schema * @returns * 如果有的对象允许删除,需要使用trigger来处理其相关联的外键对象,这些trigger写作before,则会在checker之前执行,仍然可以删除成功 */ function createRemoveCheckers(schema) { const checkers = []; // 先建立所有的一对多的关系 const OneToManyMatrix = {}; const OneToManyOnEntityMatrix = {}; const addToMto = (e, f, attr) => { if (OneToManyMatrix[f]) { OneToManyMatrix[f]?.push([e, attr]); } else { OneToManyMatrix[f] = [[e, attr]]; } }; const addToMtoEntity = (e, fs) => { for (const f of fs) { if (!OneToManyOnEntityMatrix[f]) { OneToManyOnEntityMatrix[f] = [e]; } else { OneToManyOnEntityMatrix[f]?.push(e); } } }; for (const entity in schema) { if (['operEntity', 'modiEntity', 'userEntityGrant'].includes(entity)) { continue; // 系统功能性数据,不用处理 } const { attributes } = schema[entity]; for (const attr in attributes) { if (attributes[attr].type === 'ref') { addToMto(entity, attributes[attr].ref, attr); } else if (attr === 'entity') { if (attributes[attr].ref) { addToMtoEntity(entity, attributes[attr].ref); } else if (process.env.NODE_ENV === 'development') { console.warn(`${entity}的entity反指指针找不到有效的对象`); } } } } // 当删除一时,要确认多上面没有指向一的数据 const entities = (0, lodash_1.union)(Object.keys(OneToManyMatrix), Object.keys(OneToManyOnEntityMatrix)); for (const entity of entities) { checkers.push({ entity: entity, action: 'remove', type: 'logical', priority: types_1.CHECKER_MAX_PRIORITY, checker: (operation, context, option) => { const promises = []; if (OneToManyMatrix[entity]) { for (const otm of OneToManyMatrix[entity]) { const [e, attr] = otm; const proj = { id: 1, [attr]: 1, }; const filter = operation.filter && { [attr.slice(0, attr.length - 2)]: operation.filter }; const result = context.select(e, { data: proj, filter, indexFrom: 0, count: 1 }, { dontCollect: true, ignoreAttrMiss: true }); if (result instanceof Promise) { promises.push(result.then(([row]) => { if (row) { const err = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${e}」关联的行`); err.addData(e, [row], context.getSchema()); throw err; } })); } else { const [row] = result; if (row) { const err = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${e}」关联的行`); err.addData(e, [row], context.getSchema()); throw err; } } } } if (OneToManyOnEntityMatrix[entity]) { for (const otm of OneToManyOnEntityMatrix[entity]) { const proj = { id: 1, entity: 1, entityId: 1, }; const filter = operation.filter && { [entity]: operation.filter }; const result = context.select(otm, { data: proj, filter, indexFrom: 0, count: 1 }, { dontCollect: true, ignoreAttrMiss: true }); if (result instanceof Promise) { promises.push(result.then(([row]) => { if (row) { const e = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${otm}」关联的行`); e.addData(otm, [row], context.getSchema()); throw e; } })); } else { const [row] = result; if (row) { const record = { a: 's', d: { [otm]: { [row.id]: row, } } }; const e = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${otm}」关联的行`); e.addData(otm, [row], context.getSchema()); throw e; } } } } if (promises.length > 0) { return Promise.all(promises).then(() => undefined); } } }); } return checkers; } exports.createRemoveCheckers = createRemoveCheckers; function checkAttributeLegal(schema, entity, data) { const { attributes } = schema[entity]; for (const attr in data) { if (attributes[attr]) { const { type, params, default: defaultValue, enumeration, notNull } = attributes[attr]; if (data[attr] === null || data[attr] === undefined) { if (notNull && defaultValue === undefined) { throw new Exception_1.OakAttrNotNullException(entity, [attr]); } if (defaultValue !== undefined) { Object.assign(data, { [attr]: defaultValue, }); } continue; } switch (type) { case 'char': case 'varchar': { if (typeof data[attr] !== 'string') { throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesFormatError', 'oak-domain', { attribtues: attr, format: 'string', }); } const { length } = params; if (length && data[attr].length > length) { throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesTooLong', 'oak-domain', { attributes: attr, length, }); } break; } case 'int': case 'smallint': case 'tinyint': case 'bigint': case 'decimal': case 'money': { if (typeof data[attr] !== 'number') { throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesFormatError', 'oak-domain', { attribtues: attr, format: 'number', }); } const { min, max } = params || {}; if (typeof min === 'number' && data[attr] < min) { throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesUnderMin', 'oak-domain', { attributes: attr, threshold: `${min}`, }); } if (typeof max === 'number' && data[attr] > max) { throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesOverMax', 'oak-domain', { attributes: attr, threshold: `${max}`, }); } break; } case 'enum': { (0, assert_1.default)(enumeration); if (!enumeration.includes(data[attr])) { throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesNotInEnumeration'); } break; } } } else { // 这里似乎还有一种update中带cascade remove的case,等遇到再说(貌似cascadeUpdate没有处理完整这种情况) by Xc if (typeof data[attr] === 'object' && data[attr]?.action === 'remove') { console.warn('cascade remove可能是未处理的边界,请注意'); } } } } function createCreateCheckers(schema) { const checkers = []; for (const entity in schema) { const { attributes, actions } = schema[entity]; const notNullAttrs = Object.keys(attributes).filter(ele => attributes[ele].notNull); const updateActions = (0, lodash_1.difference)(actions, action_1.excludeUpdateActions); checkers.push({ entity, type: 'data', action: 'create', checker: (data) => { const checkData = (data2) => { const illegalNullAttrs = (0, lodash_1.difference)(notNullAttrs, Object.keys(data2).filter(ele => data2[ele] !== null)); if (illegalNullAttrs.length > 0) { const emtpyAttrs = []; // 要处理多对一的cascade create for (const attr of illegalNullAttrs) { if (attr === 'entityId') { if (illegalNullAttrs.includes('entity')) { continue; } } else if (attr === 'entity' && attributes[attr].ref) { let hasCascadeCreate = false; for (const ref of attributes[attr].ref) { if (data2[ref] && data2[ref].action === 'create') { hasCascadeCreate = true; break; } } if (hasCascadeCreate) { continue; } } else if (attributes[attr].type === 'ref') { const ref = attributes[attr].ref; const attr2 = attr.slice(0, attr.length - 2); if (data2[attr2] && data2[attr2].action === 'create') { continue; } } // 到这里说明确实是有not null的属性没有赋值 emtpyAttrs.push(attr); } if (emtpyAttrs.length > 0) { throw new Exception_1.OakAttrNotNullException(entity, emtpyAttrs); } } checkAttributeLegal(schema, entity, data2); }; if (data instanceof Array) { data.forEach(ele => checkData(ele)); } else { checkData(data); } } }, { entity, type: 'data', action: updateActions, checker: (data) => { checkAttributeLegal(schema, entity, data); } }); } return checkers; } exports.createCreateCheckers = createCreateCheckers;