diff --git a/lib/compiler/schemalBuilder.js b/lib/compiler/schemalBuilder.js index 58c2ced..e309cc2 100644 --- a/lib/compiler/schemalBuilder.js +++ b/lib/compiler/schemalBuilder.js @@ -1123,7 +1123,8 @@ function constructFilter(statements, entity) { case 'Int': case 'Uint': case 'Float': - case 'Double': { + case 'Double': + case 'Price': { type2 = factory.createTypeReferenceNode(factory.createIdentifier('Q_NumberValue')); break; } @@ -1276,7 +1277,8 @@ function constructProjection(statements, entity) { case 'File': case 'SingleGeo': case 'Geo': - case 'Object': { + case 'Object': + case 'Price': { properties.push([name_4, false]); break; } @@ -1639,7 +1641,8 @@ function constructSorter(statements, entity) { case 'Boolean': case 'Datetime': case 'Image': - case 'File': { + case 'File': + case 'Price': { type2 = factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); break; } @@ -2658,6 +2661,7 @@ var initialStatements = function () { return [ factory.createImportSpecifier(false, undefined, factory.createIdentifier('Text')), factory.createImportSpecifier(false, undefined, factory.createIdentifier('Datetime')), factory.createImportSpecifier(false, undefined, factory.createIdentifier('File')), + factory.createImportSpecifier(false, undefined, factory.createIdentifier('Price')), factory.createImportSpecifier(false, undefined, factory.createIdentifier('Image')), factory.createImportSpecifier(false, undefined, factory.createIdentifier('PrimaryKey')), factory.createImportSpecifier(false, undefined, factory.createIdentifier('ForeignKey')), @@ -2934,15 +2938,13 @@ function constructAttributes(entity) { ], true))); break; } - case 'Float': { - attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("type"), factory.createStringLiteral("float")), factory.createPropertyAssignment(factory.createIdentifier("params"), factory.createObjectLiteralExpression([ - factory.createPropertyAssignment(factory.createIdentifier("precision"), factory.createNumericLiteral(typeArguments[0].literal.text)), - factory.createPropertyAssignment(factory.createIdentifier("scale"), factory.createNumericLiteral(typeArguments[1].literal.text)) - ], true))); - break; - } - case 'Double': { - attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("type"), factory.createStringLiteral("double")), factory.createPropertyAssignment(factory.createIdentifier("params"), factory.createObjectLiteralExpression([ + case 'Double': + case 'Float': + case 'Decimal': { + if (['Double', 'Float'].includes(text)) { + console.warn("".concat(entity, "\u5BF9\u8C61\u4E2D\u8FD8\u6709").concat(text, "\u7C7B\u578B\u5B9A\u4E49\uFF0C\u73B0\u5728\u7EDF\u4E00\u7528Decimal\u8FDB\u884C\u5B58\u50A8")); + } + attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("type"), factory.createStringLiteral("decimal")), factory.createPropertyAssignment(factory.createIdentifier("params"), factory.createObjectLiteralExpression([ factory.createPropertyAssignment(factory.createIdentifier("precision"), factory.createNumericLiteral(typeArguments[0].literal.text)), factory.createPropertyAssignment(factory.createIdentifier("scale"), factory.createNumericLiteral(typeArguments[1].literal.text)) ], true))); @@ -2952,6 +2954,10 @@ function constructAttributes(entity) { attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("type"), factory.createStringLiteral("boolean"))); break; } + case 'Price': { + attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("type"), factory.createStringLiteral("money"))); + break; + } case 'Datetime': { attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("type"), factory.createStringLiteral("datetime"))); break; diff --git a/lib/entities/Modi.js b/lib/entities/Modi.js index 5beec49..ecd80f2 100644 --- a/lib/entities/Modi.js +++ b/lib/entities/Modi.js @@ -21,6 +21,7 @@ var indexes = [ ]; var locale = { zh_CN: { + name: '更新', attr: { targetEntity: '目标对象', entity: '关联对象', diff --git a/lib/entities/ModiEntity.js b/lib/entities/ModiEntity.js index 5fea048..0fc7a75 100644 --- a/lib/entities/ModiEntity.js +++ b/lib/entities/ModiEntity.js @@ -6,6 +6,7 @@ var config = { }; var locale = { zh_CN: { + name: '更新对象连接', attr: { modi: '更新', entity: '关联对象', diff --git a/lib/entities/Oper.js b/lib/entities/Oper.js index a572ebc..7050f0d 100644 --- a/lib/entities/Oper.js +++ b/lib/entities/Oper.js @@ -6,6 +6,7 @@ var configuration = { }; var locale = { zh_CN: { + name: '操作', attr: { action: '动作', data: '数据', diff --git a/lib/entities/OperEntity.js b/lib/entities/OperEntity.js index 251c14c..6ee0190 100644 --- a/lib/entities/OperEntity.js +++ b/lib/entities/OperEntity.js @@ -6,6 +6,7 @@ var config = { }; var locale = { zh_CN: { + name: '操作对象连接', attr: { oper: '操作', entity: '关联对象', diff --git a/lib/entities/User.js b/lib/entities/User.js index f7bd201..80f6424 100644 --- a/lib/entities/User.js +++ b/lib/entities/User.js @@ -8,6 +8,7 @@ var UserActionDef = { }; var locale = { zh_CN: { + name: '用户', attr: { name: '姓名', nickname: '昵称', diff --git a/lib/entities/UserEntityGrant.js b/lib/entities/UserEntityGrant.js index f186147..a3a9c65 100644 --- a/lib/entities/UserEntityGrant.js +++ b/lib/entities/UserEntityGrant.js @@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); ; var locale = { zh_CN: { + name: '用户授权', attr: { relation: '关系', entity: '关联对象', diff --git a/lib/store/AsyncRowStore.js b/lib/store/AsyncRowStore.js index 6bb86a7..f5058b0 100644 --- a/lib/store/AsyncRowStore.js +++ b/lib/store/AsyncRowStore.js @@ -64,93 +64,91 @@ var AsyncContext = /** @class */ (function () { }; AsyncContext.prototype.commit = function () { return tslib_1.__awaiter(this, void 0, void 0, function () { - var _a, _b, e, e_1_1; - var e_1, _c; - return tslib_1.__generator(this, function (_d) { - switch (_d.label) { + var commitEvents, commitEvents_1, commitEvents_1_1, e, e_1_1; + var e_1, _a; + return tslib_1.__generator(this, function (_b) { + switch (_b.label) { case 0: - if (!this.uuid) return [3 /*break*/, 10]; + if (!this.uuid) return [3 /*break*/, 9]; return [4 /*yield*/, this.rowStore.commit(this.uuid)]; case 1: - _d.sent(); + _b.sent(); this.uuid = undefined; - _d.label = 2; + commitEvents = this.events.commit; + this.resetEvents(); + _b.label = 2; case 2: - _d.trys.push([2, 7, 8, 9]); - _a = tslib_1.__values(this.events.commit), _b = _a.next(); - _d.label = 3; + _b.trys.push([2, 7, 8, 9]); + commitEvents_1 = tslib_1.__values(commitEvents), commitEvents_1_1 = commitEvents_1.next(); + _b.label = 3; case 3: - if (!!_b.done) return [3 /*break*/, 6]; - e = _b.value; + if (!!commitEvents_1_1.done) return [3 /*break*/, 6]; + e = commitEvents_1_1.value; return [4 /*yield*/, e()]; case 4: - _d.sent(); - _d.label = 5; + _b.sent(); + _b.label = 5; case 5: - _b = _a.next(); + commitEvents_1_1 = commitEvents_1.next(); return [3 /*break*/, 3]; case 6: return [3 /*break*/, 9]; case 7: - e_1_1 = _d.sent(); + e_1_1 = _b.sent(); e_1 = { error: e_1_1 }; return [3 /*break*/, 9]; case 8: try { - if (_b && !_b.done && (_c = _a.return)) _c.call(_a); + if (commitEvents_1_1 && !commitEvents_1_1.done && (_a = commitEvents_1.return)) _a.call(commitEvents_1); } finally { if (e_1) throw e_1.error; } return [7 /*endfinally*/]; - case 9: - this.resetEvents(); - _d.label = 10; - case 10: return [2 /*return*/]; + case 9: return [2 /*return*/]; } }); }); }; AsyncContext.prototype.rollback = function () { return tslib_1.__awaiter(this, void 0, void 0, function () { - var _a, _b, e, e_2_1; - var e_2, _c; - return tslib_1.__generator(this, function (_d) { - switch (_d.label) { + var rollbackEvents, rollbackEvents_1, rollbackEvents_1_1, e, e_2_1; + var e_2, _a; + return tslib_1.__generator(this, function (_b) { + switch (_b.label) { case 0: - if (!this.uuid) return [3 /*break*/, 10]; + if (!this.uuid) return [3 /*break*/, 9]; return [4 /*yield*/, this.rowStore.rollback(this.uuid)]; case 1: - _d.sent(); + _b.sent(); // console.log('rollback', this.uuid); this.uuid = undefined; - _d.label = 2; + rollbackEvents = this.events.rollback; + this.resetEvents(); + _b.label = 2; case 2: - _d.trys.push([2, 7, 8, 9]); - _a = tslib_1.__values(this.events.rollback), _b = _a.next(); - _d.label = 3; + _b.trys.push([2, 7, 8, 9]); + rollbackEvents_1 = tslib_1.__values(rollbackEvents), rollbackEvents_1_1 = rollbackEvents_1.next(); + _b.label = 3; case 3: - if (!!_b.done) return [3 /*break*/, 6]; - e = _b.value; + if (!!rollbackEvents_1_1.done) return [3 /*break*/, 6]; + e = rollbackEvents_1_1.value; return [4 /*yield*/, e()]; case 4: - _d.sent(); - _d.label = 5; + _b.sent(); + _b.label = 5; case 5: - _b = _a.next(); + rollbackEvents_1_1 = rollbackEvents_1.next(); return [3 /*break*/, 3]; case 6: return [3 /*break*/, 9]; case 7: - e_2_1 = _d.sent(); + e_2_1 = _b.sent(); e_2 = { error: e_2_1 }; return [3 /*break*/, 9]; case 8: try { - if (_b && !_b.done && (_c = _a.return)) _c.call(_a); + if (rollbackEvents_1_1 && !rollbackEvents_1_1.done && (_a = rollbackEvents_1.return)) _a.call(rollbackEvents_1); } finally { if (e_2) throw e_2.error; } return [7 /*endfinally*/]; - case 9: - this.resetEvents(); - _d.label = 10; - case 10: return [2 /*return*/]; + case 9: return [2 /*return*/]; } }); }); diff --git a/lib/store/checker.js b/lib/store/checker.js index 84a3be5..ba87dc9 100644 --- a/lib/store/checker.js +++ b/lib/store/checker.js @@ -339,12 +339,18 @@ function translateCascadeRelationFilterMaker(schema, lch, entity2, pathPrefix) { }; var filterMaker = paths.length ? translateFilterMakerIter(entity2, 0) : translateRelationFilter(entity2); if (!paths.length) { + // 不可能是create return function (oper, userId) { return filterMaker(userId); }; } /** * 针对第一层做一下特别优化,比如对象A指向对象B(多对一),如果A的cascadePath是 'B', * 当create A时,会带有Bid。此时生成该B对象上的相关表达式查询返回,可以避免必须将此判定在对象创建之后再做 * 另一使用场景是,在查询A时,如果带有Bid(在对象跳一对多子对象场景下很常见),可以提前判定这个查询对某些用户一定返回空集 + * + * 20230306: + * 在前台的权限判断中,会将list上的filter当成内在的限制对create动作进行判断,此时有一种可能是,filter并不能直接判断出外键,但会限制外键的查询范围。 + * 例如,在jichuang项目中,就存在park/list上,平台的用户去访问时,其查询条件是{ system: { platformId: 1 }};而用户的关系落在system.platform.platformProvider上, + * 此时如直接通过data上的外键判断就会失败,需要通过对filter上相应的语义解构,进行进一步的判断 */ var _a = tslib_1.__read(paths, 1), attr = _a[0]; var relation = (0, relation_1.judgeRelation)(schema, entity2, attr); @@ -352,54 +358,129 @@ function translateCascadeRelationFilterMaker(schema, lch, entity2, pathPrefix) { var filterMaker2 = paths.length > 1 ? (relation === 2 ? translateFilterMakerIter(attr, 1) : translateFilterMakerIter(relation, 1)) : (relation === 2 ? translateRelationFilter(attr) : translateRelationFilter(relation)); + var translateCreateFilterMaker = function (entity, filter, userId) { + var counters = []; + if (filter) { + if (relation === 2) { + if (filter.entity === entity && filter.entityId) { + // 这里对entityId的限定的数据只要和userId有一条relation,就不能否定可能会有创建动作(外键在最终create时,data上一定会有判定) + counters.push({ + $entity: attr, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: filter.entityId }), + }); + } + if (filter[attr]) { + counters.push({ + $entity: attr, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), filter[attr]), + }); + } + } + else { + (0, assert_1.default)(typeof relation === 'string'); + if (filter["".concat(attr, "Id")]) { + var filterMaker3 = paths.length > 1 ? translateFilterMakerIter(relation, 1) : translateRelationFilter(relation); + // 这里对attrId的限定的数据只要和userId有一条relation,就不能否定可能会有创建动作(外键在最终create时,data上一定会有判定) + counters.push({ + $entity: relation, + $filter: (0, filter_1.addFilterSegment)(filterMaker3(userId), { id: filter["".concat(attr, "Id")] }), + }); + } + if (filter[attr]) { + counters.push({ + $entity: relation, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), filter[attr]), + }); + } + } + if (filter.$and) { + var countersAnd = filter.$and.map(function (ele) { return translateCreateFilterMaker(entity, ele, userId); }); + // and 只要有一个满足就行 + var ca2 = countersAnd.filter(function (ele) { return !(ele instanceof Exception_1.OakUserUnpermittedException); }); + counters.push.apply(counters, tslib_1.__spreadArray([], tslib_1.__read(ca2), false)); + } + if (filter.$or) { + var countersOr = filter.$and.map(function (ele) { return translateCreateFilterMaker(entity, ele, userId); }); + // or也只要有一个满足就行(不能否定) + var co2 = countersOr.filter(function (ele) { return !(ele instanceof Exception_1.OakUserUnpermittedException); }); + counters.push.apply(counters, tslib_1.__spreadArray([], tslib_1.__read(co2), false)); + } + } + if (counters.length === 0) { + // 一个counter都找不出来,说明当前路径上不满足 + return new Exception_1.OakUserUnpermittedException(); + } + else if (counters.length === 1) { + return counters[0]; + } + // 是or关系,只要其中有一个满足就可以通过 + return { + $$or: counters, + }; + }; return function (operation, userId) { var action = operation.action; if (action === 'create') { var data = operation.data; - var getForeignKeyId_1 = function (d) { + if (data) { + // 有data的情形根据data判定 + var getForeignKeyId_1 = function (d) { + if (relation === 2) { + if (d.entity === attr && typeof d.entityId === 'string') { + return d.entityId; + } + throw new Exception_1.OakUserUnpermittedException(); + } + else { + (0, assert_1.default)(typeof relation === 'string'); + if (typeof d["".concat(attr, "Id")] === 'string') { + return d["".concat(attr, "Id")]; + } + throw new Exception_1.OakUserUnpermittedException(); + } + }; if (relation === 2) { - if (d.entity === attr && typeof d.entityId === 'string') { - return d.entitId; + if (data instanceof Array) { + var fkIds = (0, lodash_1.uniq)(data.map(function (d) { return getForeignKeyId_1(d); })); + return { + $entity: attr, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: { $in: fkIds } }), + $count: fkIds.length, + }; } - throw new Exception_1.OakUserUnpermittedException(); + var fkId_1 = getForeignKeyId_1(data); + return { + $entity: attr, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: fkId_1 }), + }; } - else { - (0, assert_1.default)(typeof relation === 'string'); - if (typeof d["".concat(attr, "Id")] === 'string') { - return d["".concat(attr, "Id")]; - } - throw new Exception_1.OakUserUnpermittedException(); - } - }; - if (relation === 2) { + (0, assert_1.default)(typeof relation === 'string'); if (data instanceof Array) { var fkIds = (0, lodash_1.uniq)(data.map(function (d) { return getForeignKeyId_1(d); })); return { - $entity: attr, + $entity: relation, $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: { $in: fkIds } }), $count: fkIds.length, }; } - var fkId_1 = getForeignKeyId_1(data); - return { - $entity: attr, - $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: fkId_1 }), - }; - } - (0, assert_1.default)(typeof relation === 'string'); - if (data instanceof Array) { - var fkIds = (0, lodash_1.uniq)(data.map(function (d) { return getForeignKeyId_1(d); })); + var fkId = getForeignKeyId_1(data); return { $entity: relation, - $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: { $in: fkIds } }), - $count: fkIds.length, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: fkId }), }; } - var fkId = getForeignKeyId_1(data); - return { - $entity: relation, - $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: fkId }), - }; + else { + // todo + var filter_4 = operation.filter; + if (filter_4) { + var counter = translateCreateFilterMaker(entity2, filter_4, userId); + if (counter instanceof Exception_1.OakUserUnpermittedException) { + throw counter; + } + return counter; + } + throw new Exception_1.OakUserUnpermittedException(); + } } var filter = operation.filter; if (relation === 2 && (filter === null || filter === void 0 ? void 0 : filter.entity) === attr && (filter === null || filter === void 0 ? void 0 : filter.entityId)) { @@ -450,8 +531,77 @@ function translateActionAuthFilterMaker(schema, relationItem, entity, pathPrefix var filterMaker = translateCascadeRelationFilterMaker(schema, relationItem, entity, pathPrefix); return filterMaker; } +function execCreateCounter(context, counter) { + if (counter === null || counter === void 0 ? void 0 : counter.$$and) { + // 每个counter都要满足才能过 + var counters = counter === null || counter === void 0 ? void 0 : counter.$$and; + (0, assert_1.default)(counters.length > 0); + var counterResults = counters.map(function (ele) { return execCreateCounter(context, ele); }); + if (counterResults[0] instanceof Promise) { + return Promise.all(counterResults) + .then(function (cr2) { + var unpermitted = cr2.find(function (ele) { return ele instanceof Exception_1.OakUserUnpermittedException; }); + if (unpermitted) { + return unpermitted; + } + return undefined; + }); + } + else { + var unpermitted = counterResults.find(function (ele) { return ele instanceof Exception_1.OakUserUnpermittedException; }); + if (unpermitted) { + return unpermitted; + } + else { + return undefined; + } + } + } + else if (counter === null || counter === void 0 ? void 0 : counter.$$or) { + // 只要有一个counter能过就算过 + var counters = counter === null || counter === void 0 ? void 0 : counter.$$or; + (0, assert_1.default)(counters.length > 0); + var counterResults = counters.map(function (ele) { return execCreateCounter(context, ele); }); + if (counterResults[0] instanceof Promise) { + return Promise.all(counterResults) + .then(function (cr2) { + var permittedIdx = cr2.indexOf(undefined); + if (permittedIdx !== -1) { + return undefined; + } + return new Exception_1.OakUserUnpermittedException(); + }); + } + else { + var permittedIndex = counterResults.indexOf(undefined); + if (permittedIndex !== -1) { + return undefined; + } + else { + return new Exception_1.OakUserUnpermittedException(); + } + } + } + else if (counter === null || counter === void 0 ? void 0 : counter.$entity) { + var _a = counter, $entity = _a.$entity, $filter = _a.$filter, _b = _a.$count, $count_1 = _b === void 0 ? 1 : _b; + var count = context.count($entity, { + filter: $filter, + }, { dontCollect: true }); + if (count instanceof Promise) { + return count.then(function (c2) { + if (c2 >= $count_1) { + return undefined; + } + return new Exception_1.OakUserUnpermittedException(); + }); + } + else { + return count >= $count_1 ? undefined : new Exception_1.OakUserUnpermittedException(); + } + } +} function makePotentialFilter(operation, context, filterMaker) { - var e_1, _a; + var e_1, _a, e_2, _b; var userId = context.getCurrentUserId(); (0, assert_1.default)(userId); var filters = filterMaker instanceof Array ? filterMaker.map(function (ele) { @@ -470,103 +620,81 @@ function makePotentialFilter(operation, context, filterMaker) { */ var filtersOr = []; var isAsyncOr = false; - var _loop_1 = function (f) { - var e_2, _b; - if (f instanceof Array) { - var isAsyncAnd = true; - var filtersAnd = []; - var _loop_2 = function (ff) { - if (ff === null || ff === void 0 ? void 0 : ff.$entity) { - var _e = ff, $entity = _e.$entity, $filter = _e.$filter, _f = _e.$count, $count_1 = _f === void 0 ? 1 : _f; - var count = context.count($entity, { - filter: $filter, - }, {}); - if (count instanceof Promise) { - isAsyncAnd = true; - filtersAnd.push(count.then(function (c2) { - if (c2 >= $count_1) { - return undefined; - } - return new Exception_1.OakUserUnpermittedException(); - })); - } - else { - filtersAnd.push(count >= $count_1 ? undefined : new Exception_1.OakUserUnpermittedException()); - } - } - else if (ff) { - filtersAnd.push(ff); - } - }; - try { - for (var f_1 = (e_2 = void 0, tslib_1.__values(f)), f_1_1 = f_1.next(); !f_1_1.done; f_1_1 = f_1.next()) { - var ff = f_1_1.value; - _loop_2(ff); - } - } - catch (e_2_1) { e_2 = { error: e_2_1 }; } - finally { - try { - if (f_1_1 && !f_1_1.done && (_b = f_1.return)) _b.call(f_1); - } - finally { if (e_2) throw e_2.error; } - } - if (isAsyncAnd = true) { - isAsyncOr = true; - filtersOr.push(isAsyncAnd ? Promise.all(filtersAnd).then(function (fa) { - var e_3, _a; - var faR = []; - try { - for (var fa_1 = (e_3 = void 0, tslib_1.__values(fa)), fa_1_1 = fa_1.next(); !fa_1_1.done; fa_1_1 = fa_1.next()) { - var faItem = fa_1_1.value; - if (faItem instanceof Exception_1.OakUserUnpermittedException) { - return faItem; - } - else if (faItem) { - faR.push(faItem); - } - } - } - catch (e_3_1) { e_3 = { error: e_3_1 }; } - finally { - try { - if (fa_1_1 && !fa_1_1.done && (_a = fa_1.return)) _a.call(fa_1); - } - finally { if (e_3) throw e_3.error; } - } - if (faR.length > 0) { - return { - $and: faR, - }; - } - }) : { - $and: filtersAnd, - }); - } - } - else { - if (f === null || f === void 0 ? void 0 : f.$entity) { - var _c = f, $entity = _c.$entity, $filter = _c.$filter, _d = _c.$count, $count_2 = _d === void 0 ? 1 : _d; - var count = context.count($entity, { - filter: $filter, - }, {}); - if (count instanceof Promise) { - isAsyncOr = true; - filtersOr.push(count.then(function (c2) { return c2 >= $count_2 ? undefined : new Exception_1.OakUserUnpermittedException(); })); - } - else { - filtersOr.push(count >= $count_2 ? undefined : new Exception_1.OakUserUnpermittedException()); - } - } - else if (f) { - filtersOr.push(f); - } - } - }; try { for (var filters_1 = tslib_1.__values(filters), filters_1_1 = filters_1.next(); !filters_1_1.done; filters_1_1 = filters_1.next()) { var f = filters_1_1.value; - _loop_1(f); + if (f instanceof Array) { + var isAsyncAnd = false; + (0, assert_1.default)(f.length > 0); + var filtersAnd = []; + try { + for (var f_1 = (e_2 = void 0, tslib_1.__values(f)), f_1_1 = f_1.next(); !f_1_1.done; f_1_1 = f_1.next()) { + var ff = f_1_1.value; + if ((ff === null || ff === void 0 ? void 0 : ff.$$and) || (ff === null || ff === void 0 ? void 0 : ff.$$or) || (ff === null || ff === void 0 ? void 0 : ff.$entity)) { + // 每个counter都要满足才能过 + var result = execCreateCounter(context, ff); + if (result instanceof Promise) { + isAsyncAnd = true; + } + filtersAnd.push(result); + } + else if (ff) { + filtersAnd.push(ff); + } + } + } + catch (e_2_1) { e_2 = { error: e_2_1 }; } + finally { + try { + if (f_1_1 && !f_1_1.done && (_b = f_1.return)) _b.call(f_1); + } + finally { if (e_2) throw e_2.error; } + } + if (isAsyncAnd = true) { + isAsyncOr = true; + filtersOr.push(isAsyncAnd ? Promise.all(filtersAnd).then(function (fa) { + var e_3, _a; + var faR = []; + try { + for (var fa_1 = (e_3 = void 0, tslib_1.__values(fa)), fa_1_1 = fa_1.next(); !fa_1_1.done; fa_1_1 = fa_1.next()) { + var faItem = fa_1_1.value; + if (faItem instanceof Exception_1.OakUserUnpermittedException) { + return faItem; + } + else if (faItem) { + faR.push(faItem); + } + } + } + catch (e_3_1) { e_3 = { error: e_3_1 }; } + finally { + try { + if (fa_1_1 && !fa_1_1.done && (_a = fa_1.return)) _a.call(fa_1); + } + finally { if (e_3) throw e_3.error; } + } + if (faR.length > 0) { + return { + $and: faR, + }; + } + }) : { + $and: filtersAnd, + }); + } + } + else { + if ((f === null || f === void 0 ? void 0 : f.$$and) || (f === null || f === void 0 ? void 0 : f.$$or) || (f === null || f === void 0 ? void 0 : f.$entity)) { + var counterResults = execCreateCounter(context, f); + if (counterResults instanceof Promise) { + isAsyncOr = true; + } + filtersOr.push(counterResults); + } + else if (f) { + filtersOr.push(f); + } + } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } @@ -603,18 +731,30 @@ function makePotentialFilter(operation, context, filterMaker) { */ function createAuthCheckers(schema, authDict) { var checkers = []; - var _loop_3 = function (entity) { + var _loop_1 = function (entity) { var _a; if (authDict[entity]) { var _b = authDict[entity], relationAuth = _b.relationAuth, actionAuth = _b.actionAuth; if (relationAuth) { var raFilterMakerDict_1 = {}; var userEntityName = "user".concat((0, string_1.firstLetterUpperCase)(entity)); + var allAuthItem = []; for (var r in relationAuth) { + var authItem = relationAuth[r]; Object.assign(raFilterMakerDict_1, (_a = {}, - _a[r] = translateActionAuthFilterMaker(schema, relationAuth[r], userEntityName, entity), + _a[r] = translateActionAuthFilterMaker(schema, authItem, userEntityName, entity), _a)); + if (authItem instanceof Array) { + allAuthItem.push.apply(allAuthItem, tslib_1.__spreadArray([], tslib_1.__read(authItem), false)); + } + else { + allAuthItem.push(authItem); + } } + // 如果不指定relation,则使用所有的authItem的or组合 + Object.assign(raFilterMakerDict_1, { + '@@all': translateActionAuthFilterMaker(schema, allAuthItem, userEntityName, entity), + }); var entityIdAttr_1 = "".concat(entity, "Id"); checkers.push({ entity: userEntityName, @@ -624,8 +764,12 @@ function createAuthCheckers(schema, authDict) { var data = operation.data; (0, assert_1.default)(!(data instanceof Array)); var _a = data, relation = _a.relation, _b = entityIdAttr_1, entityId = _a[_b]; + if (!relation) { + // 不指定relation测试是否有创建权限 + return makePotentialFilter(operation, context, raFilterMakerDict_1['@@all']); + } if (!raFilterMakerDict_1[relation]) { - return; + throw new Exception_1.OakUserUnpermittedException(); } var filter = makePotentialFilter(operation, context, raFilterMakerDict_1[relation]); return filter; @@ -694,7 +838,7 @@ function createAuthCheckers(schema, authDict) { // todo 等实现的时候再写 } if (actionAuth) { - var _loop_4 = function (a) { + var _loop_2 = function (a) { var filterMaker = translateActionAuthFilterMaker(schema, actionAuth[a], entity); checkers.push({ entity: entity, @@ -709,13 +853,13 @@ function createAuthCheckers(schema, authDict) { }); }; for (var a in actionAuth) { - _loop_4(a); + _loop_2(a); } } } }; for (var entity in schema) { - _loop_3(entity); + _loop_1(entity); } return checkers; } @@ -784,7 +928,7 @@ function createRemoveCheckers(schema, authDict) { } // 当删除一时,要确认多上面没有指向一的数据 var entities = (0, lodash_1.union)(Object.keys(OneToManyMatrix), Object.keys(OneToManyOnEntityMatrix)); - var _loop_5 = function (entity) { + var _loop_3 = function (entity) { checkers.push({ entity: entity, action: 'remove', @@ -793,7 +937,7 @@ function createRemoveCheckers(schema, authDict) { var e_6, _a, e_7, _b; var promises = []; if (OneToManyMatrix[entity]) { - var _loop_7 = function (otm) { + var _loop_5 = function (otm) { var _g, _h; var _j = tslib_1.__read(otm, 2), e = _j[0], attr = _j[1]; var proj = (_g = { @@ -832,7 +976,7 @@ function createRemoveCheckers(schema, authDict) { try { for (var _c = (e_6 = void 0, tslib_1.__values(OneToManyMatrix[entity])), _d = _c.next(); !_d.done; _d = _c.next()) { var otm = _d.value; - _loop_7(otm); + _loop_5(otm); } } catch (e_6_1) { e_6 = { error: e_6_1 }; } @@ -844,7 +988,7 @@ function createRemoveCheckers(schema, authDict) { } } if (OneToManyOnEntityMatrix[entity]) { - var _loop_8 = function (otm) { + var _loop_6 = function (otm) { var _l, _m, _o; var proj = { id: 1, @@ -890,7 +1034,7 @@ function createRemoveCheckers(schema, authDict) { try { for (var _e = (e_7 = void 0, tslib_1.__values(OneToManyOnEntityMatrix[entity])), _f = _e.next(); !_f.done; _f = _e.next()) { var otm = _f.value; - _loop_8(otm); + _loop_6(otm); } } catch (e_7_1) { e_7 = { error: e_7_1 }; } @@ -910,7 +1054,7 @@ function createRemoveCheckers(schema, authDict) { try { for (var entities_1 = tslib_1.__values(entities), entities_1_1 = entities_1.next(); !entities_1_1.done; entities_1_1 = entities_1.next()) { var entity = entities_1_1.value; - _loop_5(entity); + _loop_3(entity); } } catch (e_4_1) { e_4 = { error: e_4_1 }; } @@ -920,13 +1064,13 @@ function createRemoveCheckers(schema, authDict) { } finally { if (e_4) throw e_4.error; } } - var _loop_6 = function (entity) { + var _loop_4 = function (entity) { var e_8, _b; var cascadeRemove = authDict[entity].cascadeRemove; if (cascadeRemove) { var entitiesOnEntityAttr = []; var hasAllEntity = false; - var _loop_9 = function (attr) { + var _loop_7 = function (attr) { if (attr === '@entity') { hasAllEntity = true; return "continue"; @@ -1001,13 +1145,13 @@ function createRemoveCheckers(schema, authDict) { } }; for (var attr in cascadeRemove) { - _loop_9(attr); + _loop_7(attr); } if (hasAllEntity) { var attributes = schema[entity].attributes; var ref = attributes.entity.ref; var restEntities = (0, lodash_1.difference)(ref, entitiesOnEntityAttr); - var _loop_10 = function (e) { + var _loop_8 = function (e) { checkers.push({ entity: e, action: 'remove', @@ -1043,7 +1187,7 @@ function createRemoveCheckers(schema, authDict) { try { for (var restEntities_1 = (e_8 = void 0, tslib_1.__values(restEntities)), restEntities_1_1 = restEntities_1.next(); !restEntities_1_1.done; restEntities_1_1 = restEntities_1.next()) { var e = restEntities_1_1.value; - _loop_10(e); + _loop_8(e); } } catch (e_8_1) { e_8 = { error: e_8_1 }; } @@ -1058,7 +1202,7 @@ function createRemoveCheckers(schema, authDict) { }; // 注入声明的cascade删除时的外键处理动作 for (var entity in authDict) { - _loop_6(entity); + _loop_4(entity); } return checkers; } diff --git a/lib/types/DataType.d.ts b/lib/types/DataType.d.ts index 1c81265..e3b63be 100644 --- a/lib/types/DataType.d.ts +++ b/lib/types/DataType.d.ts @@ -8,11 +8,14 @@ export declare type Text = string; export declare type Image = string; export declare type File = string; export declare type Datetime = number | Date; +export declare type Day = number | Date; +export declare type Time = number | Date; export declare type Boolean = boolean; +export declare type Price = number; export declare type PrimaryKey = string; export declare type ForeignKey = string; export declare type Sequence = string; export { Geo, SingleGeo } from './Geo'; -export declare type DataTypes = number | string | Datetime | Geo | Object | SingleGeo; +export declare type DataTypes = number | string | Datetime | Day | Time | Geo | Object | SingleGeo; export declare const types: string[]; export declare const unIndexedTypes: string[]; diff --git a/lib/types/DataType.js b/lib/types/DataType.js index a582716..cbbc7fc 100644 --- a/lib/types/DataType.js +++ b/lib/types/DataType.js @@ -1,5 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.unIndexedTypes = exports.types = void 0; -exports.types = ['Int', 'Uint', 'Double', 'Float', 'String', 'Text', 'Datetime', 'Boolean', 'Image', 'File', 'Geo', 'SingleGeo']; +exports.types = ['Int', 'Uint', 'Double', 'Float', 'String', 'Text', 'Datetime', 'Day', 'Time', + 'Boolean', 'Image', 'File', 'Geo', 'SingleGeo', 'Price']; exports.unIndexedTypes = ['Text', 'Image', 'File', 'Object']; diff --git a/lib/types/Locale.d.ts b/lib/types/Locale.d.ts index 108740e..daac80a 100644 --- a/lib/types/Locale.d.ts +++ b/lib/types/Locale.d.ts @@ -13,6 +13,7 @@ declare type LocaleOfValue> = { }; export declare type LocaleDef, Ac extends string, R extends string, V extends Record> = { [L in Language]?: { + name: string; attr: LocaleOfSchema & { [A in keyof V]: string; }; diff --git a/lib/types/Style.d.ts b/lib/types/Style.d.ts new file mode 100644 index 0000000..2c643b1 --- /dev/null +++ b/lib/types/Style.d.ts @@ -0,0 +1,11 @@ +import { EntityDict } from './Entity'; +import { EntityDict as BaseEntityDict } from '../base-app-domain'; +declare type ThemeColor = 'default' | 'success' | 'warning' | 'error'; +export declare type ColorDict = { + [T in keyof ED]?: { + [A in keyof ED[T]['OpSchema']]?: { + [E in ED[T]['OpSchema'][A]]?: ThemeColor | `#${string}`; + }; + }; +}; +export {}; diff --git a/lib/types/Style.js b/lib/types/Style.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/lib/types/Style.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/types/Timer.d.ts b/lib/types/Timer.d.ts index c3d869a..6fb8f24 100644 --- a/lib/types/Timer.d.ts +++ b/lib/types/Timer.d.ts @@ -1,5 +1,6 @@ +import { RecurrenceRule, RecurrenceSpecDateRange, RecurrenceSpecObjLit } from 'node-schedule'; import { EntityDict } from './Entity'; -import { AsyncContext } from "../store/AsyncRowStore"; +import { AsyncContext } from '../store/AsyncRowStore'; declare type RoutineFn> = (context: Cxt) => Promise; export declare type Routine> = { name: string; @@ -7,7 +8,7 @@ export declare type Routine> }; export declare type Timer> = { name: string; - cron: string; + cron: RecurrenceRule | RecurrenceSpecDateRange | RecurrenceSpecObjLit | Date | string | number; fn: RoutineFn; }; export {}; diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts index dcb0909..80d8ac5 100644 --- a/lib/types/index.d.ts +++ b/lib/types/index.d.ts @@ -19,3 +19,4 @@ export * from './Connector'; export * from './Timer'; export * from './Port'; export * from './Endpoint'; +export * from './Style'; diff --git a/lib/types/index.js b/lib/types/index.js index 986fd63..563f99b 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -22,3 +22,4 @@ tslib_1.__exportStar(require("./Connector"), exports); tslib_1.__exportStar(require("./Timer"), exports); tslib_1.__exportStar(require("./Port"), exports); tslib_1.__exportStar(require("./Endpoint"), exports); +tslib_1.__exportStar(require("./Style"), exports); diff --git a/lib/types/schema/DataTypes.d.ts b/lib/types/schema/DataTypes.d.ts index e3c9fba..b6479f5 100644 --- a/lib/types/schema/DataTypes.d.ts +++ b/lib/types/schema/DataTypes.d.ts @@ -29,4 +29,6 @@ export interface DataTypeParams { precision?: number; scale?: number; signed?: boolean; + max?: number; + min?: number; } diff --git a/lib/utils/SimpleConnector.js b/lib/utils/SimpleConnector.js index 3809ea4..b5333cd 100644 --- a/lib/utils/SimpleConnector.js +++ b/lib/utils/SimpleConnector.js @@ -10,7 +10,7 @@ function makeContentTypeAndBody(data) { if (process.env.OAK_PLATFORM !== 'wechatMp') { if (data instanceof FormData) { return { - contentType: 'multipart/form-data', + // contentType: 'multipart/form-data', body: data, }; } @@ -39,11 +39,12 @@ var SimpleConnector = /** @class */ (function (_super) { _a = makeContentTypeAndBody(params), contentType = _a.contentType, body = _a.body; return [4 /*yield*/, global.fetch(this.serverUrl, { method: 'POST', - headers: { - 'Content-Type': contentType, + headers: Object.assign({ 'oak-cxt': cxtStr, 'oak-aspect': name, - }, + }, contentType && { + 'Content-Type': contentType, + }), body: body, })]; case 1: @@ -106,7 +107,7 @@ var SimpleConnector = /** @class */ (function (_super) { }); }; SimpleConnector.prototype.serializeResult = function (result, context, headers, body) { - if (result instanceof stream_1.Stream) { + if (result instanceof stream_1.Stream || result instanceof Buffer) { return { body: result, }; diff --git a/lib/utils/cron.d.ts b/lib/utils/cron.d.ts deleted file mode 100644 index 40db6b9..0000000 --- a/lib/utils/cron.d.ts +++ /dev/null @@ -1 +0,0 @@ -export declare function schedule(cron: string, fn: (date: Date) => any): void; diff --git a/lib/utils/cron.js b/lib/utils/cron.js deleted file mode 100644 index 68b60cd..0000000 --- a/lib/utils/cron.js +++ /dev/null @@ -1,18 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.schedule = void 0; -var tslib_1 = require("tslib"); -var cronjs_matcher_1 = require("@datasert/cronjs-matcher"); -var dayjs_1 = tslib_1.__importDefault(require("dayjs")); -function schedule(cron, fn) { - var futureMatches = (0, cronjs_matcher_1.getFutureMatches)(cron, { - matchCount: 1, - }); - var date = (0, dayjs_1.default)(futureMatches[0]); - var interval = date.diff((0, dayjs_1.default)(), 'ms'); - setTimeout(function () { - fn(new Date()); - schedule(cron, fn); - }, interval); -} -exports.schedule = schedule; diff --git a/lib/utils/mask.d.ts b/lib/utils/mask.d.ts new file mode 100644 index 0000000..56ea119 --- /dev/null +++ b/lib/utils/mask.d.ts @@ -0,0 +1,5 @@ +declare const maskIdCard: (idCardNumber: string) => string; +declare const maskMobile: (mobile: string) => string; +declare const maskName: (name: string) => string; +declare const maskStar: (str: string, front: number, end: number, star: string) => string; +export { maskIdCard, maskMobile, maskName, maskStar, }; diff --git a/lib/utils/mask.js b/lib/utils/mask.js new file mode 100644 index 0000000..303ce15 --- /dev/null +++ b/lib/utils/mask.js @@ -0,0 +1,35 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.maskStar = exports.maskName = exports.maskMobile = exports.maskIdCard = void 0; +var maskIdCard = function (idCardNumber) { + if (!idCardNumber instanceof String) { + throw new Error("身份证号码必须是String类型"); + } + var begin = idCardNumber.slice(0, 4); + var end = idCardNumber.slice(idCardNumber.length - 4, 4); + for (var i = 0; i < idCardNumber.length - 8; i++) { + begin = begin.concat("*"); + } + return begin.concat(end); +}; +exports.maskIdCard = maskIdCard; +var maskMobile = function (mobile) { + var begin = mobile.slice(0, 3); + var end = mobile.slice(7, 11); + return begin.concat("****").concat(end); +}; +exports.maskMobile = maskMobile; +var maskName = function (name) { + return name.slice(0, name.length - 1).concat("*"); +}; +exports.maskName = maskName; +var maskStar = function (str, frontLen, endLen, star) { + if (star === void 0) { star = '*'; } + var len = str.length - frontLen - endLen; + var xing = ''; + for (var i = 0; i < len; i++) { + xing += star; + } + return str.substring(0, frontLen) + xing + str.substring(str.length - endLen); +}; +exports.maskStar = maskStar; diff --git a/lib/utils/money.d.ts b/lib/utils/money.d.ts new file mode 100644 index 0000000..75a3307 --- /dev/null +++ b/lib/utils/money.d.ts @@ -0,0 +1,5 @@ +declare const ToCent: (float: number) => number; +declare const ToYuan: (int: number) => number; +declare const StringToCent: (value: string, allowNegative?: true) => number | undefined; +declare const CentToString: (value: number) => string | undefined; +export { ToCent, ToYuan, StringToCent, CentToString, }; diff --git a/lib/utils/money.js b/lib/utils/money.js new file mode 100644 index 0000000..0790b96 --- /dev/null +++ b/lib/utils/money.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CentToString = exports.StringToCent = exports.ToYuan = exports.ToCent = void 0; +var ToCent = function (float) { + return Math.round(float * 100); +}; +exports.ToCent = ToCent; +var ToYuan = function (int) { + return Math.round(int) / 100; +}; +exports.ToYuan = ToYuan; +var StringToCent = function (value, allowNegative) { + var numValue = parseInt(value, 10); + if (typeof numValue === 'number' && (numValue >= 0 || allowNegative)) { + return ToCent(numValue); + } +}; +exports.StringToCent = StringToCent; +var CentToString = function (value) { + if (typeof value === 'number') { + return "".concat(ToYuan(value)); + } +}; +exports.CentToString = CentToString; diff --git a/lib/utils/validator.d.ts b/lib/utils/validator.d.ts index 8c52447..08a892a 100644 --- a/lib/utils/validator.d.ts +++ b/lib/utils/validator.d.ts @@ -16,6 +16,7 @@ export declare const isNickname: ValidatorFunction; export declare const isSizedCaptcha: ValidatorFunction; export declare const isDigital: ValidatorFunction; export declare const isPhone: ValidatorFunction; +export declare const isTel: ValidatorFunction; export declare const isNumber: ValidatorFunction; export declare const isMoney: ValidatorMoneyFunction; export declare const isVehicleNumber: ValidatorFunction; diff --git a/lib/utils/validator.js b/lib/utils/validator.js index a44c918..746ac13 100644 --- a/lib/utils/validator.js +++ b/lib/utils/validator.js @@ -3,7 +3,7 @@ */ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); -exports.checkAttributesScope = exports.checkAttributesNotNull = exports.isVehicleNumber = exports.isMoney = exports.isNumber = exports.isPhone = exports.isDigital = exports.isSizedCaptcha = exports.isNickname = exports.isUrl = exports.isSoldierNumber = exports.isBirthNumber = exports.isTwCardNumber = exports.isAmCardNumber = exports.isHkCardNumber = exports.isPassportNumber = exports.isIdCardNumber = exports.isCaptcha = exports.isPassword = exports.isMobile = void 0; +exports.checkAttributesScope = exports.checkAttributesNotNull = exports.isVehicleNumber = exports.isMoney = exports.isNumber = exports.isTel = exports.isPhone = exports.isDigital = exports.isSizedCaptcha = exports.isNickname = exports.isUrl = exports.isSoldierNumber = exports.isBirthNumber = exports.isTwCardNumber = exports.isAmCardNumber = exports.isHkCardNumber = exports.isPassportNumber = exports.isIdCardNumber = exports.isCaptcha = exports.isPassword = exports.isMobile = void 0; var types_1 = require("../types"); var isMobile = function (text) { return ((text) && (typeof text === "string") && ((/^1[3|4|5|6|7|8|9]\d{9}$/.test(text)))); @@ -79,14 +79,19 @@ var isPhone = function (phone) { return /^(\(\d{3,4}\)|\d{3,4}-)?\d{7,8}$/.test(phone); }; exports.isPhone = isPhone; +var isTel = function (text) { + // 1、133xxxx4545 2、0571-630xx239 3、400-123-1400 + var reg = /^(((\d{3,4}-)?[0-9]{7,8})|(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}|((400)-(\d{3})-(\d{4})(.)(\d{1,4})|(400)-(\d{3})-(\d{4}$)|(400)(\d{3})(\d{4}$)|(400)-(\d{4})-(\d{3}$)))$/.test(text); + return reg; +}; +exports.isTel = isTel; var isNumber = function (str) { return /^[0-9]*$/.test(str); }; exports.isNumber = isNumber; var isMoney = function (str, zero) { - // zero为true包含零 + // 金额,最多可以有两位小数 zero为true包含零 if (zero) { - // 金额,最多可以有两位小数 return /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/.test(str); } return /(^[1-9](\d+)?(\.\d{1,2})?$)|(^\d\.\d{1,2}$)/.test(str); diff --git a/package.json b/package.json index a97f249..e02784f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oak-domain", - "version": "2.6.3", + "version": "2.6.4", "author": { "name": "XuChang" }, @@ -27,6 +27,7 @@ "@types/luxon": "^2.0.9", "@types/mocha": "^8.2.0", "@types/node": "^14.14.25", + "@types/node-schedule": "^2.1.0", "@types/react": "^17.0.2", "@types/uuid": "^8.3.0", "@types/wechat-miniprogram": "^3.4.1", @@ -40,9 +41,9 @@ "typescript": "^4.7.4" }, "dependencies": { - "@datasert/cronjs-matcher": "^1.2.0", "dayjs": "^1.11.5", "lodash": "^4.17.21", + "node-schedule": "^2.1.1", "uuid": "^9.0.0" } } diff --git a/src/compiler/schemalBuilder.ts b/src/compiler/schemalBuilder.ts index 1ff74a0..2105bf9 100644 --- a/src/compiler/schemalBuilder.ts +++ b/src/compiler/schemalBuilder.ts @@ -1642,7 +1642,8 @@ function constructFilter(statements: Array, entity: string) { case 'Int': case 'Uint': case 'Float': - case 'Double': { + case 'Double': + case 'Price': { type2 = factory.createTypeReferenceNode( factory.createIdentifier('Q_NumberValue'), ); @@ -1897,7 +1898,8 @@ function constructProjection(statements: Array, entity: string) { case 'File': case 'SingleGeo': case 'Geo': - case 'Object': { + case 'Object': + case 'Price': { properties.push( [name, false] ) @@ -2410,7 +2412,8 @@ function constructSorter(statements: Array, entity: string) { case 'Boolean': case 'Datetime': case 'Image': - case 'File': { + case 'File': + case 'Price': { type2 = factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); break; } @@ -4417,6 +4420,11 @@ const initialStatements = () => [ undefined, factory.createIdentifier('File') ), + factory.createImportSpecifier( + false, + undefined, + factory.createIdentifier('Price') + ), factory.createImportSpecifier( false, undefined, @@ -5332,40 +5340,16 @@ function constructAttributes(entity: string): ts.PropertyAssignment[] { ); break; } - case 'Float': { + case 'Double': + case 'Float': + case 'Decimal': { + if (['Double', 'Float'].includes(text)) { + console.warn(`${entity}对象中还有${text}类型定义,现在统一用Decimal进行存储`); + } attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), - factory.createStringLiteral("float") - ), - factory.createPropertyAssignment( - factory.createIdentifier("params"), - factory.createObjectLiteralExpression( - [ - factory.createPropertyAssignment( - factory.createIdentifier("precision"), - factory.createNumericLiteral( - ((typeArguments![0]).literal).text - ) - ), - factory.createPropertyAssignment( - factory.createIdentifier("scale"), - factory.createNumericLiteral( - ((typeArguments![1]).literal).text - ) - ) - ], - true - ) - ) - ); - break; - } - case 'Double': { - attrAssignments.push( - factory.createPropertyAssignment( - factory.createIdentifier("type"), - factory.createStringLiteral("double") + factory.createStringLiteral("decimal") ), factory.createPropertyAssignment( factory.createIdentifier("params"), @@ -5399,6 +5383,15 @@ function constructAttributes(entity: string): ts.PropertyAssignment[] { ); break; } + case 'Price': { + attrAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("type"), + factory.createStringLiteral("money") + ), + ); + break; + } case 'Datetime': { attrAssignments.push( factory.createPropertyAssignment( diff --git a/src/entities/Modi.ts b/src/entities/Modi.ts index c7e1c23..a44d898 100644 --- a/src/entities/Modi.ts +++ b/src/entities/Modi.ts @@ -43,6 +43,7 @@ const locale: LocaleDef = { zh_CN: { + name: '更新', attr: { targetEntity: '目标对象', entity: '关联对象', diff --git a/src/entities/ModiEntity.ts b/src/entities/ModiEntity.ts index fe295f7..947c87c 100644 --- a/src/entities/ModiEntity.ts +++ b/src/entities/ModiEntity.ts @@ -15,6 +15,7 @@ const config: Configuration = { const locale: LocaleDef = { zh_CN: { + name: '更新对象连接', attr: { modi: '更新', entity: '关联对象', diff --git a/src/entities/Oper.ts b/src/entities/Oper.ts index 2ef3869..40df381 100644 --- a/src/entities/Oper.ts +++ b/src/entities/Oper.ts @@ -17,6 +17,7 @@ const configuration: Configuration = { const locale: LocaleDef = { zh_CN: { + name: '操作', attr: { action: '动作', data: '数据', diff --git a/src/entities/OperEntity.ts b/src/entities/OperEntity.ts index 5f4e378..e2a4065 100644 --- a/src/entities/OperEntity.ts +++ b/src/entities/OperEntity.ts @@ -15,6 +15,7 @@ const config: Configuration = { const locale: LocaleDef = { zh_CN: { + name: '操作对象连接', attr: { oper: '操作', entity: '关联对象', diff --git a/src/entities/User.ts b/src/entities/User.ts index 0962a3b..e9bd372 100644 --- a/src/entities/User.ts +++ b/src/entities/User.ts @@ -25,6 +25,7 @@ const locale: LocaleDef = { zh_CN: { + name: '用户', attr: { name: '姓名', nickname: '昵称', diff --git a/src/entities/UserEntityGrant.ts b/src/entities/UserEntityGrant.ts index 8586b7d..910563b 100644 --- a/src/entities/UserEntityGrant.ts +++ b/src/entities/UserEntityGrant.ts @@ -15,6 +15,7 @@ const locale: LocaleDef< {} > = { zh_CN: { + name: '用户授权', attr: { relation: '关系', entity: '关联对象', diff --git a/src/store/AsyncRowStore.ts b/src/store/AsyncRowStore.ts index 5cc9835..254d389 100644 --- a/src/store/AsyncRowStore.ts +++ b/src/store/AsyncRowStore.ts @@ -30,7 +30,7 @@ export abstract class AsyncContext implements Context { setHeaders(headers: IncomingHttpHeaders) { this.headers = headers; } - + getHeader(key: string): string | string[] | undefined { if (this.headers) { return this.headers[key]; @@ -43,13 +43,13 @@ export abstract class AsyncContext implements Context { this.scene = scene; } - private resetEvents() { + private resetEvents() { this.events = { commit: [], rollback: [], }; } - + on(event: 'commit' | 'rollback', callback: () => Promise): void { this.uuid && this.events[event].push(callback); } @@ -70,24 +70,26 @@ export abstract class AsyncContext implements Context { if (this.uuid) { await this.rowStore.commit(this.uuid!); this.uuid = undefined; - for(const e of this.events.commit) { + const { commit: commitEvents } = this.events; + this.resetEvents(); + for (const e of commitEvents) { await e(); } - this.resetEvents(); } } async rollback(): Promise { - if(this.uuid) { + if (this.uuid) { await this.rowStore.rollback(this.uuid!); // console.log('rollback', this.uuid); this.uuid = undefined; - for(const e of this.events.rollback) { + const { rollback: rollbackEvents } = this.events; + this.resetEvents(); + for (const e of rollbackEvents) { await e(); } - this.resetEvents(); } } - + operate( entity: T, operation: ED[T]['Operation'], @@ -95,21 +97,21 @@ export abstract class AsyncContext implements Context { ) { return this.rowStore.operate(entity, operation, this, option); } - select ( + select( entity: T, selection: ED[T]['Selection'], option: OP ) { return this.rowStore.select(entity, selection, this, option); } - aggregate ( + aggregate( entity: T, aggregation: ED[T]['Aggregation'], option: OP ) { return this.rowStore.aggregate(entity, aggregation, this, option); } - count ( + count( entity: T, selection: Pick, option: OP @@ -120,7 +122,7 @@ export abstract class AsyncContext implements Context { mergeMultipleResults(toBeMerged: OperationResult[]) { return this.rowStore.mergeMultipleResults(toBeMerged); } - + getCurrentTxnId() { return this.uuid; } @@ -140,7 +142,7 @@ export abstract class AsyncContext implements Context { abstract isRoot(): boolean; abstract getCurrentUserId(allowUnloggedIn?: boolean): string | undefined; - + abstract toString(): string; abstract allowUserUpdate(): boolean; @@ -154,21 +156,21 @@ export interface AsyncRowStore exten option: OP ): Promise>; - select ( + select( entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP ): Promise[]>; - aggregate ( + aggregate( entity: T, aggregation: ED[T]['Aggregation'], context: Cxt, option: OP ): Promise>; - count ( + count( entity: T, selection: Pick, context: Cxt, diff --git a/src/store/CascadeStore.ts b/src/store/CascadeStore.ts index fe32439..9dbccfd 100644 --- a/src/store/CascadeStore.ts +++ b/src/store/CascadeStore.ts @@ -804,88 +804,88 @@ export abstract class CascadeStore exten [foreignKey]: filter.id, }, filterOtm), }); - } - else { - Object.assign(otm, { - filter: addFilterSegment({ - [foreignKey.slice(0, foreignKey.length - 2)]: filter, - }, filterOtm), - }); + } + else { + Object.assign(otm, { + filter: addFilterSegment({ + [foreignKey.slice(0, foreignKey.length - 2)]: filter, + }, filterOtm), + }); + } } } - } - if (action === 'remove' && actionOtm === 'update') { - Object.assign(dataOtm, { - [foreignKey]: null, - }); + if (action === 'remove' && actionOtm === 'update') { + Object.assign(dataOtm, { + [foreignKey]: null, + }); + } } } - } - // 一对多的依赖应该后建,否则中间会出现空指针,导致checker等出错 - afterFns.push(() => cascadeUpdate.call(this, entityOtm!, otm, context, option2)); - }; + // 一对多的依赖应该后建,否则中间会出现空指针,导致checker等出错 + afterFns.push(() => cascadeUpdate.call(this, entityOtm!, otm, context, option2)); + }; - if (otmOperations instanceof Array) { - for (const oper of otmOperations) { - dealWithOneToMany(oper); + if (otmOperations instanceof Array) { + for (const oper of otmOperations) { + dealWithOneToMany(oper); + } + } + else { + dealWithOneToMany(otmOperations); } - } - else { - dealWithOneToMany(otmOperations); } } - } return { - data: opData, - beforeFns, - afterFns, -}; + data: opData, + beforeFns, + afterFns, + }; } // 对插入的数据,没有初始值的属性置null protected preProcessDataCreated(entity: T, data: ED[T]['Create']['data']) { - const now = Date.now(); - const { attributes } = this.getSchema()[entity]; - const processSingle = (data2: ED[T]['CreateSingle']['data']) => { - for (const key in attributes) { - if (data2[key] === undefined) { - Object.assign(data2, { - [key]: null, - }); + const now = Date.now(); + const { attributes } = this.getSchema()[entity]; + const processSingle = (data2: ED[T]['CreateSingle']['data']) => { + for (const key in attributes) { + if (data2[key] === undefined) { + Object.assign(data2, { + [key]: null, + }); + } } + Object.assign(data2, { + [CreateAtAttribute]: now, + [UpdateAtAttribute]: now, + [DeleteAtAttribute]: null, + }); + } + if (data instanceof Array) { + data.forEach( + ele => processSingle(ele) + ); + } + else { + processSingle(data as ED[T]['CreateSingle']['data']); } - Object.assign(data2, { - [CreateAtAttribute]: now, - [UpdateAtAttribute]: now, - [DeleteAtAttribute]: null, - }); } - if (data instanceof Array) { - data.forEach( - ele => processSingle(ele) - ); - } - else { - processSingle(data as ED[T]['CreateSingle']['data']); - } -} // 对更新的数据,去掉所有的undefined属性 protected preProcessDataUpdated(data: Record) { - const undefinedKeys = Object.keys(data).filter( - ele => data[ele] === undefined - ); - undefinedKeys.forEach( - ele => unset(data, ele) - ); -} + const undefinedKeys = Object.keys(data).filter( + ele => data[ele] === undefined + ); + undefinedKeys.forEach( + ele => unset(data, ele) + ); + } -judgeRelation(entity: keyof ED, attr: string) { - return judgeRelation(this.storageSchema, entity, attr); -} + judgeRelation(entity: keyof ED, attr: string) { + return judgeRelation(this.storageSchema, entity, attr); + } /** * 和具体的update过程无关的例程放在这里,包括对later动作的处理、对oper的记录以及对record的收集等 @@ -895,354 +895,334 @@ judgeRelation(entity: keyof ED, attr: string) { * @param option */ private async doUpdateSingleRowAsync>(entity: T, - operation: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], - context: Cxt, - option: OP -) { - const { data, action, id: operId, filter } = operation; - const now = Date.now(); + operation: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], + context: Cxt, + option: OP + ) { + const { data, action, id: operId, filter } = operation; + const now = Date.now(); - switch (action) { - case 'create': { - this.preProcessDataCreated(entity, data as ED[T]['Create']['data']); - if (option.modiParentEntity && !['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity as string)) { - // 变成对modi的插入 - assert(option.modiParentId); - const modiCreate: CreateModiOperation = { - id: 'dummy', - action: 'create', - data: { - id: operId!, - targetEntity: entity as string, - action, - entity: option.modiParentEntity!, - entityId: option.modiParentId!, - filter: { - id: { - $in: [(data as any).id as string], //这里记录这个filter是为了后面update的时候直接在其上面update,参见本函数后半段关于modiUpsert相关的优化 - }, - }, - data, - iState: 'active', - }, - }; - await this.cascadeUpdateAsync('modi', modiCreate, context, option); - return 1; - } - else { - let result = 0; - const createInner = async (operation2: ED[T]['Create']) => { - try { - result += await this.updateAbjointRowAsync( - entity, - operation2, - context, - option - ); - } - catch (e: any) { - /* 这段代码是处理插入时有重复的行,现在看有问题,等实际需求出现再写 - if (e instanceof OakCongruentRowExists) { - if (option.allowExists) { - // 如果允许存在,对已存在行进行update,剩下的行继续insert - const congruentRow = e.getData() as ED[T]['OpSchema']; - if (data instanceof Array) { - const rest = data.filter( - ele => ele.id !== congruentRow.id - ); - if (rest.length === data.length) { - throw e; - } - const result2 = await this.updateAbjointRow( - entity, - Object.assign({}, operation, { - data: rest, - }), - context, - option - ); - - const row = data.find( - ele => ele.id === congruentRow.id - ); - const updateData = omit(row, ['id', '$$createAt$$']); - const result3 = await this.updateAbjointRow( - entity, - { - id: await generateNewId(), - action: 'update', - data: updateData, - filter: { - id: congruentRow.id, - } as any, - }, - context, - option - ); - - return result2 + result3; - } - else { - if (data.id !== congruentRow.id) { - throw e; - } - const updateData = omit(data, ['id', '$$createAt$$']); - const result2 = await this.updateAbjointRow( - entity, - { - id: await generateNewId(), - action: 'update', - data: updateData, - filter: { - id: congruentRow.id, - } as any, - }, - context, - option - ); - return result2; - } - } - } */ - throw e; - } - }; - - if (data instanceof Array) { - const multipleCreate = this.supportMultipleCreate(); - if (multipleCreate) { - await createInner(operation as ED[T]['Create']); - } - else { - for (const d of data) { - const createSingleOper: ED[T]['CreateSingle'] = { - id: 'any', - action: 'create', - data: d, - }; - await createInner(createSingleOper); - } - } - } - else { - await createInner(operation as ED[T]['Create']); - } - - if (!option.dontCollect) { - context.opRecords.push({ - a: 'c', - e: entity, - d: data as ED[T]['OpSchema'] | ED[T]['OpSchema'][], - }); - } - if (!option.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity as string)) { - // 按照框架要求生成Oper和OperEntity这两个内置的对象 - assert(operId); - const operatorId = await context.getCurrentUserId(true); - if (operatorId) { - const createOper: CreateOperOperation = { - id: 'dummy', - action: 'create', - data: { - id: operId, - action, - data, - operatorId, - operEntity$oper: data instanceof Array ? { - id: 'dummy', - action: 'create', - data: await Promise.all( - data.map( - async (ele) => ({ - id: await generateNewIdAsync(), - entity: entity as string, - entityId: ele.id, - }) - ) - ), - } : [{ - id: 'dummy', - action: 'create', - data: { - id: await generateNewIdAsync(), - entity: entity as string, - entityId: (data as ED[T]['CreateSingle']['data']).id, - }, - }] - }, - }; - await this.cascadeUpdateAsync('oper', createOper, context, { - dontCollect: true, - dontCreateOper: true, - }); - } - } - return result!; - } - } - default: { - // 这里要优化一下,显式的对id的update/remove不要去查了,节省数据库层的性能(如果这些row是建立在一个create的modi上也查不到) - const ids = getRelevantIds(filter); - if (ids.length === 0) { - const selection: ED[T]['Selection'] = { - data: { - id: 1, - }, - filter: operation.filter, - indexFrom: operation.indexFrom, - count: operation.count, - }; - const rows = await this.selectAbjointRowAsync(entity, selection, context, { - dontCollect: true, - }); - ids.push(...(rows.map(ele => ele.id! as string))); - } - if (data) { - this.preProcessDataUpdated(data); - } - - if (option.modiParentEntity && !['modi', 'modiEntity'].includes(entity as string)) { - // 延时更新,变成对modi的插入 - // 变成对modi的插入 - // 优化,这里如果是对同一个targetEntity反复update,则变成对最后一条create/update的modi进行update,以避免发布文章这样的需求时产生过多的modi - let modiUpsert: CreateModiOperation | UpdateModiOperation | undefined; - if (action !== 'remove') { - const upsertModis = await this.selectAbjointRowAsync('modi', { - data: { - id: 1, - data: 1, - }, - filter: { - targetEntity: entity as string, - action: { - $in: ['create', 'update'], - }, - entity: option.modiParentEntity!, - entityId: option.modiParentId!, - iState: 'active', - filter: ids.length > 0 ? { - id: { - $in: ids, - }, - } : filter, - }, - sorter: [ - { - $attr: { - $$createAt$$: 1, - }, - $direction: 'desc', - } - ], - indexFrom: 0, - count: 1, - }, context, option); - if (upsertModis.length > 0) { - const { data: originData, id: originId } = upsertModis[0]; - modiUpsert = { - id: 'dummy', - action: 'update', - data: { - data: Object.assign({}, originData, data), - }, - filter: { - id: originId as string, - } - }; - } - } - if (!modiUpsert) { - modiUpsert = { + switch (action) { + case 'create': { + this.preProcessDataCreated(entity, data as ED[T]['Create']['data']); + if (option.modiParentEntity && !['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity as string)) { + // 变成对modi的插入 + assert(option.modiParentId); + const modiCreate: CreateModiOperation = { id: 'dummy', action: 'create', data: { - id: operId, + id: operId!, targetEntity: entity as string, + action, entity: option.modiParentEntity!, entityId: option.modiParentId!, - action, + filter: { + id: { + $in: [(data as any).id as string], //这里记录这个filter是为了后面update的时候直接在其上面update,参见本函数后半段关于modiUpsert相关的优化 + }, + }, data, iState: 'active', - filter, }, }; - if (ids.length > 0) { - modiUpsert.data.modiEntity$modi = { - id: 'dummy', - action: 'create', - data: await Promise.all( - ids.map( - async (id) => ({ - id: await generateNewIdAsync(), - entity: entity as string, - entityId: id, - }) - ) - ), - }; - } + await this.cascadeUpdateAsync('modi', modiCreate, context, option); + return 1; } - await this.cascadeUpdateAsync('modi', modiUpsert!, context, option); - return 1; - } - else { - const createOper = async () => { - if (!option?.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity as string) && ids.length > 0) { + else { + let result = 0; + const createInner = async (operation2: ED[T]['Create']) => { + try { + result += await this.updateAbjointRowAsync( + entity, + operation2, + context, + option + ); + } + catch (e: any) { + /* 这段代码是处理插入时有重复的行,现在看有问题,等实际需求出现再写 + if (e instanceof OakCongruentRowExists) { + if (option.allowExists) { + // 如果允许存在,对已存在行进行update,剩下的行继续insert + const congruentRow = e.getData() as ED[T]['OpSchema']; + if (data instanceof Array) { + const rest = data.filter( + ele => ele.id !== congruentRow.id + ); + if (rest.length === data.length) { + throw e; + } + const result2 = await this.updateAbjointRow( + entity, + Object.assign({}, operation, { + data: rest, + }), + context, + option + ); + + const row = data.find( + ele => ele.id === congruentRow.id + ); + const updateData = omit(row, ['id', '$$createAt$$']); + const result3 = await this.updateAbjointRow( + entity, + { + id: await generateNewId(), + action: 'update', + data: updateData, + filter: { + id: congruentRow.id, + } as any, + }, + context, + option + ); + + return result2 + result3; + } + else { + if (data.id !== congruentRow.id) { + throw e; + } + const updateData = omit(data, ['id', '$$createAt$$']); + const result2 = await this.updateAbjointRow( + entity, + { + id: await generateNewId(), + action: 'update', + data: updateData, + filter: { + id: congruentRow.id, + } as any, + }, + context, + option + ); + return result2; + } + } + } */ + throw e; + } + }; + + if (data instanceof Array) { + const multipleCreate = this.supportMultipleCreate(); + if (multipleCreate) { + await createInner(operation as ED[T]['Create']); + } + else { + for (const d of data) { + const createSingleOper: ED[T]['CreateSingle'] = { + id: 'any', + action: 'create', + data: d, + }; + await createInner(createSingleOper); + } + } + } + else { + await createInner(operation as ED[T]['Create']); + } + + if (!option.dontCollect) { + context.opRecords.push({ + a: 'c', + e: entity, + d: data as ED[T]['OpSchema'] | ED[T]['OpSchema'][], + }); + } + if (!option.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity as string)) { // 按照框架要求生成Oper和OperEntity这两个内置的对象 assert(operId); - const createOper: CreateOperOperation = { + const operatorId = await context.getCurrentUserId(true); + if (operatorId) { + const createOper: CreateOperOperation = { + id: 'dummy', + action: 'create', + data: { + id: operId, + action, + data, + operatorId, + operEntity$oper: data instanceof Array ? { + id: 'dummy', + action: 'create', + data: await Promise.all( + data.map( + async (ele) => ({ + id: await generateNewIdAsync(), + entity: entity as string, + entityId: ele.id, + }) + ) + ), + } : [{ + id: 'dummy', + action: 'create', + data: { + id: await generateNewIdAsync(), + entity: entity as string, + entityId: (data as ED[T]['CreateSingle']['data']).id, + }, + }] + }, + }; + await this.cascadeUpdateAsync('oper', createOper, context, { + dontCollect: true, + dontCreateOper: true, + }); + } + } + return result!; + } + } + default: { + // 这里要优化一下,显式的对id的update/remove不要去查了,节省数据库层的性能(如果这些row是建立在一个create的modi上也查不到) + const ids = getRelevantIds(filter); + if (ids.length === 0) { + const selection: ED[T]['Selection'] = { + data: { + id: 1, + }, + filter: operation.filter, + indexFrom: operation.indexFrom, + count: operation.count, + }; + const rows = await this.selectAbjointRowAsync(entity, selection, context, { + dontCollect: true, + }); + ids.push(...(rows.map(ele => ele.id! as string))); + } + if (data) { + this.preProcessDataUpdated(data); + } + + if (option.modiParentEntity && !['modi', 'modiEntity'].includes(entity as string)) { + // 延时更新,变成对modi的插入 + // 变成对modi的插入 + // 优化,这里如果是对同一个targetEntity反复update,则变成对最后一条create/update的modi进行update,以避免发布文章这样的需求时产生过多的modi + let modiUpsert: CreateModiOperation | UpdateModiOperation | undefined; + if (action !== 'remove') { + const upsertModis = await this.selectAbjointRowAsync('modi', { + data: { + id: 1, + data: 1, + }, + filter: { + targetEntity: entity as string, + action: { + $in: ['create', 'update'], + }, + entity: option.modiParentEntity!, + entityId: option.modiParentId!, + iState: 'active', + filter: ids.length > 0 ? { + id: { + $in: ids, + }, + } : filter, + }, + sorter: [ + { + $attr: { + $$createAt$$: 1, + }, + $direction: 'desc', + } + ], + indexFrom: 0, + count: 1, + }, context, option); + if (upsertModis.length > 0) { + const { data: originData, id: originId } = upsertModis[0]; + modiUpsert = { + id: 'dummy', + action: 'update', + data: { + data: Object.assign({}, originData, data), + }, + filter: { + id: originId as string, + } + }; + } + } + if (!modiUpsert) { + modiUpsert = { id: 'dummy', action: 'create', data: { id: operId, + targetEntity: entity as string, + entity: option.modiParentEntity!, + entityId: option.modiParentId!, action, data, - operEntity$oper: { - id: 'dummy', - action: 'create', - data: await Promise.all( - ids.map( - async (ele) => ({ - id: await generateNewIdAsync(), - entity: entity as string, - entityId: ele, - }) - ) + iState: 'active', + filter, + }, + }; + if (ids.length > 0) { + modiUpsert.data.modiEntity$modi = { + id: 'dummy', + action: 'create', + data: await Promise.all( + ids.map( + async (id) => ({ + id: await generateNewIdAsync(), + entity: entity as string, + entityId: id, + }) ) - }, - }, + ), + }; } - await this.cascadeUpdateAsync('oper', createOper, context, { - dontCollect: true, - dontCreateOper: true, - }); - } - }; - if (action === 'remove') { - if (!option.dontCollect) { - context.opRecords.push({ - a: 'r', - e: entity, - f: { - id: { - $in: ids, - } - }, - }); } + await this.cascadeUpdateAsync('modi', modiUpsert!, context, option); + return 1; } else { - const updateAttrCount = Object.keys(data).length; - if (updateAttrCount > 0) { - // 优化一下,如果不更新任何属性,则不实际执行 - Object.assign(data, { - $$updateAt$$: now, - }); + const createOper = async () => { + if (!option?.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity as string) && ids.length > 0) { + // 按照框架要求生成Oper和OperEntity这两个内置的对象 + assert(operId); + const createOper: CreateOperOperation = { + id: 'dummy', + action: 'create', + data: { + id: operId, + action, + data, + operEntity$oper: { + id: 'dummy', + action: 'create', + data: await Promise.all( + ids.map( + async (ele) => ({ + id: await generateNewIdAsync(), + entity: entity as string, + entityId: ele, + }) + ) + ) + }, + }, + } + await this.cascadeUpdateAsync('oper', createOper, context, { + dontCollect: true, + dontCreateOper: true, + }); + } + }; + if (action === 'remove') { if (!option.dontCollect) { context.opRecords.push({ - a: 'u', + a: 'r', e: entity, - d: data as ED[T]['Update']['data'], f: { id: { $in: ids, @@ -1251,150 +1231,170 @@ judgeRelation(entity: keyof ED, attr: string) { }); } } - else if (action !== 'update') { - // 如果不是update动作而是用户自定义的动作,这里还是要记录oper - await createOper(); - return 0; + else { + const updateAttrCount = Object.keys(data).length; + if (updateAttrCount > 0) { + // 优化一下,如果不更新任何属性,则不实际执行 + Object.assign(data, { + $$updateAt$$: now, + }); + if (!option.dontCollect) { + context.opRecords.push({ + a: 'u', + e: entity, + d: data as ED[T]['Update']['data'], + f: { + id: { + $in: ids, + } + }, + }); + } + } + else if (action !== 'update') { + // 如果不是update动作而是用户自定义的动作,这里还是要记录oper + await createOper(); + return 0; + } + else { + return 0; + } + } + + const result = await this.updateAbjointRowAsync(entity, operation, context, option); + await createOper(); + + return result; + } + } + } + } + + private doUpdateSingleRow>(entity: T, + operation: ED[T]['Operation'], + context: Cxt, + option: OP + ) { + const { data, action, id: operId, filter } = operation; + const now = Date.now(); + + switch (action) { + case 'create': { + this.preProcessDataCreated(entity, data as ED[T]['Create']['data']); + let result = 0; + const createInner = (operation2: ED[T]['Create']) => { + try { + result += this.updateAbjointRow( + entity, + operation2, + context, + option + ); + } + catch (e: any) { + throw e; + } + }; + + if (data instanceof Array) { + const multipleCreate = this.supportMultipleCreate(); + if (multipleCreate) { + createInner(operation as ED[T]['Create']); + } + else { + for (const d of data) { + const createSingleOper: ED[T]['CreateSingle'] = { + id: 'any', + action: 'create', + data: d, + }; + createInner(createSingleOper); + } + } + } + else { + createInner(operation as ED[T]['Create']); + } + return result; + } + default: { + if (action === 'remove') { + } + else { + const updateAttrCount = Object.keys(data).length; + if (updateAttrCount > 0) { + // 优化一下,如果不更新任何属性,则不实际执行 + Object.assign(data, { + $$updateAt$$: now, + }); + this.preProcessDataUpdated(data); } else { return 0; } } - const result = await this.updateAbjointRowAsync(entity, operation, context, option); - await createOper(); - - return result; + return this.updateAbjointRow(entity, operation, context, option); } } } -} - - private doUpdateSingleRow>(entity: T, - operation: ED[T]['Operation'], - context: Cxt, - option: OP -) { - const { data, action, id: operId, filter } = operation; - const now = Date.now(); - - switch (action) { - case 'create': { - this.preProcessDataCreated(entity, data as ED[T]['Create']['data']); - let result = 0; - const createInner = (operation2: ED[T]['Create']) => { - try { - result += this.updateAbjointRow( - entity, - operation2, - context, - option - ); - } - catch (e: any) { - throw e; - } - }; - - if (data instanceof Array) { - const multipleCreate = this.supportMultipleCreate(); - if (multipleCreate) { - createInner(operation as ED[T]['Create']); - } - else { - for (const d of data) { - const createSingleOper: ED[T]['CreateSingle'] = { - id: 'any', - action: 'create', - data: d, - }; - createInner(createSingleOper); - } - } - } - else { - createInner(operation as ED[T]['Create']); - } - return result; - } - default: { - if (action === 'remove') { - } - else { - const updateAttrCount = Object.keys(data).length; - if (updateAttrCount > 0) { - // 优化一下,如果不更新任何属性,则不实际执行 - Object.assign(data, { - $$updateAt$$: now, - }); - this.preProcessDataUpdated(data); - } - else { - return 0; - } - } - - return this.updateAbjointRow(entity, operation, context, option); - } - } -} protected cascadeUpdate, OP extends OperateOption>( - entity: T, - operation: ED[T]['Operation'], - context: Cxt, - option: OP): OperationResult < ED > { + entity: T, + operation: ED[T]['Operation'], + context: Cxt, + option: OP): OperationResult { reinforceOperation(this.getSchema(), entity, operation); const { action, data, filter, id } = operation; let opData: any; - const wholeBeforeFns: Array<() => any> =[]; -const wholeAfterFns: Array<() => any> = []; -const result: OperationResult = {}; + const wholeBeforeFns: Array<() => any> = []; + const wholeAfterFns: Array<() => any> = []; + const result: OperationResult = {}; -if (['create', 'create-l'].includes(action) && data instanceof Array) { - opData = []; - for (const d of data) { - const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( - entity, - action, - d, - context, - option, - this.cascadeUpdate, - ); - opData.push(od); - wholeBeforeFns.push(...beforeFns); - wholeAfterFns.push(...afterFns); - } -} -else { - const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( - entity, - action, - data, - context, - option, - this.cascadeUpdate, - filter - ); - opData = od; - wholeBeforeFns.push(...beforeFns); - wholeAfterFns.push(...afterFns); -} + if (['create', 'create-l'].includes(action) && data instanceof Array) { + opData = []; + for (const d of data) { + const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( + entity, + action, + d, + context, + option, + this.cascadeUpdate, + ); + opData.push(od); + wholeBeforeFns.push(...beforeFns); + wholeAfterFns.push(...afterFns); + } + } + else { + const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( + entity, + action, + data, + context, + option, + this.cascadeUpdate, + filter + ); + opData = od; + wholeBeforeFns.push(...beforeFns); + wholeAfterFns.push(...afterFns); + } -const operation2: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'] = - Object.assign({}, operation as ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], { - data: opData as ED[T]['OpSchema'], - }); + const operation2: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'] = + Object.assign({}, operation as ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], { + data: opData as ED[T]['OpSchema'], + }); -for (const before of wholeBeforeFns) { - before(); -} -const count = this.doUpdateSingleRow(entity, operation2, context, option); -for (const after of wholeAfterFns) { - after(); -} -return result; + for (const before of wholeBeforeFns) { + before(); + } + const count = this.doUpdateSingleRow(entity, operation2, context, option); + for (const after of wholeAfterFns) { + after(); + } + return result; } /** @@ -1405,121 +1405,121 @@ return result; * @param option */ protected async cascadeUpdateAsync, OP extends OperateOption>( - entity: T, - operation: ED[T]['Operation'], - context: Cxt, - option: OP): Promise < OperationResult < ED >> { + entity: T, + operation: ED[T]['Operation'], + context: Cxt, + option: OP): Promise> { reinforceOperation(this.getSchema(), entity, operation); const { action, data, filter, id } = operation; let opData: any; - const wholeBeforeFns: Array<() => Promise> =[]; -const wholeAfterFns: Array<() => Promise> = []; -const result: OperationResult = {}; + const wholeBeforeFns: Array<() => Promise> = []; + const wholeAfterFns: Array<() => Promise> = []; + const result: OperationResult = {}; -if (['create', 'create-l'].includes(action) && data instanceof Array) { - opData = []; - for (const d of data) { - const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( - entity, - action, - d, - context, - option, - this.cascadeUpdateAsync, - ); - opData.push(od); - wholeBeforeFns.push(...beforeFns); - wholeAfterFns.push(...afterFns); - } -} -else { - const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( - entity, - action, - data, - context, - option, - this.cascadeUpdateAsync, - filter - ); - opData = od; - wholeBeforeFns.push(...beforeFns); - wholeAfterFns.push(...afterFns); -} + if (['create', 'create-l'].includes(action) && data instanceof Array) { + opData = []; + for (const d of data) { + const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( + entity, + action, + d, + context, + option, + this.cascadeUpdateAsync, + ); + opData.push(od); + wholeBeforeFns.push(...beforeFns); + wholeAfterFns.push(...afterFns); + } + } + else { + const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( + entity, + action, + data, + context, + option, + this.cascadeUpdateAsync, + filter + ); + opData = od; + wholeBeforeFns.push(...beforeFns); + wholeAfterFns.push(...afterFns); + } -const operation2: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'] = - Object.assign({}, operation as ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], { - data: opData as ED[T]['OpSchema'], - }); + const operation2: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'] = + Object.assign({}, operation as ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], { + data: opData as ED[T]['OpSchema'], + }); -for (const before of wholeBeforeFns) { - await before(); -} -const count = await this.doUpdateSingleRowAsync(entity, operation2, context, option); -this.mergeOperationResult(result, { - [entity]: { - [operation2.action]: count, - } -} as OperationResult); -for (const after of wholeAfterFns) { - await after(); -} -return result; + for (const before of wholeBeforeFns) { + await before(); + } + const count = await this.doUpdateSingleRowAsync(entity, operation2, context, option); + this.mergeOperationResult(result, { + [entity]: { + [operation2.action]: count, + } + } as OperationResult); + for (const after of wholeAfterFns) { + await after(); + } + return result; } protected cascadeSelect>( - entity: T, - selection: ED[T]['Selection'], - context: Cxt, - option: OP): Partial < ED[T]['Schema'] > [] { - reinforceSelection(this.getSchema(), entity, selection); - const { data, filter, indexFrom, count, sorter } = selection; - const { projection, cascadeSelectionFns } = this.destructCascadeSelect( - entity, - data, - context, - this.cascadeSelect, - this.aggregateSync, - option); + entity: T, + selection: ED[T]['Selection'], + context: Cxt, + option: OP): Partial[] { + reinforceSelection(this.getSchema(), entity, selection); + const { data, filter, indexFrom, count, sorter } = selection; + const { projection, cascadeSelectionFns } = this.destructCascadeSelect( + entity, + data, + context, + this.cascadeSelect, + this.aggregateSync, + option); - const rows = this.selectAbjointRow(entity, { - data: projection, - filter, - indexFrom, - count, - sorter - }, context, option); + const rows = this.selectAbjointRow(entity, { + data: projection, + filter, + indexFrom, + count, + sorter + }, context, option); - if (cascadeSelectionFns.length > 0) { - const ruException: Array<{ - entity: keyof ED, - selection: ED[keyof ED]['Selection'] - }> = []; - cascadeSelectionFns.forEach( - ele => { - try { - ele(rows); - } - catch (e) { - if (e instanceof OakRowUnexistedException) { - const rows = e.getRows(); - ruException.push(...rows); + if (cascadeSelectionFns.length > 0) { + const ruException: Array<{ + entity: keyof ED, + selection: ED[keyof ED]['Selection'] + }> = []; + cascadeSelectionFns.forEach( + ele => { + try { + ele(rows); } - else { - throw e; + catch (e) { + if (e instanceof OakRowUnexistedException) { + const rows = e.getRows(); + ruException.push(...rows); + } + else { + throw e; + } } } + ) + + if (ruException.length > 0) { + throw new OakRowUnexistedException(ruException); } - ) - - if (ruException.length > 0) { - throw new OakRowUnexistedException(ruException); } - } - return rows; -} + return rows; + } /** * 将一次查询的结果集加入result @@ -1528,90 +1528,90 @@ return result; * @param rows * @param context */ - private addToResultSelections>(entity: T, rows: Partial < ED[T]['Schema'] > [], context: Cxt) { - if (this.supportManyToOneJoin()) { - const attrsToPick: string[] = []; - for (const attr in rows[0]) { - const data: Partial = {} - const rel = this.judgeRelation(entity, attr); - if (rel === 2) { - this.addToResultSelections(attr, rows.map(ele => ele[attr]!).filter(ele => !!ele), context); - } - else if (typeof rel === 'string') { - this.addToResultSelections(rel, rows.map(ele => ele[attr]!).filter(ele => !!ele), context); - } - else if (rel instanceof Array) { - this.addToResultSelections(rel[0], rows.map(ele => ele[attr]!).reduce((prev, current) => prev.concat(current), [] as any[]), context); - } - else { - attrsToPick.push(attr); + private addToResultSelections>(entity: T, rows: Partial[], context: Cxt) { + if (this.supportManyToOneJoin()) { + const attrsToPick: string[] = []; + for (const attr in rows[0]) { + const data: Partial = {} + const rel = this.judgeRelation(entity, attr); + if (rel === 2) { + this.addToResultSelections(attr, rows.map(ele => ele[attr]!).filter(ele => !!ele), context); + } + else if (typeof rel === 'string') { + this.addToResultSelections(rel, rows.map(ele => ele[attr]!).filter(ele => !!ele), context); + } + else if (rel instanceof Array) { + this.addToResultSelections(rel[0], rows.map(ele => ele[attr]!).reduce((prev, current) => prev.concat(current), [] as any[]), context); + } + else { + attrsToPick.push(attr); + } } + const originRows = rows.map( + ele => pick(ele, attrsToPick) + ) as Partial[]; + this.addSingleRowToResultSelections(entity, originRows, context); + } + else { + this.addSingleRowToResultSelections(entity, rows, context); } - const originRows = rows.map( - ele => pick(ele, attrsToPick) - ) as Partial[]; - this.addSingleRowToResultSelections(entity, originRows, context); } - else { - this.addSingleRowToResultSelections(entity, rows, context); - } -} - private addSingleRowToResultSelections>(entity: T, rows: Partial < ED[T]['OpSchema'] > [], context: Cxt) { - const { opRecords } = context; + private addSingleRowToResultSelections>(entity: T, rows: Partial[], context: Cxt) { + const { opRecords } = context; - let lastOperation = opRecords[opRecords.length - 1]; - if (lastOperation && lastOperation.a === 's') { - const entityBranch = lastOperation.d[entity]; - if (entityBranch) { - rows.forEach( - (row) => { - if (row) { - assert(row.id); - const { id } = row; - if (!entityBranch![id!]) { - Object.assign(entityBranch!, { - [id!]: row, - }); - } - else { - Object.assign(entityBranch[id], row); + let lastOperation = opRecords[opRecords.length - 1]; + if (lastOperation && lastOperation.a === 's') { + const entityBranch = lastOperation.d[entity]; + if (entityBranch) { + rows.forEach( + (row) => { + if (row) { + assert(row.id); + const { id } = row; + if (!entityBranch![id!]) { + Object.assign(entityBranch!, { + [id!]: row, + }); + } + else { + Object.assign(entityBranch[id], row); + } } } - } - ); - return; - } - } - else { - lastOperation = { - a: 's', - d: {}, - }; - opRecords.push(lastOperation); - } - - const entityBranch = {}; - rows.forEach( - (row) => { - if (row) { - const { id } = row as { id: string }; - Object.assign(entityBranch!, { - [id!]: row, - }); + ); + return; } } - ); - Object.assign(lastOperation.d, { - [entity]: entityBranch, - }); -} + else { + lastOperation = { + a: 's', + d: {}, + }; + opRecords.push(lastOperation); + } + + const entityBranch = {}; + rows.forEach( + (row) => { + if (row) { + const { id } = row as { id: string }; + Object.assign(entityBranch!, { + [id!]: row, + }); + } + } + ); + Object.assign(lastOperation.d, { + [entity]: entityBranch, + }); + } protected async cascadeSelectAsync>( - entity: T, - selection: ED[T]['Selection'], - context: Cxt, - option: OP): Promise < Partial < ED[T]['Schema'] > [] > { + entity: T, + selection: ED[T]['Selection'], + context: Cxt, + option: OP): Promise[]> { reinforceSelection(this.getSchema(), entity, selection); const { data, filter, indexFrom, count, sorter } = selection; const { projection, cascadeSelectionFns } = this.destructCascadeSelect( @@ -1631,39 +1631,39 @@ return result; }, context, option); - if(!option.dontCollect) { - this.addToResultSelections(entity, rows, context); -} + if (!option.dontCollect) { + this.addToResultSelections(entity, rows, context); + } -if (cascadeSelectionFns.length > 0) { - const ruException: Array<{ - entity: keyof ED, - selection: ED[keyof ED]['Selection'] - }> = []; - await Promise.all( - cascadeSelectionFns.map( - async ele => { - try { - await ele(rows); - } - catch (e) { - if (e instanceof OakRowUnexistedException) { - const rows = e.getRows(); - ruException.push(...rows); + if (cascadeSelectionFns.length > 0) { + const ruException: Array<{ + entity: keyof ED, + selection: ED[keyof ED]['Selection'] + }> = []; + await Promise.all( + cascadeSelectionFns.map( + async ele => { + try { + await ele(rows); + } + catch (e) { + if (e instanceof OakRowUnexistedException) { + const rows = e.getRows(); + ruException.push(...rows); + } + else { + throw e; + } + } } - else { - throw e; - } - } + ) + ); + + if (ruException.length > 0) { + throw new OakRowUnexistedException(ruException); } - ) - ); + } - if (ruException.length > 0) { - throw new OakRowUnexistedException(ruException); - } -} - -return rows; + return rows; } } \ No newline at end of file diff --git a/src/store/checker.ts b/src/store/checker.ts index c3f1c81..672ba4c 100644 --- a/src/store/checker.ts +++ b/src/store/checker.ts @@ -220,12 +220,22 @@ export function translateCheckerInSyncContext< } } -type FilterMakeFn = (operation: ED[keyof ED]['Operation'] | ED[keyof ED]['Selection'], userId: string) => ED[keyof ED]['Selection']['filter'] | { +type CreateRelationSingleCounter = { $entity: keyof ED; $filter?: ED[keyof ED]['Selection']['filter']; $count?: number; }; +type CreateRelationMultipleCounter = { + [K in '$$and' | '$$or']?: (CreateRelationSingleCounter | CreateRelationMultipleCounter)[]; +} + +type CreateRelationCounter = CreateRelationSingleCounter | CreateRelationMultipleCounter; + + +type FilterMakeFn = + (operation: ED[keyof ED]['Operation'] | ED[keyof ED]['Selection'], userId: string) => ED[keyof ED]['Selection']['filter'] | CreateRelationCounter; + function translateCascadeRelationFilterMaker( schema: StorageSchema, lch: CascadeRelationItem, @@ -315,12 +325,18 @@ function translateCascadeRelationFilterMaker filterMaker(userId); } /** * 针对第一层做一下特别优化,比如对象A指向对象B(多对一),如果A的cascadePath是 'B', * 当create A时,会带有Bid。此时生成该B对象上的相关表达式查询返回,可以避免必须将此判定在对象创建之后再做 * 另一使用场景是,在查询A时,如果带有Bid(在对象跳一对多子对象场景下很常见),可以提前判定这个查询对某些用户一定返回空集 + * + * 20230306: + * 在前台的权限判断中,会将list上的filter当成内在的限制对create动作进行判断,此时有一种可能是,filter并不能直接判断出外键,但会限制外键的查询范围。 + * 例如,在jichuang项目中,就存在park/list上,平台的用户去访问时,其查询条件是{ system: { platformId: 1 }};而用户的关系落在system.platform.platformProvider上, + * 此时如直接通过data上的外键判断就会失败,需要通过对filter上相应的语义解构,进行进一步的判断 */ const [attr] = paths; const relation = judgeRelation(schema, entity2, attr); @@ -328,54 +344,145 @@ function translateCascadeRelationFilterMaker 1 ? (relation === 2 ? translateFilterMakerIter(attr, 1) : translateFilterMakerIter(relation, 1)) : (relation === 2 ? translateRelationFilter(attr) : translateRelationFilter(relation)); + + const translateCreateFilterMaker = ( + entity: keyof ED, + filter: ED[keyof ED]['Selection']['filter'], + userId: string + ): CreateRelationCounter | OakUserUnpermittedException => { + const counters: CreateRelationCounter[] = []; + if (filter) { + if (relation === 2) { + if (filter.entity === entity && filter.entityId) { + // 这里对entityId的限定的数据只要和userId有一条relation,就不能否定可能会有创建动作(外键在最终create时,data上一定会有判定) + counters.push({ + $entity: attr, + $filter: addFilterSegment(filterMaker2(userId), { id: filter.entityId }), + }); + } + if (filter[attr]) { + counters.push({ + $entity: attr, + $filter: addFilterSegment(filterMaker2(userId), filter[attr]), + }); + } + } + else { + assert(typeof relation === 'string'); + if (filter[`${attr}Id`]) { + const filterMaker3 = paths.length > 1 ? translateFilterMakerIter(relation, 1) : translateRelationFilter(relation); + // 这里对attrId的限定的数据只要和userId有一条relation,就不能否定可能会有创建动作(外键在最终create时,data上一定会有判定) + counters.push({ + $entity: relation, + $filter: addFilterSegment(filterMaker3(userId), { id: filter[`${attr}Id`] }), + }); + } + if (filter[attr]) { + counters.push({ + $entity: relation, + $filter: addFilterSegment(filterMaker2(userId), filter[attr]), + }); + } + } + + if (filter.$and) { + const countersAnd = filter.$and.map( + (ele: ED[keyof ED]['Selection']['filter']) => translateCreateFilterMaker(entity, ele, userId) + ) as ReturnType[]; + // and 只要有一个满足就行 + const ca2 = countersAnd.filter( + ele => !(ele instanceof OakUserUnpermittedException) + ) as CreateRelationCounter[]; + counters.push(...ca2); + } + + if (filter.$or) { + const countersOr = filter.$and.map( + (ele: ED[keyof ED]['Selection']['filter']) => translateCreateFilterMaker(entity, ele, userId) + ) as ReturnType[]; + // or也只要有一个满足就行(不能否定) + const co2 = countersOr.filter( + ele => !(ele instanceof OakUserUnpermittedException) + ) as CreateRelationCounter[]; + counters.push(...co2); + } + } + + if (counters.length === 0) { + // 一个counter都找不出来,说明当前路径上不满足 + return new OakUserUnpermittedException(); + } + else if (counters.length === 1) { + return counters[0]; + } + // 是or关系,只要其中有一个满足就可以通过 + return { + $$or: counters, + }; + }; return (operation, userId) => { const { action } = operation as ED[keyof ED]['Operation']; if (action === 'create') { const { data } = operation as ED[keyof ED]['Create']; - const getForeignKeyId = (d: ED[keyof ED]['CreateSingle']['data']) => { + if (data) { + // 有data的情形根据data判定 + const getForeignKeyId = (d: ED[keyof ED]['CreateSingle']['data']) => { + if (relation === 2) { + if (d.entity === attr && typeof d.entityId === 'string') { + return d.entityId as string; + } + throw new OakUserUnpermittedException(); + } + else { + assert(typeof relation === 'string'); + if (typeof d[`${attr}Id`] === 'string') { + return d[`${attr}Id`] as string; + } + throw new OakUserUnpermittedException(); + } + }; if (relation === 2) { - if (d.entity === attr && typeof d.entityId === 'string') { - return d.entitId as string; + if (data instanceof Array) { + const fkIds = uniq(data.map(d => getForeignKeyId(d))); + return { + $entity: attr, + $filter: addFilterSegment(filterMaker2(userId), { id: { $in: fkIds } }), + $count: fkIds.length, + }; } - throw new OakUserUnpermittedException(); + const fkId = getForeignKeyId(data); + return { + $entity: attr, + $filter: addFilterSegment(filterMaker2(userId), { id: fkId }), + }; } - else { - assert(typeof relation === 'string'); - if (typeof d[`${attr}Id`] === 'string') { - return d[`${attr}Id`] as string; - } - throw new OakUserUnpermittedException(); - } - }; - if (relation === 2) { + assert(typeof relation === 'string'); if (data instanceof Array) { const fkIds = uniq(data.map(d => getForeignKeyId(d))); return { - $entity: attr, + $entity: relation, $filter: addFilterSegment(filterMaker2(userId), { id: { $in: fkIds } }), $count: fkIds.length, }; } const fkId = getForeignKeyId(data); return { - $entity: attr, + $entity: relation, $filter: addFilterSegment(filterMaker2(userId), { id: fkId }), }; } - assert(typeof relation === 'string'); - if (data instanceof Array) { - const fkIds = uniq(data.map(d => getForeignKeyId(d))); - return { - $entity: relation, - $filter: addFilterSegment(filterMaker2(userId), { id: { $in: fkIds } }), - $count: fkIds.length, - }; + else { + // todo + const { filter } = operation as ED[keyof ED]['Selection']; + if (filter) { + const counter = translateCreateFilterMaker(entity2, filter, userId); + if (counter instanceof OakUserUnpermittedException) { + throw counter; + } + return counter; + } + throw new OakUserUnpermittedException(); } - const fkId = getForeignKeyId(data); - return { - $entity: relation, - $filter: addFilterSegment(filterMaker2(userId), { id: fkId }), - }; } const { filter } = operation; if (relation === 2 && filter?.entity === attr && filter?.entityId) { @@ -437,7 +544,94 @@ function translateActionAuthFilterMaker( return filterMaker; } -function makePotentialFilter | SyncContext> ( +function execCreateCounter | SyncContext>( + context: Cxt, + counter: CreateRelationCounter, +): SyncOrAsync> { + if ((>counter)?.$$and) { + // 每个counter都要满足才能过 + const counters = (>counter)?.$$and!; + assert(counters.length > 0); + const counterResults = counters.map( + ele => execCreateCounter(context, ele) + ); + if (counterResults[0] instanceof Promise) { + return Promise.all(counterResults) + .then( + (cr2) => { + const unpermitted = cr2.find( + (ele) => ele instanceof OakUserUnpermittedException + ); + if (unpermitted) { + return unpermitted; + } + return undefined; + } + ); + } + else { + const unpermitted = counterResults.find( + (ele) => ele instanceof OakUserUnpermittedException + ); + if (unpermitted) { + return unpermitted; + } + else { + return undefined; + } + } + } + else if ((>counter)?.$$or) { + // 只要有一个counter能过就算过 + const counters = (>counter)?.$$or!; + assert(counters.length > 0); + const counterResults = counters.map( + ele => execCreateCounter(context, ele) + ); + if (counterResults[0] instanceof Promise) { + return Promise.all(counterResults) + .then( + (cr2) => { + const permittedIdx = cr2.indexOf(undefined); + if (permittedIdx !== -1) { + return undefined; + } + return new OakUserUnpermittedException(); + } + ); + } + else { + const permittedIndex = counterResults.indexOf(undefined); + if (permittedIndex !== -1) { + return undefined; + } + else { + return new OakUserUnpermittedException(); + } + } + } + else if ((>counter)?.$entity) { + const { $entity, $filter, $count = 1 } = counter as CreateRelationSingleCounter; + const count = context.count($entity, { + filter: $filter, + }, { dontCollect: true }); + if (count instanceof Promise) { + return count.then( + (c2) => { + if (c2 >= $count) { + return undefined; + } + return new OakUserUnpermittedException(); + } + ); + } + else { + return count >= $count ? undefined : new OakUserUnpermittedException(); + } + } +} + +function makePotentialFilter | SyncContext>( operation: ED[keyof ED]['Operation'] | ED[keyof ED]['Selection'], context: Cxt, filterMaker: FilterMakeFn | (FilterMakeFn | FilterMakeFn[])[]): SyncOrAsync { @@ -466,30 +660,17 @@ function makePotentialFilter 0); const filtersAnd: (SyncOrAsync>)[] = []; for (const ff of f) { - if (ff?.$entity) { - const { $entity, $filter, $count = 1 } = ff!; - const count = context.count($entity, { - filter: $filter, - }, {}); - if (count instanceof Promise) { + if ((>ff)?.$$and || (>ff)?.$$or || (>ff)?.$entity) { + // 每个counter都要满足才能过 + const result = execCreateCounter(context, ff as CreateRelationMultipleCounter); + if (result instanceof Promise) { isAsyncAnd = true; - filtersAnd.push( - count.then( - (c2) => { - if (c2 >= $count) { - return undefined; - } - return new OakUserUnpermittedException(); - } - ) - ); - } - else { - filtersAnd.push(count >= $count ? undefined : new OakUserUnpermittedException()); } + filtersAnd.push(result); } else if (ff) { filtersAnd.push(ff as ED[keyof ED]['Selection']['filter']); @@ -520,22 +701,12 @@ function makePotentialFilter>f)?.$$and || (>f)?.$$or || (>f)?.$entity) { + const counterResults = execCreateCounter(context, f as CreateRelationCounter); + if (counterResults instanceof Promise) { isAsyncOr = true; - filtersOr.push( - count.then( - (c2) => c2 >= $count ? undefined : new OakUserUnpermittedException() - ) - ); - } - else { - filtersOr.push(count >= $count ? undefined : new OakUserUnpermittedException()); } + filtersOr.push(counterResults); } else if (f) { filtersOr.push(f as ED[keyof ED]['Selection']['filter']); @@ -584,11 +755,26 @@ export function createAuthCheckers | (FilterMakeFn | FilterMakeFn[])[]>; const userEntityName = `user${firstLetterUpperCase(entity)}`; + const allAuthItem: (CascadeRelationItem | CascadeRelationItem[])[] = []; for (const r in relationAuth) { + const authItem = relationAuth[r as NonNullable]!; Object.assign(raFilterMakerDict, { - [r]: translateActionAuthFilterMaker(schema, relationAuth[r as NonNullable]!, userEntityName, entity), + [r]: translateActionAuthFilterMaker(schema, authItem, userEntityName, entity), }); + + if (authItem instanceof Array) { + allAuthItem.push(...authItem); + } + else { + allAuthItem.push(authItem); + } } + + // 如果不指定relation,则使用所有的authItem的or组合 + Object.assign(raFilterMakerDict, { + '@@all': translateActionAuthFilterMaker(schema, allAuthItem, userEntityName, entity), + }); + const entityIdAttr = `${entity}Id`; checkers.push({ entity: userEntityName as keyof ED, @@ -598,11 +784,15 @@ export function createAuthCheckers = string; export type Sequence = string; // 自增长序列,为了让人阅读方便,为了支持分布式这里用string,底层实现可自定义 export { Geo, SingleGeo } from './Geo'; -export type DataTypes = number | string | Datetime | Geo | Object | SingleGeo; +export type DataTypes = number | string | Datetime | Day | Time | Geo | Object | SingleGeo; -export const types = ['Int', 'Uint', 'Double', 'Float', 'String', 'Text', 'Datetime', 'Boolean', 'Image', 'File', 'Geo', 'SingleGeo']; +export const types = ['Int', 'Uint', 'Double', 'Float', 'String', 'Text', 'Datetime', 'Day', 'Time', + 'Boolean', 'Image', 'File', 'Geo', 'SingleGeo', 'Price']; export const unIndexedTypes = ['Text', 'Image', 'File', 'Object']; diff --git a/src/types/Locale.ts b/src/types/Locale.ts index 404b0a5..6fdeb80 100644 --- a/src/types/Locale.ts +++ b/src/types/Locale.ts @@ -18,6 +18,7 @@ type LocaleOfValue> = { export type LocaleDef, Ac extends string, R extends string, V extends Record> = { [L in Language]?: { + name: string; attr: LocaleOfSchema & { [A in keyof V]: string; // 目前写在V里面的应该只有枚举变量和state }; diff --git a/src/types/Style.ts b/src/types/Style.ts new file mode 100644 index 0000000..05283c5 --- /dev/null +++ b/src/types/Style.ts @@ -0,0 +1,12 @@ +import { EntityDict } from './Entity'; +import { EntityDict as BaseEntityDict } from '../base-app-domain'; + +type ThemeColor = 'default' | 'success' | 'warning' | 'error'; + +export type ColorDict = { + [T in keyof ED]?: { + [A in keyof ED[T]['OpSchema']]?: { + [E in ED[T]['OpSchema'][A]]?: ThemeColor | `#${string}`; + }; + }; +}; diff --git a/src/types/Timer.ts b/src/types/Timer.ts index a33ee54..c9c3be9 100644 --- a/src/types/Timer.ts +++ b/src/types/Timer.ts @@ -1,7 +1,10 @@ +import { RecurrenceRule, RecurrenceSpecDateRange, RecurrenceSpecObjLit } from 'node-schedule'; import { EntityDict } from './Entity'; -import { AsyncContext } from "../store/AsyncRowStore"; +import { AsyncContext } from '../store/AsyncRowStore'; -type RoutineFn> = (context: Cxt) => Promise; +type RoutineFn> = ( + context: Cxt +) => Promise; export type Routine> = { name: string; @@ -10,6 +13,6 @@ export type Routine> = { export type Timer> = { name: string; - cron: string; + cron: RecurrenceRule | RecurrenceSpecDateRange | RecurrenceSpecObjLit | Date | string | number; fn: RoutineFn; }; diff --git a/src/types/index.ts b/src/types/index.ts index dcb0909..80d8ac5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -19,3 +19,4 @@ export * from './Connector'; export * from './Timer'; export * from './Port'; export * from './Endpoint'; +export * from './Style'; diff --git a/src/types/schema/DataTypes.ts b/src/types/schema/DataTypes.ts index 9fa98b4..1c6b30d 100644 --- a/src/types/schema/DataTypes.ts +++ b/src/types/schema/DataTypes.ts @@ -218,4 +218,6 @@ export interface DataTypeParams { precision?: number; scale?: number; signed?: boolean; + max?: number; + min?: number; }; diff --git a/src/utils/SimpleConnector.ts b/src/utils/SimpleConnector.ts index 7c7f714..cdcc458 100644 --- a/src/utils/SimpleConnector.ts +++ b/src/utils/SimpleConnector.ts @@ -10,7 +10,7 @@ function makeContentTypeAndBody(data: any) { if (process.env.OAK_PLATFORM !== 'wechatMp') { if (data instanceof FormData) { return { - contentType: 'multipart/form-data', + // contentType: 'multipart/form-data', body: data, }; } @@ -41,11 +41,15 @@ export class SimpleConnector 299) { @@ -61,7 +65,7 @@ export class SimpleConnector): Promise<{ name: string; params: any; context: BackCxt; }> { + async parseRequest(headers: IncomingHttpHeaders, body: any, store: AsyncRowStore): Promise<{ name: string; params: any; context: BackCxt; }> { const { 'oak-cxt': oakCxtStr, 'oak-aspect': aspectName } = headers; assert(typeof oakCxtStr === 'string' || oakCxtStr === undefined); assert(typeof aspectName === 'string'); @@ -99,9 +103,9 @@ export class SimpleConnector | undefined; } { - if (result instanceof Stream) { + if (result instanceof Stream || result instanceof Buffer) { return { body: result, }; @@ -117,7 +121,7 @@ export class SimpleConnector, headers: IncomingHttpHeaders, body: any): { body: any; headers?: Record | undefined; } { + serializeException(exception: OakException, headers: IncomingHttpHeaders, body: any): { body: any; headers?: Record | undefined; } { return { body: { exception: exception.toString(), diff --git a/src/utils/cron.ts b/src/utils/cron.ts deleted file mode 100644 index e51741e..0000000 --- a/src/utils/cron.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { getFutureMatches } from '@datasert/cronjs-matcher'; -import DayJs from 'dayjs'; - -export function schedule(cron: string, fn: (date: Date) => any) { - const futureMatches = getFutureMatches(cron, { - matchCount: 1, - }); - const date = DayJs(futureMatches[0]); - const interval = date.diff(DayJs(), 'ms'); - setTimeout( - () => { - fn(new Date()); - schedule(cron, fn); - }, - interval - ); -} diff --git a/src/utils/mask.ts b/src/utils/mask.ts new file mode 100644 index 0000000..e286fce --- /dev/null +++ b/src/utils/mask.ts @@ -0,0 +1,40 @@ +const maskIdCard: (idCardNumber: string) => string = (idCardNumber) => { + if(!idCardNumber as any instanceof String) { + throw new Error("身份证号码必须是String类型"); + } + let begin = idCardNumber.slice(0, 4); + let end = idCardNumber.slice(idCardNumber.length - 4, 4); + for(let i = 0; i < idCardNumber.length - 8; i ++) { + begin = begin.concat("*"); + } + return begin.concat(end); +} + +const maskMobile: (mobile: string) => string = (mobile) => { + let begin = mobile.slice(0, 3); + let end = mobile.slice(7, 11); + return begin.concat("****").concat(end); +} + + +const maskName: (name: string) => string = (name) => { + return name.slice(0, name.length - 1).concat("*"); +} + +const maskStar: (str: string, front: number, end: number, star: string) => string = (str, frontLen, endLen, star = '*') => { + const len = str.length - frontLen - endLen; + let xing = ''; + for (let i = 0; i < len; i++) { + xing += star; + } + return str.substring(0, frontLen) + xing + str.substring(str.length - endLen); +} + + + +export { + maskIdCard, + maskMobile, + maskName, + maskStar, +} diff --git a/src/utils/money.ts b/src/utils/money.ts new file mode 100644 index 0000000..db77b10 --- /dev/null +++ b/src/utils/money.ts @@ -0,0 +1,27 @@ +const ToCent: (float: number) => number = (float) => { + return Math.round(float * 100); +} + +const ToYuan: (int: number) => number = ( int) => { + return Math.round(int) / 100; +} + +const StringToCent: (value: string, allowNegative?: true) => number | undefined = (value, allowNegative) => { + const numValue = parseInt(value, 10); + if (typeof numValue === 'number' && (numValue >= 0 || allowNegative)) { + return ToCent(numValue); + } +} + +const CentToString: (value: number) => string | undefined = (value) => { + if (typeof value === 'number') { + return `${ToYuan(value)}`; + } +} + +export { + ToCent, + ToYuan, + StringToCent, + CentToString, +} \ No newline at end of file diff --git a/src/utils/validator.ts b/src/utils/validator.ts index b05cb04..34bd97f 100644 --- a/src/utils/validator.ts +++ b/src/utils/validator.ts @@ -83,16 +83,26 @@ export const isPhone: ValidatorFunction = (phone) => { return /^(\(\d{3,4}\)|\d{3,4}-)?\d{7,8}$/.test(phone); }; +export const isTel: ValidatorFunction = (text) => { + // 1、133xxxx4545 2、0571-630xx239 3、400-123-1400 + const reg = + /^(((\d{3,4}-)?[0-9]{7,8})|(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}|((400)-(\d{3})-(\d{4})(.)(\d{1,4})|(400)-(\d{3})-(\d{4}$)|(400)(\d{3})(\d{4}$)|(400)-(\d{4})-(\d{3}$)))$/.test( + text + ); + return reg; +}; + export const isNumber: ValidatorFunction = (str) => { return /^[0-9]*$/.test(str); } export const isMoney: ValidatorMoneyFunction = (str, zero) => { - // zero为true包含零 + // 金额,最多可以有两位小数 zero为true包含零 if (zero) { - // 金额,最多可以有两位小数 - return /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/.test(str); + return /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/.test( + str + ); } return /(^[1-9](\d+)?(\.\d{1,2})?$)|(^\d\.\d{1,2}$)/.test(str); }