@nest-omni/core 4.1.3-1 → 4.1.3-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common/boilerplate.polyfill.d.ts +0 -4
- package/common/boilerplate.polyfill.js +7 -100
- package/i18n/en_US/validation.json +2 -1
- package/i18n/zh_CN/validation.json +2 -1
- package/package.json +1 -1
- package/shared/serviceRegistryModule.js +4 -0
- package/validators/custom-validate.examples.d.ts +73 -0
- package/validators/custom-validate.examples.js +328 -0
- package/validators/custom-validate.validator.d.ts +26 -0
- package/validators/custom-validate.validator.js +129 -0
- package/validators/index.d.ts +1 -0
- package/validators/index.js +1 -0
- package/validators/is-exists.validator.d.ts +7 -4
- package/validators/is-exists.validator.js +45 -6
- package/validators/is-unique.validator.d.ts +8 -4
- package/validators/is-unique.validator.js +49 -11
|
@@ -16,10 +16,6 @@ declare global {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
declare module 'typeorm' {
|
|
19
|
-
interface Repository<Entity extends ObjectLiteral> {
|
|
20
|
-
saveWithPk(this: Repository<Entity>, entity: Entity | Entity[]): Promise<Entity | Entity[]>;
|
|
21
|
-
getPrimaryKeyFields(this: Repository<Entity>): string[];
|
|
22
|
-
}
|
|
23
19
|
interface SelectQueryBuilder<Entity> {
|
|
24
20
|
searchByString(q: string, columnNames: string[], options?: {
|
|
25
21
|
formStart: boolean;
|
|
@@ -25,7 +25,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
25
25
|
require("source-map-support/register");
|
|
26
26
|
const lodash_1 = require("lodash");
|
|
27
27
|
const typeorm_1 = require("typeorm");
|
|
28
|
-
const typeorm_2 = require("typeorm");
|
|
29
28
|
const page_dto_1 = require("./dto/page.dto");
|
|
30
29
|
const page_meta_dto_1 = require("./dto/page-meta.dto");
|
|
31
30
|
const providers_1 = require("../providers");
|
|
@@ -44,11 +43,11 @@ Array.prototype.getByLanguage = function (languageCode) {
|
|
|
44
43
|
Array.prototype.toPageDto = function (pageMetaDto, options) {
|
|
45
44
|
return new page_dto_1.PageDto(this.toDtos(options), pageMetaDto);
|
|
46
45
|
};
|
|
47
|
-
|
|
46
|
+
typeorm_1.SelectQueryBuilder.prototype.searchByString = function (q, columnNames, options) {
|
|
48
47
|
if (!q) {
|
|
49
48
|
return this;
|
|
50
49
|
}
|
|
51
|
-
this.andWhere(new
|
|
50
|
+
this.andWhere(new typeorm_1.Brackets((qb) => {
|
|
52
51
|
for (const item of columnNames) {
|
|
53
52
|
qb.orWhere(`${item} ILIKE :q`);
|
|
54
53
|
}
|
|
@@ -61,7 +60,7 @@ typeorm_2.SelectQueryBuilder.prototype.searchByString = function (q, columnNames
|
|
|
61
60
|
}
|
|
62
61
|
return this;
|
|
63
62
|
};
|
|
64
|
-
|
|
63
|
+
typeorm_1.SelectQueryBuilder.prototype.paginate = function (pageOptionsDto, options) {
|
|
65
64
|
return __awaiter(this, void 0, void 0, function* () {
|
|
66
65
|
if (!(options === null || options === void 0 ? void 0 : options.takeAll)) {
|
|
67
66
|
this.skip(pageOptionsDto.skip).take(pageOptionsDto.pageSize);
|
|
@@ -78,7 +77,7 @@ typeorm_2.SelectQueryBuilder.prototype.paginate = function (pageOptionsDto, opti
|
|
|
78
77
|
return [entities, pageMetaDto];
|
|
79
78
|
});
|
|
80
79
|
};
|
|
81
|
-
|
|
80
|
+
typeorm_1.SelectQueryBuilder.prototype.paginateAndMap = function (pageOptionsDto, options) {
|
|
82
81
|
return __awaiter(this, void 0, void 0, function* () {
|
|
83
82
|
const [entities, pageMetaDto] = yield this.paginate(pageOptionsDto, {
|
|
84
83
|
skipCount: options === null || options === void 0 ? void 0 : options.skipCount,
|
|
@@ -91,7 +90,7 @@ typeorm_2.SelectQueryBuilder.prototype.paginateAndMap = function (pageOptionsDto
|
|
|
91
90
|
return transformedEntities.toPageDto(pageMetaDto, options === null || options === void 0 ? void 0 : options.dtoOptions);
|
|
92
91
|
});
|
|
93
92
|
};
|
|
94
|
-
|
|
93
|
+
typeorm_1.SelectQueryBuilder.prototype.withTenant = function (tenantId, tenantFieldName = 'tenantId') {
|
|
95
94
|
var _a;
|
|
96
95
|
if (!tenantId) {
|
|
97
96
|
tenantId = providers_1.ContextProvider.getTenantId();
|
|
@@ -107,7 +106,7 @@ typeorm_2.SelectQueryBuilder.prototype.withTenant = function (tenantId, tenantFi
|
|
|
107
106
|
}
|
|
108
107
|
return this;
|
|
109
108
|
};
|
|
110
|
-
|
|
109
|
+
typeorm_1.SelectQueryBuilder.prototype.iterate = function (options) {
|
|
111
110
|
return __asyncGenerator(this, arguments, function* () {
|
|
112
111
|
const batchSize = (options === null || options === void 0 ? void 0 : options.batchSize) || 100;
|
|
113
112
|
let offset = 0;
|
|
@@ -125,7 +124,7 @@ typeorm_2.SelectQueryBuilder.prototype.iterate = function (options) {
|
|
|
125
124
|
}
|
|
126
125
|
});
|
|
127
126
|
};
|
|
128
|
-
|
|
127
|
+
typeorm_1.SelectQueryBuilder.prototype.eachBatch = function (callback, options) {
|
|
129
128
|
return __awaiter(this, void 0, void 0, function* () {
|
|
130
129
|
const batchSize = (options === null || options === void 0 ? void 0 : options.batchSize) || 100;
|
|
131
130
|
const mode = (options === null || options === void 0 ? void 0 : options.mode) || 'batch';
|
|
@@ -151,95 +150,3 @@ typeorm_2.SelectQueryBuilder.prototype.eachBatch = function (callback, options)
|
|
|
151
150
|
}
|
|
152
151
|
});
|
|
153
152
|
};
|
|
154
|
-
const insertWithPk = function (entity) {
|
|
155
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
156
|
-
if (!this.metadata) {
|
|
157
|
-
throw new Error('Repository metadata is not available. Make sure the repository is properly initialized.');
|
|
158
|
-
}
|
|
159
|
-
const tableName = this.metadata.tableName;
|
|
160
|
-
const columns = this.metadata.columns;
|
|
161
|
-
const columnNames = [];
|
|
162
|
-
const columnValues = [];
|
|
163
|
-
const valuePlaceholders = [];
|
|
164
|
-
for (const column of columns) {
|
|
165
|
-
const propertyName = column.propertyName;
|
|
166
|
-
const value = entity[propertyName];
|
|
167
|
-
if (value === undefined) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const columnName = column.databaseName;
|
|
171
|
-
if (columnName) {
|
|
172
|
-
columnNames.push(columnName);
|
|
173
|
-
columnValues.push(value);
|
|
174
|
-
valuePlaceholders.push(`?`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const sql = `INSERT INTO ${tableName} (${columnNames.join(', ')}) VALUES (${valuePlaceholders.join(', ')})`;
|
|
178
|
-
try {
|
|
179
|
-
const result = yield this.query(sql, columnValues);
|
|
180
|
-
return result[0];
|
|
181
|
-
}
|
|
182
|
-
catch (error) {
|
|
183
|
-
console.warn(`Native SQL insert failed for ${tableName}, falling back to regular save:`, error);
|
|
184
|
-
return yield this.save(entity);
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
};
|
|
188
|
-
typeorm_1.Repository.prototype.getPrimaryKeyFields = function () {
|
|
189
|
-
if (!this.metadata) {
|
|
190
|
-
throw new Error('Repository metadata is not available. Make sure the repository is properly initialized.');
|
|
191
|
-
}
|
|
192
|
-
const primaryKeys = this.metadata.primaryColumns;
|
|
193
|
-
if (!primaryKeys || primaryKeys.length === 0) {
|
|
194
|
-
throw new Error(`Entity ${this.metadata.targetName} does not have any primary key columns defined.`);
|
|
195
|
-
}
|
|
196
|
-
return primaryKeys.map(column => column.propertyName);
|
|
197
|
-
};
|
|
198
|
-
typeorm_1.Repository.prototype.saveWithPk = function (entity) {
|
|
199
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
200
|
-
const isArray = Array.isArray(entity);
|
|
201
|
-
const entities = isArray ? entity : [entity];
|
|
202
|
-
if (entities.length === 0) {
|
|
203
|
-
return isArray ? [] : null;
|
|
204
|
-
}
|
|
205
|
-
const pkFields = this.getPrimaryKeyFields();
|
|
206
|
-
const results = yield Promise.all(entities.map((singleEntity) => __awaiter(this, void 0, void 0, function* () {
|
|
207
|
-
const hasPkValues = pkFields.every(pkField => {
|
|
208
|
-
const value = singleEntity[pkField];
|
|
209
|
-
return value !== undefined && value !== null && value !== '';
|
|
210
|
-
});
|
|
211
|
-
if (hasPkValues) {
|
|
212
|
-
try {
|
|
213
|
-
const existingEntity = yield this.findOne({
|
|
214
|
-
where: pkFields.reduce((acc, pkField) => {
|
|
215
|
-
acc[pkField] = singleEntity[pkField];
|
|
216
|
-
return acc;
|
|
217
|
-
}, {})
|
|
218
|
-
});
|
|
219
|
-
if (existingEntity) {
|
|
220
|
-
yield this.update(pkFields.reduce((acc, pkField) => {
|
|
221
|
-
acc[pkField] = singleEntity[pkField];
|
|
222
|
-
return acc;
|
|
223
|
-
}, {}), singleEntity);
|
|
224
|
-
return yield this.findOne({
|
|
225
|
-
where: pkFields.reduce((acc, pkField) => {
|
|
226
|
-
acc[pkField] = singleEntity[pkField];
|
|
227
|
-
return acc;
|
|
228
|
-
}, {})
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
return yield insertWithPk.call(this, singleEntity);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
return yield insertWithPk.call(this, singleEntity);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
return yield this.save(singleEntity);
|
|
241
|
-
}
|
|
242
|
-
})));
|
|
243
|
-
return isArray ? results : results[0];
|
|
244
|
-
});
|
|
245
|
-
};
|
|
@@ -40,5 +40,6 @@
|
|
|
40
40
|
"IS_LOCALE": "{property} value `{value}` must be a valid locale",
|
|
41
41
|
"AT_LEAST_ONE_FIELD": "At least one of the following fields must be provided: {properties}",
|
|
42
42
|
"CUSTOM_VALIDATION": "Custom validation failed for {property}",
|
|
43
|
-
"CUSTOM_VALIDATION_METHOD": "Validation failed for {property}"
|
|
43
|
+
"CUSTOM_VALIDATION_METHOD": "Validation failed for {property}",
|
|
44
|
+
"CUSTOM_VALIDATE": "Validation failed for {property}"
|
|
44
45
|
}
|
|
@@ -40,5 +40,6 @@
|
|
|
40
40
|
"IS_LOCALE": "{property} 值 `{value}` 必须是有效语言",
|
|
41
41
|
"AT_LEAST_ONE_FIELD": "以下字段中至少必须提供一个:{properties}",
|
|
42
42
|
"CUSTOM_VALIDATION": "{property}自定义验证失败",
|
|
43
|
-
"CUSTOM_VALIDATION_METHOD": "{property}验证失败"
|
|
43
|
+
"CUSTOM_VALIDATION_METHOD": "{property}验证失败",
|
|
44
|
+
"CUSTOM_VALIDATE": "{property}验证失败"
|
|
44
45
|
}
|
package/package.json
CHANGED
|
@@ -37,11 +37,15 @@ const nestjs_cls_1 = require("nestjs-cls");
|
|
|
37
37
|
const redis_lock_1 = require("../redis-lock");
|
|
38
38
|
const typeorm_2 = require("typeorm");
|
|
39
39
|
const vault_1 = require("../vault");
|
|
40
|
+
const validators_1 = require("../validators");
|
|
40
41
|
const providers = [
|
|
41
42
|
services_1.ApiConfigService,
|
|
42
43
|
services_1.ValidatorService,
|
|
43
44
|
services_1.GeneratorService,
|
|
44
45
|
services_1.TranslationService,
|
|
46
|
+
validators_1.IsExistsValidator,
|
|
47
|
+
validators_1.IsUniqueValidator,
|
|
48
|
+
validators_1.CustomValidateValidator,
|
|
45
49
|
];
|
|
46
50
|
if (!((_a = process === null || process === void 0 ? void 0 : process.env) === null || _a === void 0 ? void 0 : _a.ENV_FILE_PATH)) {
|
|
47
51
|
Promise.resolve().then(() => require('../setup/bootstrap.setup'));
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Repository } from 'typeorm';
|
|
2
|
+
import { ValidationContext } from '../validators/custom-validate.validator';
|
|
3
|
+
export declare class Example1_InlineValidation {
|
|
4
|
+
price: number;
|
|
5
|
+
code: string;
|
|
6
|
+
maxPrice: number;
|
|
7
|
+
minPrice: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class Example2_EntityMethod {
|
|
10
|
+
version: string;
|
|
11
|
+
publishedAt: Date;
|
|
12
|
+
createdAt: Date;
|
|
13
|
+
validateVersion(value: string): boolean;
|
|
14
|
+
checkDates(value: Date, context: ValidationContext): Promise<boolean>;
|
|
15
|
+
}
|
|
16
|
+
export declare class ExampleValidationService {
|
|
17
|
+
private readonly userRepository;
|
|
18
|
+
constructor(userRepository: Repository<any>);
|
|
19
|
+
usernameAvailable(username: string): Promise<boolean>;
|
|
20
|
+
validateEmail(email: string): boolean;
|
|
21
|
+
checkRange(value: number, context: ValidationContext, min: number, max: number): boolean;
|
|
22
|
+
validateBusinessRule(value: any, context: ValidationContext): Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
export declare class Example3_ServiceValidation {
|
|
25
|
+
username: string;
|
|
26
|
+
email: string;
|
|
27
|
+
age: number;
|
|
28
|
+
businessField: string;
|
|
29
|
+
userId: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class Example4_LazyReference {
|
|
32
|
+
username: string;
|
|
33
|
+
}
|
|
34
|
+
export declare class Example5_ComplexValidation {
|
|
35
|
+
password: string;
|
|
36
|
+
confirmPassword: string;
|
|
37
|
+
approverId?: string;
|
|
38
|
+
requiresApproval: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare class FileValidationService {
|
|
41
|
+
private readonly allowedMimeTypes;
|
|
42
|
+
private readonly maxFileSize;
|
|
43
|
+
checkFileType(mimeType: string): boolean;
|
|
44
|
+
checkFileSize(size: number, context: ValidationContext, maxSize?: number): boolean;
|
|
45
|
+
checkFileHash(hash: string): Promise<boolean>;
|
|
46
|
+
}
|
|
47
|
+
export declare class Example6_FileUpload {
|
|
48
|
+
mimeType: string;
|
|
49
|
+
size: number;
|
|
50
|
+
hash: string;
|
|
51
|
+
}
|
|
52
|
+
export declare class Example7_DynamicMessage {
|
|
53
|
+
discount: number;
|
|
54
|
+
}
|
|
55
|
+
export declare class ThirdPartyValidationService {
|
|
56
|
+
validateAddress(address: string): Promise<boolean>;
|
|
57
|
+
validateTaxId(taxId: string, context: ValidationContext): Promise<boolean>;
|
|
58
|
+
}
|
|
59
|
+
export declare class Example8_ThirdPartyAPI {
|
|
60
|
+
address: string;
|
|
61
|
+
taxId: string;
|
|
62
|
+
country: string;
|
|
63
|
+
}
|
|
64
|
+
export declare class CachedValidationService {
|
|
65
|
+
private cache;
|
|
66
|
+
private readonly cacheTTL;
|
|
67
|
+
expensiveValidation(value: string): Promise<boolean>;
|
|
68
|
+
private doExpensiveCheck;
|
|
69
|
+
private cleanExpiredCache;
|
|
70
|
+
}
|
|
71
|
+
export declare class Example9_CachedValidation {
|
|
72
|
+
field: string;
|
|
73
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
15
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
16
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
17
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
18
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
19
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
20
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.Example9_CachedValidation = exports.CachedValidationService = exports.Example8_ThirdPartyAPI = exports.ThirdPartyValidationService = exports.Example7_DynamicMessage = exports.Example6_FileUpload = exports.FileValidationService = exports.Example5_ComplexValidation = exports.Example4_LazyReference = exports.Example3_ServiceValidation = exports.ExampleValidationService = exports.Example2_EntityMethod = exports.Example1_InlineValidation = void 0;
|
|
25
|
+
const common_1 = require("@nestjs/common");
|
|
26
|
+
const typeorm_1 = require("@nestjs/typeorm");
|
|
27
|
+
const typeorm_2 = require("typeorm");
|
|
28
|
+
const custom_validate_validator_1 = require("../validators/custom-validate.validator");
|
|
29
|
+
class Example1_InlineValidation {
|
|
30
|
+
}
|
|
31
|
+
exports.Example1_InlineValidation = Example1_InlineValidation;
|
|
32
|
+
__decorate([
|
|
33
|
+
(0, custom_validate_validator_1.CustomValidate)((value) => value > 0, { message: '价格必须大于0' }),
|
|
34
|
+
__metadata("design:type", Number)
|
|
35
|
+
], Example1_InlineValidation.prototype, "price", void 0);
|
|
36
|
+
__decorate([
|
|
37
|
+
(0, custom_validate_validator_1.CustomValidate)((value) => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
+
const response = yield fetch(`/api/check/${value}`);
|
|
39
|
+
return response.ok;
|
|
40
|
+
}), { message: '验证失败' }),
|
|
41
|
+
__metadata("design:type", String)
|
|
42
|
+
], Example1_InlineValidation.prototype, "code", void 0);
|
|
43
|
+
__decorate([
|
|
44
|
+
(0, custom_validate_validator_1.CustomValidate)((value, context) => {
|
|
45
|
+
const minValue = context.object.minPrice || 0;
|
|
46
|
+
return value > minValue;
|
|
47
|
+
}, { message: '最大价格必须大于最小价格' }),
|
|
48
|
+
__metadata("design:type", Number)
|
|
49
|
+
], Example1_InlineValidation.prototype, "maxPrice", void 0);
|
|
50
|
+
class Example2_EntityMethod {
|
|
51
|
+
validateVersion(value) {
|
|
52
|
+
return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value);
|
|
53
|
+
}
|
|
54
|
+
checkDates(value, context) {
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
return value >= this.createdAt;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.Example2_EntityMethod = Example2_EntityMethod;
|
|
61
|
+
__decorate([
|
|
62
|
+
(0, custom_validate_validator_1.CustomValidate)('validateVersion', {
|
|
63
|
+
message: '版本号格式必须为 x.y.z'
|
|
64
|
+
}),
|
|
65
|
+
__metadata("design:type", String)
|
|
66
|
+
], Example2_EntityMethod.prototype, "version", void 0);
|
|
67
|
+
__decorate([
|
|
68
|
+
(0, custom_validate_validator_1.CustomValidate)('checkDates'),
|
|
69
|
+
__metadata("design:type", Date)
|
|
70
|
+
], Example2_EntityMethod.prototype, "publishedAt", void 0);
|
|
71
|
+
let ExampleValidationService = class ExampleValidationService {
|
|
72
|
+
constructor(userRepository) {
|
|
73
|
+
this.userRepository = userRepository;
|
|
74
|
+
}
|
|
75
|
+
usernameAvailable(username) {
|
|
76
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
+
const count = yield this.userRepository.count({
|
|
78
|
+
where: { username }
|
|
79
|
+
});
|
|
80
|
+
return count === 0;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
validateEmail(email) {
|
|
84
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
85
|
+
}
|
|
86
|
+
checkRange(value, context, min, max) {
|
|
87
|
+
return value >= min && value <= max;
|
|
88
|
+
}
|
|
89
|
+
validateBusinessRule(value, context) {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
const user = yield this.userRepository.findOne({
|
|
92
|
+
where: { id: context.object.userId },
|
|
93
|
+
relations: ['profile', 'permissions']
|
|
94
|
+
});
|
|
95
|
+
if (!user) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return user.profile.isVerified && user.permissions.length > 0;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
exports.ExampleValidationService = ExampleValidationService;
|
|
103
|
+
exports.ExampleValidationService = ExampleValidationService = __decorate([
|
|
104
|
+
(0, common_1.Injectable)(),
|
|
105
|
+
__param(0, (0, typeorm_1.InjectRepository)(UserEntity)),
|
|
106
|
+
__metadata("design:paramtypes", [typeorm_2.Repository])
|
|
107
|
+
], ExampleValidationService);
|
|
108
|
+
class Example3_ServiceValidation {
|
|
109
|
+
}
|
|
110
|
+
exports.Example3_ServiceValidation = Example3_ServiceValidation;
|
|
111
|
+
__decorate([
|
|
112
|
+
(0, custom_validate_validator_1.CustomValidate)([ExampleValidationService, 'usernameAvailable'], { message: '用户名已被使用' }),
|
|
113
|
+
__metadata("design:type", String)
|
|
114
|
+
], Example3_ServiceValidation.prototype, "username", void 0);
|
|
115
|
+
__decorate([
|
|
116
|
+
(0, custom_validate_validator_1.CustomValidate)([ExampleValidationService, 'validateEmail'], { message: '邮箱格式不正确' }),
|
|
117
|
+
__metadata("design:type", String)
|
|
118
|
+
], Example3_ServiceValidation.prototype, "email", void 0);
|
|
119
|
+
__decorate([
|
|
120
|
+
(0, custom_validate_validator_1.CustomValidate)([ExampleValidationService, 'checkRange', 18, 100], { message: '年龄必须在 18-100 之间' }),
|
|
121
|
+
__metadata("design:type", Number)
|
|
122
|
+
], Example3_ServiceValidation.prototype, "age", void 0);
|
|
123
|
+
__decorate([
|
|
124
|
+
(0, custom_validate_validator_1.CustomValidate)([ExampleValidationService, 'validateBusinessRule'], { message: '业务规则验证失败' }),
|
|
125
|
+
__metadata("design:type", String)
|
|
126
|
+
], Example3_ServiceValidation.prototype, "businessField", void 0);
|
|
127
|
+
class Example4_LazyReference {
|
|
128
|
+
}
|
|
129
|
+
exports.Example4_LazyReference = Example4_LazyReference;
|
|
130
|
+
__decorate([
|
|
131
|
+
(0, custom_validate_validator_1.CustomValidate)([() => ExampleValidationService, 'usernameAvailable'], { message: '用户名已存在' }),
|
|
132
|
+
__metadata("design:type", String)
|
|
133
|
+
], Example4_LazyReference.prototype, "username", void 0);
|
|
134
|
+
class Example5_ComplexValidation {
|
|
135
|
+
}
|
|
136
|
+
exports.Example5_ComplexValidation = Example5_ComplexValidation;
|
|
137
|
+
__decorate([
|
|
138
|
+
(0, custom_validate_validator_1.CustomValidate)((value, context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
139
|
+
if (value.length < 8) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const hasUpperCase = /[A-Z]/.test(value);
|
|
143
|
+
const hasLowerCase = /[a-z]/.test(value);
|
|
144
|
+
const hasNumber = /\d/.test(value);
|
|
145
|
+
const hasSpecial = /[@$!%*?&]/.test(value);
|
|
146
|
+
if (!(hasUpperCase && hasLowerCase && hasNumber && hasSpecial)) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const response = yield fetch(`/api/check-weak-password`, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
body: JSON.stringify({ password: value }),
|
|
152
|
+
headers: { 'Content-Type': 'application/json' }
|
|
153
|
+
});
|
|
154
|
+
const data = yield response.json();
|
|
155
|
+
return !data.isWeak;
|
|
156
|
+
}), { message: '密码不符合安全要求:至少8位,包含大小写字母、数字和特殊字符,且不在弱密码列表中' }),
|
|
157
|
+
__metadata("design:type", String)
|
|
158
|
+
], Example5_ComplexValidation.prototype, "password", void 0);
|
|
159
|
+
__decorate([
|
|
160
|
+
(0, custom_validate_validator_1.CustomValidate)((value, context) => {
|
|
161
|
+
return value === context.object.password;
|
|
162
|
+
}, { message: '两次密码输入不一致' }),
|
|
163
|
+
__metadata("design:type", String)
|
|
164
|
+
], Example5_ComplexValidation.prototype, "confirmPassword", void 0);
|
|
165
|
+
__decorate([
|
|
166
|
+
(0, custom_validate_validator_1.CustomValidate)((value, context) => {
|
|
167
|
+
if (context.object.requiresApproval) {
|
|
168
|
+
return value && value.length > 0;
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}, { message: '需要审批时必须指定审批人' }),
|
|
172
|
+
__metadata("design:type", String)
|
|
173
|
+
], Example5_ComplexValidation.prototype, "approverId", void 0);
|
|
174
|
+
let FileValidationService = class FileValidationService {
|
|
175
|
+
constructor() {
|
|
176
|
+
this.allowedMimeTypes = [
|
|
177
|
+
'image/jpeg',
|
|
178
|
+
'image/png',
|
|
179
|
+
'image/gif',
|
|
180
|
+
'application/pdf'
|
|
181
|
+
];
|
|
182
|
+
this.maxFileSize = 5 * 1024 * 1024;
|
|
183
|
+
}
|
|
184
|
+
checkFileType(mimeType) {
|
|
185
|
+
return this.allowedMimeTypes.includes(mimeType);
|
|
186
|
+
}
|
|
187
|
+
checkFileSize(size, context, maxSize) {
|
|
188
|
+
const limit = maxSize || this.maxFileSize;
|
|
189
|
+
return size <= limit;
|
|
190
|
+
}
|
|
191
|
+
checkFileHash(hash) {
|
|
192
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
193
|
+
return true;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
exports.FileValidationService = FileValidationService;
|
|
198
|
+
exports.FileValidationService = FileValidationService = __decorate([
|
|
199
|
+
(0, common_1.Injectable)()
|
|
200
|
+
], FileValidationService);
|
|
201
|
+
class Example6_FileUpload {
|
|
202
|
+
}
|
|
203
|
+
exports.Example6_FileUpload = Example6_FileUpload;
|
|
204
|
+
__decorate([
|
|
205
|
+
(0, custom_validate_validator_1.CustomValidate)([FileValidationService, 'checkFileType'], { message: '文件类型不支���,仅支持 JPEG, PNG, GIF, PDF' }),
|
|
206
|
+
__metadata("design:type", String)
|
|
207
|
+
], Example6_FileUpload.prototype, "mimeType", void 0);
|
|
208
|
+
__decorate([
|
|
209
|
+
(0, custom_validate_validator_1.CustomValidate)([FileValidationService, 'checkFileSize', 10 * 1024 * 1024], { message: '文件大小不能超过 10MB' }),
|
|
210
|
+
__metadata("design:type", Number)
|
|
211
|
+
], Example6_FileUpload.prototype, "size", void 0);
|
|
212
|
+
__decorate([
|
|
213
|
+
(0, custom_validate_validator_1.CustomValidate)([FileValidationService, 'checkFileHash'], { message: '文件已存在' }),
|
|
214
|
+
__metadata("design:type", String)
|
|
215
|
+
], Example6_FileUpload.prototype, "hash", void 0);
|
|
216
|
+
class Example7_DynamicMessage {
|
|
217
|
+
}
|
|
218
|
+
exports.Example7_DynamicMessage = Example7_DynamicMessage;
|
|
219
|
+
__decorate([
|
|
220
|
+
(0, custom_validate_validator_1.CustomValidate)((value) => value > 0 && value <= 100, {
|
|
221
|
+
message: (args) => {
|
|
222
|
+
const value = args.value;
|
|
223
|
+
if (value <= 0) {
|
|
224
|
+
return '折扣必须大于 0';
|
|
225
|
+
}
|
|
226
|
+
if (value > 100) {
|
|
227
|
+
return `折扣 ${value}% 超过了最大值 100%`;
|
|
228
|
+
}
|
|
229
|
+
return '折扣值无效';
|
|
230
|
+
}
|
|
231
|
+
}),
|
|
232
|
+
__metadata("design:type", Number)
|
|
233
|
+
], Example7_DynamicMessage.prototype, "discount", void 0);
|
|
234
|
+
let ThirdPartyValidationService = class ThirdPartyValidationService {
|
|
235
|
+
validateAddress(address) {
|
|
236
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
237
|
+
try {
|
|
238
|
+
const response = yield fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}`);
|
|
239
|
+
const data = yield response.json();
|
|
240
|
+
return data.status === 'OK' && data.results.length > 0;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
console.error('Address validation error:', error);
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
validateTaxId(taxId, context) {
|
|
249
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
250
|
+
try {
|
|
251
|
+
const response = yield fetch(`https://api.tax-service.com/validate`, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: { 'Content-Type': 'application/json' },
|
|
254
|
+
body: JSON.stringify({ taxId, country: context.object.country })
|
|
255
|
+
});
|
|
256
|
+
const data = yield response.json();
|
|
257
|
+
return data.valid;
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
console.error('Tax ID validation error:', error);
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
exports.ThirdPartyValidationService = ThirdPartyValidationService;
|
|
267
|
+
exports.ThirdPartyValidationService = ThirdPartyValidationService = __decorate([
|
|
268
|
+
(0, common_1.Injectable)()
|
|
269
|
+
], ThirdPartyValidationService);
|
|
270
|
+
class Example8_ThirdPartyAPI {
|
|
271
|
+
}
|
|
272
|
+
exports.Example8_ThirdPartyAPI = Example8_ThirdPartyAPI;
|
|
273
|
+
__decorate([
|
|
274
|
+
(0, custom_validate_validator_1.CustomValidate)([ThirdPartyValidationService, 'validateAddress'], { message: '地址无效,请输入真实存在的地址' }),
|
|
275
|
+
__metadata("design:type", String)
|
|
276
|
+
], Example8_ThirdPartyAPI.prototype, "address", void 0);
|
|
277
|
+
__decorate([
|
|
278
|
+
(0, custom_validate_validator_1.CustomValidate)([ThirdPartyValidationService, 'validateTaxId'], { message: '税号无效' }),
|
|
279
|
+
__metadata("design:type", String)
|
|
280
|
+
], Example8_ThirdPartyAPI.prototype, "taxId", void 0);
|
|
281
|
+
let CachedValidationService = class CachedValidationService {
|
|
282
|
+
constructor() {
|
|
283
|
+
this.cache = new Map();
|
|
284
|
+
this.cacheTTL = 5 * 60 * 1000;
|
|
285
|
+
}
|
|
286
|
+
expensiveValidation(value) {
|
|
287
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
const cached = this.cache.get(value);
|
|
290
|
+
if (cached && (now - cached.timestamp) < this.cacheTTL) {
|
|
291
|
+
console.log(`[Cache Hit] Value: ${value}`);
|
|
292
|
+
return cached.result;
|
|
293
|
+
}
|
|
294
|
+
console.log(`[Cache Miss] Performing validation for: ${value}`);
|
|
295
|
+
const result = yield this.doExpensiveCheck(value);
|
|
296
|
+
this.cache.set(value, { result, timestamp: now });
|
|
297
|
+
this.cleanExpiredCache();
|
|
298
|
+
return result;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
doExpensiveCheck(value) {
|
|
302
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
303
|
+
yield new Promise(resolve => setTimeout(resolve, 1000));
|
|
304
|
+
return true;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
cleanExpiredCache() {
|
|
308
|
+
const now = Date.now();
|
|
309
|
+
for (const [key, value] of this.cache.entries()) {
|
|
310
|
+
if (now - value.timestamp >= this.cacheTTL) {
|
|
311
|
+
this.cache.delete(key);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
exports.CachedValidationService = CachedValidationService;
|
|
317
|
+
exports.CachedValidationService = CachedValidationService = __decorate([
|
|
318
|
+
(0, common_1.Injectable)()
|
|
319
|
+
], CachedValidationService);
|
|
320
|
+
class Example9_CachedValidation {
|
|
321
|
+
}
|
|
322
|
+
exports.Example9_CachedValidation = Example9_CachedValidation;
|
|
323
|
+
__decorate([
|
|
324
|
+
(0, custom_validate_validator_1.CustomValidate)([CachedValidationService, 'expensiveValidation'], { message: '验证失败' }),
|
|
325
|
+
__metadata("design:type", String)
|
|
326
|
+
], Example9_CachedValidation.prototype, "field", void 0);
|
|
327
|
+
class UserEntity {
|
|
328
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ValidationArguments, ValidationOptions, ValidatorConstraintInterface } from 'class-validator';
|
|
2
|
+
import { ModuleRef } from '@nestjs/core';
|
|
3
|
+
export interface ValidationContext {
|
|
4
|
+
value: any;
|
|
5
|
+
object: any;
|
|
6
|
+
property: string;
|
|
7
|
+
constraints: any[];
|
|
8
|
+
targetName: string;
|
|
9
|
+
args: ValidationArguments;
|
|
10
|
+
}
|
|
11
|
+
export type ValidatorFunction = (value: any, context: ValidationContext, ...extraArgs: any[]) => boolean | Promise<boolean>;
|
|
12
|
+
export type ValidationConstraint = ValidatorFunction | string | [Function | (() => Function), string, ...any[]];
|
|
13
|
+
export interface CustomValidateOptions extends ValidationOptions {
|
|
14
|
+
message?: string | ((args: ValidationArguments) => string);
|
|
15
|
+
async?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare class CustomValidateValidator implements ValidatorConstraintInterface {
|
|
18
|
+
private readonly moduleRef;
|
|
19
|
+
constructor(moduleRef: ModuleRef);
|
|
20
|
+
validate(value: any, args: ValidationArguments): Promise<boolean>;
|
|
21
|
+
private callFunction;
|
|
22
|
+
private callEntityMethod;
|
|
23
|
+
private callServiceMethod;
|
|
24
|
+
defaultMessage(args: ValidationArguments): string;
|
|
25
|
+
}
|
|
26
|
+
export declare function CustomValidate(constraint: ValidationConstraint, validationOptions?: CustomValidateOptions): PropertyDecorator;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.CustomValidateValidator = void 0;
|
|
22
|
+
exports.CustomValidate = CustomValidate;
|
|
23
|
+
const class_validator_1 = require("class-validator");
|
|
24
|
+
const common_1 = require("@nestjs/common");
|
|
25
|
+
const core_1 = require("@nestjs/core");
|
|
26
|
+
const nestjs_i18n_1 = require("nestjs-i18n");
|
|
27
|
+
let CustomValidateValidator = class CustomValidateValidator {
|
|
28
|
+
constructor(moduleRef) {
|
|
29
|
+
this.moduleRef = moduleRef;
|
|
30
|
+
}
|
|
31
|
+
validate(value, args) {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
const [constraint, ...extraArgs] = args.constraints;
|
|
34
|
+
const context = {
|
|
35
|
+
value,
|
|
36
|
+
object: args.object,
|
|
37
|
+
property: args.property,
|
|
38
|
+
constraints: args.constraints,
|
|
39
|
+
targetName: args.targetName,
|
|
40
|
+
args,
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
if (typeof constraint === 'function') {
|
|
44
|
+
return yield this.callFunction(constraint, value, context, extraArgs);
|
|
45
|
+
}
|
|
46
|
+
if (typeof constraint === 'string') {
|
|
47
|
+
return yield this.callEntityMethod(args.object, constraint, value, context, extraArgs);
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(constraint)) {
|
|
50
|
+
return yield this.callServiceMethod(constraint, value, context);
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`[@CustomValidate] Invalid constraint type for property "${args.property}". ` +
|
|
53
|
+
`Expected: function, string, or array. Got: ${typeof constraint}`);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error(`[@CustomValidate] Validation error for property "${args.property}":`, error);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
callFunction(fn, value, context, extraArgs) {
|
|
62
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
const result = fn(value, context, ...extraArgs);
|
|
64
|
+
return result instanceof Promise ? yield result : result;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
callEntityMethod(entity, methodName, value, context, extraArgs) {
|
|
68
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
if (!entity || typeof entity[methodName] !== 'function') {
|
|
70
|
+
throw new Error(`[@CustomValidate] Method "${methodName}" not found on entity "${context.targetName}". ` +
|
|
71
|
+
`Make sure the method exists and is accessible.`);
|
|
72
|
+
}
|
|
73
|
+
const result = entity[methodName](value, context, ...extraArgs);
|
|
74
|
+
return result instanceof Promise ? yield result : result;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
callServiceMethod(constraint, value, context) {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
let [serviceClass, methodName, ...extraArgs] = constraint;
|
|
80
|
+
if (!serviceClass || !methodName) {
|
|
81
|
+
throw new Error(`[@CustomValidate] Invalid service constraint format. ` +
|
|
82
|
+
`Expected: [ServiceClass, 'methodName'] or [() => ServiceClass, 'methodName']`);
|
|
83
|
+
}
|
|
84
|
+
if (typeof serviceClass === 'function' && !serviceClass.prototype) {
|
|
85
|
+
serviceClass = serviceClass();
|
|
86
|
+
}
|
|
87
|
+
if (!serviceClass) {
|
|
88
|
+
throw new Error(`[@CustomValidate] Service class is undefined. ` +
|
|
89
|
+
`This may be caused by circular dependencies. ` +
|
|
90
|
+
`Consider using lazy reference: [() => ServiceClass, 'methodName']`);
|
|
91
|
+
}
|
|
92
|
+
let service;
|
|
93
|
+
try {
|
|
94
|
+
service = this.moduleRef.get(serviceClass, { strict: false });
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
throw new Error(`[@CustomValidate] Failed to resolve service "${serviceClass.name}". ` +
|
|
98
|
+
`Make sure the service is registered as a provider in a NestJS module. ` +
|
|
99
|
+
`Error: ${error.message}`);
|
|
100
|
+
}
|
|
101
|
+
if (!service || typeof service[methodName] !== 'function') {
|
|
102
|
+
throw new Error(`[@CustomValidate] Method "${methodName}" not found on service "${serviceClass.name}". ` +
|
|
103
|
+
`Available methods: ${Object.getOwnPropertyNames(Object.getPrototypeOf(service)).join(', ')}`);
|
|
104
|
+
}
|
|
105
|
+
const result = service[methodName](value, context, ...extraArgs);
|
|
106
|
+
return result instanceof Promise ? yield result : result;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
defaultMessage(args) {
|
|
110
|
+
return (0, nestjs_i18n_1.i18nValidationMessage)('validation.CUSTOM_VALIDATE')(args);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
exports.CustomValidateValidator = CustomValidateValidator;
|
|
114
|
+
exports.CustomValidateValidator = CustomValidateValidator = __decorate([
|
|
115
|
+
(0, common_1.Injectable)(),
|
|
116
|
+
(0, class_validator_1.ValidatorConstraint)({ name: 'customValidate', async: true }),
|
|
117
|
+
__metadata("design:paramtypes", [core_1.ModuleRef])
|
|
118
|
+
], CustomValidateValidator);
|
|
119
|
+
function CustomValidate(constraint, validationOptions) {
|
|
120
|
+
return (object, propertyName) => {
|
|
121
|
+
(0, class_validator_1.registerDecorator)({
|
|
122
|
+
target: object.constructor,
|
|
123
|
+
propertyName,
|
|
124
|
+
options: validationOptions,
|
|
125
|
+
constraints: [constraint],
|
|
126
|
+
validator: CustomValidateValidator,
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
}
|
package/validators/index.d.ts
CHANGED
package/validators/index.js
CHANGED
|
@@ -20,3 +20,4 @@ __exportStar(require("./file-mimetype.validator"), exports);
|
|
|
20
20
|
__exportStar(require("./is-exists.validator"), exports);
|
|
21
21
|
__exportStar(require("./is-unique.validator"), exports);
|
|
22
22
|
__exportStar(require("./same-as.validator"), exports);
|
|
23
|
+
__exportStar(require("./custom-validate.validator"), exports);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { ValidationArguments, ValidationOptions, ValidatorConstraintInterface } from 'class-validator';
|
|
2
2
|
import type { EntitySchema, FindOptionsWhere, ObjectType } from 'typeorm';
|
|
3
|
-
import {
|
|
3
|
+
import { TransactionHost } from '@nestjs-cls/transactional';
|
|
4
|
+
import { TransactionalAdapterTypeOrm } from '@nestjs-cls/transactional-adapter-typeorm';
|
|
4
5
|
export declare class IsExistsValidator implements ValidatorConstraintInterface {
|
|
5
|
-
private readonly
|
|
6
|
-
constructor(
|
|
6
|
+
private readonly transactionHost;
|
|
7
|
+
constructor(transactionHost: TransactionHost<TransactionalAdapterTypeOrm>);
|
|
8
|
+
private resolveEntity;
|
|
9
|
+
private inferEntityNameFromProperty;
|
|
7
10
|
validate<E>(value: string, args: IExistsValidationArguments<E>): Promise<boolean>;
|
|
8
11
|
defaultMessage(args: ValidationArguments): string;
|
|
9
12
|
}
|
|
10
13
|
type ExistsValidationConstraints<E> = [
|
|
11
|
-
ObjectType<E> | EntitySchema<E> | string,
|
|
14
|
+
ObjectType<E> | EntitySchema<E> | string | (() => ObjectType<E> | EntitySchema<E> | string),
|
|
12
15
|
(validationArguments: ValidationArguments) => FindOptionsWhere<E>
|
|
13
16
|
];
|
|
14
17
|
interface IExistsValidationArguments<E> extends ValidationArguments {
|
|
@@ -21,18 +21,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
exports.IsExistsValidator = void 0;
|
|
22
22
|
exports.IsExists = IsExists;
|
|
23
23
|
const class_validator_1 = require("class-validator");
|
|
24
|
-
const typeorm_1 = require("typeorm");
|
|
25
24
|
const nestjs_i18n_1 = require("nestjs-i18n");
|
|
26
25
|
const common_1 = require("@nestjs/common");
|
|
26
|
+
const transactional_1 = require("@nestjs-cls/transactional");
|
|
27
27
|
let IsExistsValidator = class IsExistsValidator {
|
|
28
|
-
constructor(
|
|
29
|
-
this.
|
|
28
|
+
constructor(transactionHost) {
|
|
29
|
+
this.transactionHost = transactionHost;
|
|
30
|
+
}
|
|
31
|
+
resolveEntity(entityRef, propertyName, object) {
|
|
32
|
+
if (typeof entityRef === 'string') {
|
|
33
|
+
return entityRef;
|
|
34
|
+
}
|
|
35
|
+
if (typeof entityRef === 'function') {
|
|
36
|
+
if (!entityRef.prototype) {
|
|
37
|
+
const resolved = entityRef();
|
|
38
|
+
if (resolved)
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return entityRef;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!entityRef) {
|
|
46
|
+
const inferredEntityName = this.inferEntityNameFromProperty(propertyName);
|
|
47
|
+
if (inferredEntityName) {
|
|
48
|
+
console.warn(`[IsExists] Entity reference is undefined for property "${propertyName}". ` +
|
|
49
|
+
`This may be caused by circular dependencies. ` +
|
|
50
|
+
`Consider using lazy reference: () => ${inferredEntityName}`);
|
|
51
|
+
return inferredEntityName;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`[IsExists] Cannot resolve entity reference for property "${propertyName}". ` +
|
|
55
|
+
`Entity is undefined. This is likely caused by circular dependencies. ` +
|
|
56
|
+
`Please use one of the following solutions:\n` +
|
|
57
|
+
`1. Lazy reference: @IsExists([() => EntityClass, ...])\n` +
|
|
58
|
+
`2. String reference: @IsExists(["EntityName", ...])\n` +
|
|
59
|
+
`3. Break the circular dependency`);
|
|
60
|
+
}
|
|
61
|
+
inferEntityNameFromProperty(propertyName) {
|
|
62
|
+
const name = propertyName.replace(/(Id|Ids|Key|Code)$/, '');
|
|
63
|
+
if (!name)
|
|
64
|
+
return null;
|
|
65
|
+
const entityName = name.charAt(0).toUpperCase() + name.slice(1) + 'Entity';
|
|
66
|
+
return entityName;
|
|
30
67
|
}
|
|
31
68
|
validate(value, args) {
|
|
32
69
|
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
-
const [
|
|
70
|
+
const [entityRef, findCondition] = args.constraints;
|
|
71
|
+
const entityClass = this.resolveEntity(entityRef, args.property, args.object);
|
|
72
|
+
const repository = this.transactionHost.tx.getRepository(entityClass);
|
|
34
73
|
args.value = value;
|
|
35
|
-
return ((yield
|
|
74
|
+
return ((yield repository.count({
|
|
36
75
|
where: findCondition(args),
|
|
37
76
|
})) > 0);
|
|
38
77
|
});
|
|
@@ -45,7 +84,7 @@ exports.IsExistsValidator = IsExistsValidator;
|
|
|
45
84
|
exports.IsExistsValidator = IsExistsValidator = __decorate([
|
|
46
85
|
(0, common_1.Injectable)(),
|
|
47
86
|
(0, class_validator_1.ValidatorConstraint)({ name: 'isExists', async: true }),
|
|
48
|
-
__metadata("design:paramtypes", [
|
|
87
|
+
__metadata("design:paramtypes", [transactional_1.TransactionHost])
|
|
49
88
|
], IsExistsValidator);
|
|
50
89
|
function IsExists(constraints, validationOptions) {
|
|
51
90
|
return (object, propertyName) => {
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { ValidationArguments, ValidationOptions, ValidatorConstraintInterface } from 'class-validator';
|
|
2
|
-
import {
|
|
2
|
+
import { EntitySchema, FindOptionsWhere, ObjectType } from 'typeorm';
|
|
3
|
+
import { TransactionHost } from '@nestjs-cls/transactional';
|
|
4
|
+
import { TransactionalAdapterTypeOrm } from '@nestjs-cls/transactional-adapter-typeorm';
|
|
3
5
|
export declare class IsUniqueValidator implements ValidatorConstraintInterface {
|
|
4
|
-
private readonly
|
|
5
|
-
constructor(
|
|
6
|
+
private readonly transactionHost;
|
|
7
|
+
constructor(transactionHost: TransactionHost<TransactionalAdapterTypeOrm>);
|
|
8
|
+
private resolveEntity;
|
|
9
|
+
private inferEntityNameFromProperty;
|
|
6
10
|
validate<E>(value: string, args: IUniqueValidationArguments<E>): Promise<boolean>;
|
|
7
11
|
defaultMessage(args: ValidationArguments): string;
|
|
8
12
|
}
|
|
9
13
|
type UniqueValidationConstraints<E> = [
|
|
10
|
-
ObjectType<E> | EntitySchema<E> | string,
|
|
14
|
+
ObjectType<E> | EntitySchema<E> | string | (() => ObjectType<E> | EntitySchema<E> | string),
|
|
11
15
|
(validationArguments: ValidationArguments) => FindOptionsWhere<E>
|
|
12
16
|
];
|
|
13
17
|
interface IUniqueValidationArguments<E> extends ValidationArguments {
|
|
@@ -21,31 +21,68 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
exports.IsUniqueValidator = void 0;
|
|
22
22
|
exports.IsUnique = IsUnique;
|
|
23
23
|
const class_validator_1 = require("class-validator");
|
|
24
|
-
const typeorm_1 = require("typeorm");
|
|
25
24
|
const nestjs_i18n_1 = require("nestjs-i18n");
|
|
25
|
+
const common_1 = require("@nestjs/common");
|
|
26
|
+
const transactional_1 = require("@nestjs-cls/transactional");
|
|
26
27
|
let IsUniqueValidator = class IsUniqueValidator {
|
|
27
|
-
constructor(
|
|
28
|
-
this.
|
|
28
|
+
constructor(transactionHost) {
|
|
29
|
+
this.transactionHost = transactionHost;
|
|
30
|
+
}
|
|
31
|
+
resolveEntity(entityRef, propertyName, object) {
|
|
32
|
+
if (typeof entityRef === 'string') {
|
|
33
|
+
return entityRef;
|
|
34
|
+
}
|
|
35
|
+
if (typeof entityRef === 'function') {
|
|
36
|
+
if (!entityRef.prototype) {
|
|
37
|
+
const resolved = entityRef();
|
|
38
|
+
if (resolved)
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return entityRef;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!entityRef) {
|
|
46
|
+
const inferredEntityName = this.inferEntityNameFromProperty(propertyName);
|
|
47
|
+
if (inferredEntityName) {
|
|
48
|
+
console.warn(`[IsUnique] Entity reference is undefined for property "${propertyName}". ` +
|
|
49
|
+
`This may be caused by circular dependencies. ` +
|
|
50
|
+
`Consider using lazy reference: () => ${inferredEntityName}`);
|
|
51
|
+
return inferredEntityName;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`[IsUnique] Cannot resolve entity reference for property "${propertyName}". ` +
|
|
55
|
+
`Entity is undefined. This is likely caused by circular dependencies. ` +
|
|
56
|
+
`Please use one of the following solutions:\n` +
|
|
57
|
+
`1. Lazy reference: @IsUnique([() => EntityClass, ...])\n` +
|
|
58
|
+
`2. String reference: @IsUnique(["EntityName", ...])\n` +
|
|
59
|
+
`3. Break the circular dependency`);
|
|
60
|
+
}
|
|
61
|
+
inferEntityNameFromProperty(propertyName) {
|
|
62
|
+
const name = propertyName.replace(/(Id|Ids|Key|Code)$/, '');
|
|
63
|
+
if (!name)
|
|
64
|
+
return null;
|
|
65
|
+
const entityName = name.charAt(0).toUpperCase() + name.slice(1) + 'Entity';
|
|
66
|
+
return entityName;
|
|
29
67
|
}
|
|
30
68
|
validate(value, args) {
|
|
31
69
|
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
-
const [
|
|
70
|
+
const [entityRef, findCondition] = args.constraints;
|
|
71
|
+
const entityClass = this.resolveEntity(entityRef, args.property, args.object);
|
|
72
|
+
const repository = this.transactionHost.tx.getRepository(entityClass);
|
|
33
73
|
args.value = value;
|
|
34
74
|
let exists;
|
|
35
75
|
const defCon = findCondition(args);
|
|
36
|
-
const pkCols =
|
|
37
|
-
.getMetadata(entityClass)
|
|
76
|
+
const pkCols = repository.metadata
|
|
38
77
|
.primaryColumns.map((column) => column.propertyName);
|
|
39
78
|
const isNew = pkCols.some((pk) => args.object[pk]);
|
|
40
79
|
if (!isNew) {
|
|
41
80
|
exists =
|
|
42
|
-
(yield
|
|
43
|
-
.getRepository(entityClass)
|
|
81
|
+
(yield repository
|
|
44
82
|
.count({ where: defCon })) < 1;
|
|
45
83
|
}
|
|
46
84
|
else {
|
|
47
|
-
const entities = yield
|
|
48
|
-
.getRepository(entityClass)
|
|
85
|
+
const entities = yield repository
|
|
49
86
|
.createQueryBuilder()
|
|
50
87
|
.where(defCon)
|
|
51
88
|
.select(pkCols)
|
|
@@ -75,8 +112,9 @@ let IsUniqueValidator = class IsUniqueValidator {
|
|
|
75
112
|
};
|
|
76
113
|
exports.IsUniqueValidator = IsUniqueValidator;
|
|
77
114
|
exports.IsUniqueValidator = IsUniqueValidator = __decorate([
|
|
115
|
+
(0, common_1.Injectable)(),
|
|
78
116
|
(0, class_validator_1.ValidatorConstraint)({ name: 'isUnique', async: true }),
|
|
79
|
-
__metadata("design:paramtypes", [
|
|
117
|
+
__metadata("design:paramtypes", [transactional_1.TransactionHost])
|
|
80
118
|
], IsUniqueValidator);
|
|
81
119
|
function IsUnique(constraints, validationOptions) {
|
|
82
120
|
return function (object, propertyName) {
|