@nest-omni/core 4.1.3-20 → 4.1.3-22
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/audit/audit.module.d.ts +1 -0
- package/audit/audit.module.js +5 -3
- package/audit/controllers/audit.controller.d.ts +3 -11
- package/audit/controllers/audit.controller.js +12 -19
- package/audit/decorators/audit-operation.decorator.d.ts +0 -7
- package/audit/decorators/audit-operation.decorator.js +0 -7
- package/audit/dto/audit-action-query.dto.d.ts +13 -0
- package/audit/dto/audit-action-query.dto.js +77 -0
- package/audit/dto/index.d.ts +1 -0
- package/audit/dto/index.js +1 -0
- package/audit/entities/entity-audit-log.entity.d.ts +1 -4
- package/audit/entities/entity-audit-log.entity.js +1 -17
- package/audit/entities/manual-operation-log.entity.d.ts +0 -2
- package/audit/entities/manual-operation-log.entity.js +0 -8
- package/audit/enums/audit.enums.d.ts +0 -8
- package/audit/enums/audit.enums.js +1 -10
- package/audit/examples/decorator-value-mapping.example.d.ts +70 -0
- package/audit/examples/decorator-value-mapping.example.js +414 -0
- package/audit/index.d.ts +1 -0
- package/audit/index.js +5 -1
- package/audit/interceptors/audit.interceptor.d.ts +1 -0
- package/audit/interceptors/audit.interceptor.js +19 -11
- package/audit/interfaces/audit.interfaces.d.ts +2 -17
- package/audit/services/audit-context.service.d.ts +9 -0
- package/audit/services/entity-audit.service.d.ts +65 -24
- package/audit/services/entity-audit.service.js +280 -93
- package/audit/services/manual-audit-log.service.d.ts +0 -1
- package/audit/services/manual-audit-log.service.js +1 -3
- package/audit/subscribers/entity-audit.subscriber.d.ts +1 -0
- package/audit/subscribers/entity-audit.subscriber.js +22 -5
- package/cache/cache.module.d.ts +7 -2
- package/cache/cache.module.js +9 -7
- package/cache/cache.service.d.ts +4 -4
- package/cache/cache.service.js +5 -5
- package/cache/entities/index.d.ts +1 -0
- package/cache/entities/index.js +17 -0
- package/cache/entities/typeorm-cache.entity.d.ts +71 -0
- package/cache/entities/typeorm-cache.entity.js +110 -0
- package/cache/index.d.ts +2 -1
- package/cache/index.js +19 -2
- package/cache/providers/index.d.ts +2 -1
- package/cache/providers/index.js +2 -1
- package/cache/providers/lrucache.provider.d.ts +76 -0
- package/cache/providers/lrucache.provider.js +226 -0
- package/cache/providers/typeorm-cache.provider.d.ts +211 -0
- package/cache/providers/typeorm-cache.provider.js +483 -0
- package/common/boilerplate.polyfill.d.ts +1 -0
- package/common/boilerplate.polyfill.js +17 -0
- package/common/helpers/validation-metadata-helper.d.ts +55 -0
- package/common/helpers/validation-metadata-helper.js +60 -0
- package/common/index.d.ts +1 -0
- package/common/index.js +4 -0
- package/decorators/field.decorators.d.ts +71 -2
- package/decorators/field.decorators.js +147 -18
- package/decorators/transform.decorators.d.ts +0 -2
- package/decorators/transform.decorators.js +0 -23
- package/filters/bad-request.filter.js +19 -4
- package/http-client/utils/context-extractor.util.js +2 -0
- package/ip-filter/constants.d.ts +21 -0
- package/ip-filter/constants.js +24 -0
- package/ip-filter/decorators/index.d.ts +1 -0
- package/ip-filter/decorators/index.js +17 -0
- package/ip-filter/decorators/ip-filter.decorator.d.ts +58 -0
- package/ip-filter/decorators/ip-filter.decorator.js +79 -0
- package/ip-filter/guards/index.d.ts +1 -0
- package/ip-filter/guards/index.js +17 -0
- package/ip-filter/guards/ip-filter.guard.d.ts +62 -0
- package/ip-filter/guards/ip-filter.guard.js +174 -0
- package/ip-filter/index.d.ts +7 -0
- package/ip-filter/index.js +23 -0
- package/ip-filter/interfaces/index.d.ts +4 -0
- package/ip-filter/interfaces/index.js +20 -0
- package/ip-filter/interfaces/ip-filter-async-options.interface.d.ts +15 -0
- package/ip-filter/interfaces/ip-filter-async-options.interface.js +2 -0
- package/ip-filter/interfaces/ip-filter-metadata.interface.d.ts +26 -0
- package/ip-filter/interfaces/ip-filter-metadata.interface.js +2 -0
- package/ip-filter/interfaces/ip-filter-options.interface.d.ts +34 -0
- package/ip-filter/interfaces/ip-filter-options.interface.js +2 -0
- package/ip-filter/interfaces/ip-rule.interface.d.ts +36 -0
- package/ip-filter/interfaces/ip-rule.interface.js +2 -0
- package/ip-filter/ip-filter.module.d.ts +55 -0
- package/ip-filter/ip-filter.module.js +105 -0
- package/ip-filter/services/index.d.ts +1 -0
- package/ip-filter/services/index.js +17 -0
- package/ip-filter/services/ip-filter.service.d.ts +92 -0
- package/ip-filter/services/ip-filter.service.js +238 -0
- package/ip-filter/utils/index.d.ts +1 -0
- package/ip-filter/utils/index.js +17 -0
- package/ip-filter/utils/ip-utils.d.ts +61 -0
- package/ip-filter/utils/ip-utils.js +162 -0
- package/package.json +23 -24
- package/providers/context.provider.d.ts +9 -0
- package/providers/context.provider.js +13 -0
- package/setup/bootstrap.setup.d.ts +1 -1
- package/setup/bootstrap.setup.js +1 -1
- package/shared/service-registry.module.js +0 -1
- package/cache/providers/memory-cache.provider.d.ts +0 -69
- package/cache/providers/memory-cache.provider.js +0 -237
|
@@ -7,6 +7,52 @@ interface IFieldOptions {
|
|
|
7
7
|
nullable?: boolean;
|
|
8
8
|
group?: string[];
|
|
9
9
|
message?: string;
|
|
10
|
+
skipValidation?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 字段标签(多语言)
|
|
13
|
+
* 用于审计日志、验证错误消息、UI 显示等
|
|
14
|
+
*/
|
|
15
|
+
fieldLabel?: string | {
|
|
16
|
+
zh?: string;
|
|
17
|
+
en?: string;
|
|
18
|
+
[lang: string]: string | undefined;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 字段描述(多语言)
|
|
22
|
+
* 用于 API 文档、审计日志等
|
|
23
|
+
*/
|
|
24
|
+
fieldDescription?: string | {
|
|
25
|
+
zh?: string;
|
|
26
|
+
en?: string;
|
|
27
|
+
[lang: string]: string | undefined;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* 示例值
|
|
31
|
+
* 用于 API 文档
|
|
32
|
+
*/
|
|
33
|
+
fieldExample?: any;
|
|
34
|
+
/**
|
|
35
|
+
* 值标签映射(多语言)
|
|
36
|
+
* 用于枚举、布尔值的友好显示(审计日志、UI)
|
|
37
|
+
*/
|
|
38
|
+
valueLabels?: {
|
|
39
|
+
[value: string]: string | {
|
|
40
|
+
zh?: string;
|
|
41
|
+
en?: string;
|
|
42
|
+
[lang: string]: string | undefined;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* 格式化函数
|
|
47
|
+
* 用于自定义显示格式(审计日志)
|
|
48
|
+
* 注意:命名为 displayFormatter 避免与 ApiPropertyOptions.format 冲突
|
|
49
|
+
*/
|
|
50
|
+
displayFormatter?: (value: any, language?: string) => string;
|
|
51
|
+
/**
|
|
52
|
+
* 是否敏感字段
|
|
53
|
+
* 用于审计日志脱敏
|
|
54
|
+
*/
|
|
55
|
+
sensitive?: boolean;
|
|
10
56
|
}
|
|
11
57
|
interface INumberFieldOptions extends IFieldOptions {
|
|
12
58
|
min?: number;
|
|
@@ -58,8 +104,6 @@ export declare function EnumFieldOptional<TEnum extends object>(getEnum: () => T
|
|
|
58
104
|
export declare function ClassFieldOptional<TClass extends object>(getClass: () => TClass, options?: Omit<ApiPropertyOptions, 'type' | 'required'> & IClassFieldOptions): PropertyDecorator;
|
|
59
105
|
export declare function EmailField(options?: Omit<ApiPropertyOptions, 'type'> & IStringFieldOptions & IEmailFieldOptions): PropertyDecorator;
|
|
60
106
|
export declare function EmailFieldOptional(options?: Omit<ApiPropertyOptions, 'type'> & IStringFieldOptions & IEmailFieldOptions): PropertyDecorator;
|
|
61
|
-
export declare function PhoneField(options?: Omit<ApiPropertyOptions, 'type'> & IFieldOptions): PropertyDecorator;
|
|
62
|
-
export declare function PhoneFieldOptional(options?: Omit<ApiPropertyOptions, 'type' | 'required'> & IFieldOptions): PropertyDecorator;
|
|
63
107
|
export declare function UUIDField(options?: Omit<ApiPropertyOptions, 'type' | 'format' | 'isArray'> & IFieldOptions): PropertyDecorator;
|
|
64
108
|
export declare function UUIDFieldOptional(options?: Omit<ApiPropertyOptions, 'type' | 'required' | 'isArray'> & IFieldOptions): PropertyDecorator;
|
|
65
109
|
export declare function URLField(options?: Omit<ApiPropertyOptions, 'type'> & IURLFieldOptions & IStringFieldOptions): PropertyDecorator;
|
|
@@ -76,4 +120,29 @@ export declare function TimeZoneField(options?: Omit<ApiPropertyOptions, 'type'>
|
|
|
76
120
|
export declare function TimeZoneFieldOptional(options?: Omit<ApiPropertyOptions, 'type'> & IStringFieldOptions): PropertyDecorator;
|
|
77
121
|
export declare function LocaleField(options?: Omit<ApiPropertyOptions, 'type'> & IStringFieldOptions): PropertyDecorator;
|
|
78
122
|
export declare function LocaleFieldOptional(options?: Omit<ApiPropertyOptions, 'type'> & IStringFieldOptions): PropertyDecorator;
|
|
123
|
+
/**
|
|
124
|
+
* @Field 装饰器
|
|
125
|
+
*
|
|
126
|
+
* 用于只需要元数据(Swagger、审计、UI配置)而不需要验证的字段
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* // 只需要 Swagger 文档和审计元数据,不需要验证
|
|
130
|
+
* @Field({
|
|
131
|
+
* label: { zh: '备注', en: 'Remarks' },
|
|
132
|
+
* description: { zh: '用户备注信息', en: 'User remarks' },
|
|
133
|
+
* ui: { showInList: true, showInDetail: true },
|
|
134
|
+
* })
|
|
135
|
+
* remarks: string;
|
|
136
|
+
*/
|
|
137
|
+
export declare function Field(options?: Omit<ApiPropertyOptions, 'type'> & IFieldOptions & {
|
|
138
|
+
type?: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
139
|
+
}): PropertyDecorator;
|
|
140
|
+
/**
|
|
141
|
+
* @FieldOptional 装饰器
|
|
142
|
+
*
|
|
143
|
+
* 可选的元数据专用字段
|
|
144
|
+
*/
|
|
145
|
+
export declare function FieldOptional(options?: Omit<ApiPropertyOptions, 'type' | 'required'> & IFieldOptions & {
|
|
146
|
+
type?: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
147
|
+
}): PropertyDecorator;
|
|
79
148
|
export {};
|
|
@@ -19,8 +19,6 @@ exports.EnumFieldOptional = EnumFieldOptional;
|
|
|
19
19
|
exports.ClassFieldOptional = ClassFieldOptional;
|
|
20
20
|
exports.EmailField = EmailField;
|
|
21
21
|
exports.EmailFieldOptional = EmailFieldOptional;
|
|
22
|
-
exports.PhoneField = PhoneField;
|
|
23
|
-
exports.PhoneFieldOptional = PhoneFieldOptional;
|
|
24
22
|
exports.UUIDField = UUIDField;
|
|
25
23
|
exports.UUIDFieldOptional = UUIDFieldOptional;
|
|
26
24
|
exports.URLField = URLField;
|
|
@@ -37,6 +35,8 @@ exports.TimeZoneField = TimeZoneField;
|
|
|
37
35
|
exports.TimeZoneFieldOptional = TimeZoneFieldOptional;
|
|
38
36
|
exports.LocaleField = LocaleField;
|
|
39
37
|
exports.LocaleFieldOptional = LocaleFieldOptional;
|
|
38
|
+
exports.Field = Field;
|
|
39
|
+
exports.FieldOptional = FieldOptional;
|
|
40
40
|
const common_1 = require("@nestjs/common");
|
|
41
41
|
const swagger_1 = require("@nestjs/swagger");
|
|
42
42
|
const nestjs_i18n_1 = require("nestjs-i18n");
|
|
@@ -46,6 +46,62 @@ const constants_1 = require("../constants");
|
|
|
46
46
|
const property_decorators_1 = require("./property.decorators");
|
|
47
47
|
const transform_decorators_1 = require("./transform.decorators");
|
|
48
48
|
const validator_decorators_1 = require("./validator.decorators");
|
|
49
|
+
// ========================================
|
|
50
|
+
// Unified Field Options Integration
|
|
51
|
+
// ========================================
|
|
52
|
+
const validation_metadata_helper_1 = require("../common/helpers/validation-metadata-helper");
|
|
53
|
+
// ========================================
|
|
54
|
+
// Helper: Apply Audit Metadata from Unified Options
|
|
55
|
+
// ========================================
|
|
56
|
+
/**
|
|
57
|
+
* 应用审计元数据装饰器
|
|
58
|
+
*
|
|
59
|
+
* 将字段元数据存储到 Reflect metadata,供审计日志系统使用
|
|
60
|
+
*/
|
|
61
|
+
function applyAuditMetadata(options) {
|
|
62
|
+
return (target, propertyKey) => {
|
|
63
|
+
// 只有当有相关元数据时才存储
|
|
64
|
+
if (options.fieldLabel ||
|
|
65
|
+
options.fieldDescription ||
|
|
66
|
+
options.valueLabels ||
|
|
67
|
+
options.displayFormatter ||
|
|
68
|
+
options.sensitive) {
|
|
69
|
+
const constructor = target.constructor;
|
|
70
|
+
const AUDIT_FIELD_OPTIONS_KEY = 'FIELD_AUDIT_OPTIONS';
|
|
71
|
+
// 获取现有的审计元数据
|
|
72
|
+
const existingOptions = Reflect.getMetadata(AUDIT_FIELD_OPTIONS_KEY, constructor) || {};
|
|
73
|
+
// 合并元数据
|
|
74
|
+
existingOptions[propertyKey] = {
|
|
75
|
+
label: options.fieldLabel,
|
|
76
|
+
description: options.fieldDescription,
|
|
77
|
+
example: options.fieldExample,
|
|
78
|
+
valueLabels: options.valueLabels,
|
|
79
|
+
formatter: options.displayFormatter,
|
|
80
|
+
sensitive: options.sensitive,
|
|
81
|
+
};
|
|
82
|
+
// 存储回 Reflect
|
|
83
|
+
Reflect.defineMetadata(AUDIT_FIELD_OPTIONS_KEY, existingOptions, constructor);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 应用验证元数据装饰器
|
|
89
|
+
*
|
|
90
|
+
* 存储验证元数据到 Reflect metadata,供验证错误过滤器使用
|
|
91
|
+
* 生成友好的多语言错误消息
|
|
92
|
+
*/
|
|
93
|
+
function applyValidationMetadata(options) {
|
|
94
|
+
return (target, propertyKey) => {
|
|
95
|
+
// 只有当有相关元数据时才存储
|
|
96
|
+
if (options.fieldLabel) {
|
|
97
|
+
(0, validation_metadata_helper_1.setValidationMetadata)(target.constructor, String(propertyKey), {
|
|
98
|
+
fieldName: String(propertyKey),
|
|
99
|
+
label: options.fieldLabel,
|
|
100
|
+
description: options.fieldDescription,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
49
105
|
function NumberField(options = {}) {
|
|
50
106
|
const decorators = [(0, class_transformer_1.Type)(() => Number)];
|
|
51
107
|
if (options.nullable) {
|
|
@@ -95,6 +151,10 @@ function NumberField(options = {}) {
|
|
|
95
151
|
message: (0, nestjs_i18n_1.i18nValidationMessage)('validation.IS_POSITIVE'),
|
|
96
152
|
}));
|
|
97
153
|
}
|
|
154
|
+
// 应用审计元数据(如果有)
|
|
155
|
+
decorators.push(applyAuditMetadata(options));
|
|
156
|
+
// 应用验证元数据(用于友好的错误消息)
|
|
157
|
+
decorators.push(applyValidationMetadata(options));
|
|
98
158
|
return (0, common_1.applyDecorators)(...decorators);
|
|
99
159
|
}
|
|
100
160
|
function NumberFieldOptional(options = {}) {
|
|
@@ -138,6 +198,10 @@ function StringField(options = {}) {
|
|
|
138
198
|
if (options.toUpperCase) {
|
|
139
199
|
decorators.push((0, transform_decorators_1.ToUpperCase)());
|
|
140
200
|
}
|
|
201
|
+
// 应用审计元数据(如果有)
|
|
202
|
+
decorators.push(applyAuditMetadata(options));
|
|
203
|
+
// 应用验证元数据(用于友好的错误消息)
|
|
204
|
+
decorators.push(applyValidationMetadata(options));
|
|
141
205
|
return (0, common_1.applyDecorators)(...decorators);
|
|
142
206
|
}
|
|
143
207
|
function StringFieldOptional(options = {}) {
|
|
@@ -172,6 +236,10 @@ function BooleanField(options = {}) {
|
|
|
172
236
|
if (options.swagger !== false) {
|
|
173
237
|
decorators.push((0, swagger_1.ApiProperty)(Object.assign({ type: Boolean }, options)));
|
|
174
238
|
}
|
|
239
|
+
// 应用审计元数据(如果有)
|
|
240
|
+
decorators.push(applyAuditMetadata(options));
|
|
241
|
+
// 应用验证元数据(用于友好的错误消息)
|
|
242
|
+
decorators.push(applyValidationMetadata(options));
|
|
175
243
|
return (0, common_1.applyDecorators)(...decorators);
|
|
176
244
|
}
|
|
177
245
|
function BooleanFieldOptional(options = {}) {
|
|
@@ -245,6 +313,10 @@ function EnumField(getEnum, options = {}) {
|
|
|
245
313
|
if (options.swagger !== false) {
|
|
246
314
|
decorators.push((0, property_decorators_1.ApiEnumProperty)(getEnum, Object.assign(Object.assign({}, options), { isArray: options.each })));
|
|
247
315
|
}
|
|
316
|
+
// 应用审计元数据(如果有)
|
|
317
|
+
decorators.push(applyAuditMetadata(options));
|
|
318
|
+
// 应用验证元数据(用于友好的错误消息)
|
|
319
|
+
decorators.push(applyValidationMetadata(options));
|
|
248
320
|
return (0, common_1.applyDecorators)(...decorators);
|
|
249
321
|
}
|
|
250
322
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
@@ -303,22 +375,6 @@ function EmailField(options = {}) {
|
|
|
303
375
|
function EmailFieldOptional(options = {}) {
|
|
304
376
|
return (0, common_1.applyDecorators)((0, validator_decorators_1.IsEmptyable)(), EmailField(Object.assign({ required: false }, options)));
|
|
305
377
|
}
|
|
306
|
-
function PhoneField(options = {}) {
|
|
307
|
-
const decorators = [(0, validator_decorators_1.IsPhoneNumber)(), (0, transform_decorators_1.PhoneNumberSerializer)()];
|
|
308
|
-
if (options.nullable) {
|
|
309
|
-
decorators.push((0, validator_decorators_1.IsNullable)());
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
decorators.push((0, class_validator_1.NotEquals)(null));
|
|
313
|
-
}
|
|
314
|
-
if (options.swagger !== false) {
|
|
315
|
-
decorators.push((0, swagger_1.ApiProperty)(Object.assign({ type: String }, options)));
|
|
316
|
-
}
|
|
317
|
-
return (0, common_1.applyDecorators)(...decorators);
|
|
318
|
-
}
|
|
319
|
-
function PhoneFieldOptional(options = {}) {
|
|
320
|
-
return (0, common_1.applyDecorators)((0, validator_decorators_1.IsEmptyable)(), PhoneField(Object.assign({ required: false }, options)));
|
|
321
|
-
}
|
|
322
378
|
function UUIDField(options = {}) {
|
|
323
379
|
const decorators = [
|
|
324
380
|
(0, class_transformer_1.Type)(() => String),
|
|
@@ -417,6 +473,10 @@ function DateField(options = {}) {
|
|
|
417
473
|
const swaggerOptions = Object.assign({ type: Date, example: (_a = options.example) !== null && _a !== void 0 ? _a : new Date() }, options);
|
|
418
474
|
decorators.push((0, swagger_1.ApiProperty)(swaggerOptions));
|
|
419
475
|
}
|
|
476
|
+
// 应用审计元数据(如果有)
|
|
477
|
+
decorators.push(applyAuditMetadata(options));
|
|
478
|
+
// 应用验证元数据(用于友好的错误消息)
|
|
479
|
+
decorators.push(applyValidationMetadata(options));
|
|
420
480
|
return (0, common_1.applyDecorators)(...decorators);
|
|
421
481
|
}
|
|
422
482
|
function DateFieldOptional(options = {}) {
|
|
@@ -504,3 +564,72 @@ function LocaleField(options = {}) {
|
|
|
504
564
|
function LocaleFieldOptional(options = {}) {
|
|
505
565
|
return (0, common_1.applyDecorators)((0, validator_decorators_1.IsEmptyable)(), LocaleField(Object.assign({ required: false }, options)));
|
|
506
566
|
}
|
|
567
|
+
// ========================================
|
|
568
|
+
// @Field - 元数据专用装饰器
|
|
569
|
+
// ========================================
|
|
570
|
+
/**
|
|
571
|
+
* @Field 装饰器
|
|
572
|
+
*
|
|
573
|
+
* 用于只需要元数据(Swagger、审计、UI配置)而不需要验证的字段
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* // 只需要 Swagger 文档和审计元数据,不需要验证
|
|
577
|
+
* @Field({
|
|
578
|
+
* label: { zh: '备注', en: 'Remarks' },
|
|
579
|
+
* description: { zh: '用户备注信息', en: 'User remarks' },
|
|
580
|
+
* ui: { showInList: true, showInDetail: true },
|
|
581
|
+
* })
|
|
582
|
+
* remarks: string;
|
|
583
|
+
*/
|
|
584
|
+
function Field(options = {}) {
|
|
585
|
+
const decorators = [];
|
|
586
|
+
// 标准化选项
|
|
587
|
+
// 添加类型转换(仅用于序列化)
|
|
588
|
+
const fieldType = options.type || 'string';
|
|
589
|
+
switch (fieldType) {
|
|
590
|
+
case 'number':
|
|
591
|
+
decorators.push((0, class_transformer_1.Type)(() => Number));
|
|
592
|
+
break;
|
|
593
|
+
case 'boolean':
|
|
594
|
+
decorators.push((0, class_transformer_1.Type)(() => Boolean));
|
|
595
|
+
break;
|
|
596
|
+
case 'object':
|
|
597
|
+
decorators.push((0, class_transformer_1.Type)(() => Object));
|
|
598
|
+
break;
|
|
599
|
+
case 'array':
|
|
600
|
+
decorators.push((0, class_transformer_1.Type)(() => Array));
|
|
601
|
+
if (options.each)
|
|
602
|
+
decorators.push((0, transform_decorators_1.ToArray)());
|
|
603
|
+
break;
|
|
604
|
+
case 'string':
|
|
605
|
+
default:
|
|
606
|
+
decorators.push((0, class_transformer_1.Type)(() => String));
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
// Swagger 文档
|
|
610
|
+
if (options.swagger !== false) {
|
|
611
|
+
const swaggerType = fieldType === 'array'
|
|
612
|
+
? String
|
|
613
|
+
: fieldType === 'object'
|
|
614
|
+
? Object
|
|
615
|
+
: fieldType === 'boolean'
|
|
616
|
+
? Boolean
|
|
617
|
+
: fieldType === 'number'
|
|
618
|
+
? Number
|
|
619
|
+
: String;
|
|
620
|
+
decorators.push((0, swagger_1.ApiProperty)(Object.assign({ type: swaggerType, isArray: fieldType === 'array' || options.each }, options)));
|
|
621
|
+
}
|
|
622
|
+
// 应用审计元数据
|
|
623
|
+
decorators.push(applyAuditMetadata(options));
|
|
624
|
+
// 应用验证元数据(用于错误消息)
|
|
625
|
+
decorators.push(applyValidationMetadata(options));
|
|
626
|
+
return (0, common_1.applyDecorators)(...decorators);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* @FieldOptional 装饰器
|
|
630
|
+
*
|
|
631
|
+
* 可选的元数据专用字段
|
|
632
|
+
*/
|
|
633
|
+
function FieldOptional(options = {}) {
|
|
634
|
+
return (0, common_1.applyDecorators)((0, validator_decorators_1.IsEmptyable)(), Field(Object.assign({ required: false }, options)));
|
|
635
|
+
}
|
|
@@ -31,5 +31,3 @@ export declare function ToInt(): PropertyDecorator;
|
|
|
31
31
|
export declare function ToArray(): PropertyDecorator;
|
|
32
32
|
export declare function ToLowerCase(): PropertyDecorator;
|
|
33
33
|
export declare function ToUpperCase(): PropertyDecorator;
|
|
34
|
-
export declare function S3UrlParser(): PropertyDecorator;
|
|
35
|
-
export declare function PhoneNumberSerializer(): PropertyDecorator;
|
|
@@ -6,12 +6,8 @@ exports.ToInt = ToInt;
|
|
|
6
6
|
exports.ToArray = ToArray;
|
|
7
7
|
exports.ToLowerCase = ToLowerCase;
|
|
8
8
|
exports.ToUpperCase = ToUpperCase;
|
|
9
|
-
exports.S3UrlParser = S3UrlParser;
|
|
10
|
-
exports.PhoneNumberSerializer = PhoneNumberSerializer;
|
|
11
9
|
const class_transformer_1 = require("class-transformer");
|
|
12
|
-
const libphonenumber_js_1 = require("libphonenumber-js");
|
|
13
10
|
const lodash_1 = require("lodash");
|
|
14
|
-
const providers_1 = require("../providers");
|
|
15
11
|
/**
|
|
16
12
|
* @description trim spaces from start and end, replace multiple spaces with one.
|
|
17
13
|
* @example
|
|
@@ -108,22 +104,3 @@ function ToUpperCase() {
|
|
|
108
104
|
toClassOnly: true,
|
|
109
105
|
});
|
|
110
106
|
}
|
|
111
|
-
function S3UrlParser() {
|
|
112
|
-
return (0, class_transformer_1.Transform)((params) => {
|
|
113
|
-
const key = params.value;
|
|
114
|
-
switch (params.type) {
|
|
115
|
-
case class_transformer_1.TransformationType.CLASS_TO_PLAIN: {
|
|
116
|
-
return providers_1.GeneratorProvider.getS3PublicUrl(key);
|
|
117
|
-
}
|
|
118
|
-
case class_transformer_1.TransformationType.PLAIN_TO_CLASS: {
|
|
119
|
-
return providers_1.GeneratorProvider.getS3Key(key);
|
|
120
|
-
}
|
|
121
|
-
default: {
|
|
122
|
-
return key;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
function PhoneNumberSerializer() {
|
|
128
|
-
return (0, class_transformer_1.Transform)((params) => (0, libphonenumber_js_1.parsePhoneNumber)(params.value).number);
|
|
129
|
-
}
|
|
@@ -10,9 +10,10 @@ exports.HttpExceptionFilter = void 0;
|
|
|
10
10
|
const common_1 = require("@nestjs/common");
|
|
11
11
|
const typeorm_1 = require("typeorm");
|
|
12
12
|
const nestjs_i18n_1 = require("nestjs-i18n");
|
|
13
|
+
const validation_metadata_helper_1 = require("../common/helpers/validation-metadata-helper");
|
|
13
14
|
let HttpExceptionFilter = class HttpExceptionFilter {
|
|
14
15
|
catch(exception, host) {
|
|
15
|
-
var _a, _b, _c, _d;
|
|
16
|
+
var _a, _b, _c, _d, _e;
|
|
16
17
|
const i18n = nestjs_i18n_1.I18nContext.current(host);
|
|
17
18
|
const ctx = host.switchToHttp();
|
|
18
19
|
const response = ctx.getResponse();
|
|
@@ -48,12 +49,26 @@ let HttpExceptionFilter = class HttpExceptionFilter {
|
|
|
48
49
|
return error;
|
|
49
50
|
};
|
|
50
51
|
if (exception instanceof nestjs_i18n_1.I18nValidationException) {
|
|
51
|
-
const
|
|
52
|
-
const constraint = Object.values(
|
|
52
|
+
const firstError = getFirstError(exception.errors[0], exception.errors[0].property);
|
|
53
|
+
const constraint = Object.values(firstError.constraints)[0];
|
|
53
54
|
const [translationKey, argsString] = constraint.split('|');
|
|
54
55
|
const args = !!argsString ? JSON.parse(argsString) : {};
|
|
56
|
+
// 获取当前语言
|
|
57
|
+
const lang = i18n.lang || 'zh';
|
|
58
|
+
// 在 stringify 之前获取 target
|
|
59
|
+
// firstError.target 可能是实例而不是构造函数,所以需要获取 constructor
|
|
60
|
+
let target = ((_e = firstError.target) === null || _e === void 0 ? void 0 : _e.constructor) || firstError.target;
|
|
61
|
+
if (!target && (firstError === null || firstError === void 0 ? void 0 : firstError.object)) {
|
|
62
|
+
target = firstError.object.constructor;
|
|
63
|
+
}
|
|
64
|
+
// 尝试从验证元数据中获取字段标签
|
|
65
|
+
const metadata = target ? (0, validation_metadata_helper_1.getValidationMetadata)(target, firstError.property) : undefined;
|
|
66
|
+
const fieldLabel = metadata
|
|
67
|
+
? (0, validation_metadata_helper_1.getFieldLabelForValidation)(metadata, lang)
|
|
68
|
+
: firstError.property;
|
|
69
|
+
// 使用字段标签作为 property 参数
|
|
55
70
|
error = i18n.translate(translationKey, {
|
|
56
|
-
args: Object.assign({ property:
|
|
71
|
+
args: Object.assign({ field: fieldLabel, property: fieldLabel, fieldName: fieldLabel, value: firstError.value, constraints: firstError.constraints }, args),
|
|
57
72
|
});
|
|
58
73
|
}
|
|
59
74
|
const parseJson = {
|
|
@@ -15,6 +15,7 @@ class ContextExtractor {
|
|
|
15
15
|
const requestId = providers_1.ContextProvider.getRequestId();
|
|
16
16
|
const authUser = providers_1.ContextProvider.getAuthUser();
|
|
17
17
|
const router = providers_1.ContextProvider.getRouter();
|
|
18
|
+
const source = providers_1.ContextProvider.getSource();
|
|
18
19
|
return {
|
|
19
20
|
requestId,
|
|
20
21
|
userId: authUser === null || authUser === void 0 ? void 0 : authUser.uid,
|
|
@@ -23,6 +24,7 @@ class ContextExtractor {
|
|
|
23
24
|
metadata: {
|
|
24
25
|
authUser,
|
|
25
26
|
router,
|
|
27
|
+
source, // 添加 source 字段
|
|
26
28
|
},
|
|
27
29
|
tags: [],
|
|
28
30
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP过滤元数据键名
|
|
3
|
+
* 用于在路由元数据中存储IP过滤配置
|
|
4
|
+
*/
|
|
5
|
+
export declare const IP_FILTER_KEY = "ip_filter";
|
|
6
|
+
/**
|
|
7
|
+
* IP过滤选项依赖注入键名
|
|
8
|
+
*/
|
|
9
|
+
export declare const IP_FILTER_OPTIONS = "IP_FILTER_OPTIONS";
|
|
10
|
+
/**
|
|
11
|
+
* 默认错误消息
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_ERROR_MESSAGE = "Access denied: Your IP address is not authorized";
|
|
14
|
+
/**
|
|
15
|
+
* 默认HTTP状态码
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_ERROR_STATUS_CODE = 403;
|
|
18
|
+
/**
|
|
19
|
+
* 默认优先级
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_PRIORITY = 50;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_PRIORITY = exports.DEFAULT_ERROR_STATUS_CODE = exports.DEFAULT_ERROR_MESSAGE = exports.IP_FILTER_OPTIONS = exports.IP_FILTER_KEY = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* IP过滤元数据键名
|
|
6
|
+
* 用于在路由元数据中存储IP过滤配置
|
|
7
|
+
*/
|
|
8
|
+
exports.IP_FILTER_KEY = 'ip_filter';
|
|
9
|
+
/**
|
|
10
|
+
* IP过滤选项依赖注入键名
|
|
11
|
+
*/
|
|
12
|
+
exports.IP_FILTER_OPTIONS = 'IP_FILTER_OPTIONS';
|
|
13
|
+
/**
|
|
14
|
+
* 默认错误消息
|
|
15
|
+
*/
|
|
16
|
+
exports.DEFAULT_ERROR_MESSAGE = 'Access denied: Your IP address is not authorized';
|
|
17
|
+
/**
|
|
18
|
+
* 默认HTTP状态码
|
|
19
|
+
*/
|
|
20
|
+
exports.DEFAULT_ERROR_STATUS_CODE = 403;
|
|
21
|
+
/**
|
|
22
|
+
* 默认优先级
|
|
23
|
+
*/
|
|
24
|
+
exports.DEFAULT_PRIORITY = 50;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ip-filter.decorator';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./ip-filter.decorator"), exports);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { IpFilterMetadata } from '../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* IP过滤装饰器
|
|
4
|
+
* 用于控制器或方法级别配置IP访问控制
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // 控制器级别
|
|
9
|
+
* @IpFilter({
|
|
10
|
+
* mode: 'whitelist',
|
|
11
|
+
* ipRanges: ['192.168.1.0/24', '10.0.0.0/8'],
|
|
12
|
+
* priority: 100
|
|
13
|
+
* })
|
|
14
|
+
* @Controller('admin')
|
|
15
|
+
* export class AdminController {}
|
|
16
|
+
*
|
|
17
|
+
* // 方法级别
|
|
18
|
+
* @Get()
|
|
19
|
+
* @IpFilter({
|
|
20
|
+
* mode: 'blacklist',
|
|
21
|
+
* ipRanges: ['203.0.113.0/24']
|
|
22
|
+
* })
|
|
23
|
+
* getData() {}
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @param options IP过滤配置选项
|
|
27
|
+
*/
|
|
28
|
+
export declare const IpFilter: (options: IpFilterMetadata) => import("@nestjs/common").CustomDecorator<string>;
|
|
29
|
+
/**
|
|
30
|
+
* 白名单装饰器(快捷方式)
|
|
31
|
+
* 仅允许指定的IP范围访问
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* @IpWhitelist(['192.168.1.0/24', '10.0.0.0/8'], 'Office network')
|
|
36
|
+
* @Get('admin')
|
|
37
|
+
* adminDashboard() {}
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @param ipRanges 允许的IP范围列表(CIDR格式)
|
|
41
|
+
* @param description 规则描述
|
|
42
|
+
*/
|
|
43
|
+
export declare const IpWhitelist: (ipRanges: string[], description?: string) => import("@nestjs/common").CustomDecorator<string>;
|
|
44
|
+
/**
|
|
45
|
+
* 黑名单装饰器(快捷方式)
|
|
46
|
+
* 禁止指定的IP范围访问
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* @IpBlacklist(['203.0.113.0/24'], 'Blocked malicious IPs')
|
|
51
|
+
* @Get('sensitive')
|
|
52
|
+
* sensitiveData() {}
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @param ipRanges 禁止的IP范围列表(CIDR格式)
|
|
56
|
+
* @param description 规则描述
|
|
57
|
+
*/
|
|
58
|
+
export declare const IpBlacklist: (ipRanges: string[], description?: string) => import("@nestjs/common").CustomDecorator<string>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IpBlacklist = exports.IpWhitelist = exports.IpFilter = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const constants_1 = require("../constants");
|
|
6
|
+
/**
|
|
7
|
+
* IP过滤装饰器
|
|
8
|
+
* 用于控制器或方法级别配置IP访问控制
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // 控制器级别
|
|
13
|
+
* @IpFilter({
|
|
14
|
+
* mode: 'whitelist',
|
|
15
|
+
* ipRanges: ['192.168.1.0/24', '10.0.0.0/8'],
|
|
16
|
+
* priority: 100
|
|
17
|
+
* })
|
|
18
|
+
* @Controller('admin')
|
|
19
|
+
* export class AdminController {}
|
|
20
|
+
*
|
|
21
|
+
* // 方法级别
|
|
22
|
+
* @Get()
|
|
23
|
+
* @IpFilter({
|
|
24
|
+
* mode: 'blacklist',
|
|
25
|
+
* ipRanges: ['203.0.113.0/24']
|
|
26
|
+
* })
|
|
27
|
+
* getData() {}
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @param options IP过滤配置选项
|
|
31
|
+
*/
|
|
32
|
+
const IpFilter = (options) => (0, common_1.SetMetadata)(constants_1.IP_FILTER_KEY, options);
|
|
33
|
+
exports.IpFilter = IpFilter;
|
|
34
|
+
/**
|
|
35
|
+
* 白名单装饰器(快捷方式)
|
|
36
|
+
* 仅允许指定的IP范围访问
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* @IpWhitelist(['192.168.1.0/24', '10.0.0.0/8'], 'Office network')
|
|
41
|
+
* @Get('admin')
|
|
42
|
+
* adminDashboard() {}
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @param ipRanges 允许的IP范围列表(CIDR格式)
|
|
46
|
+
* @param description 规则描述
|
|
47
|
+
*/
|
|
48
|
+
const IpWhitelist = (ipRanges, description) => {
|
|
49
|
+
return (0, exports.IpFilter)({
|
|
50
|
+
mode: 'whitelist',
|
|
51
|
+
ipRanges,
|
|
52
|
+
priority: 100,
|
|
53
|
+
description,
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
exports.IpWhitelist = IpWhitelist;
|
|
57
|
+
/**
|
|
58
|
+
* 黑名单装饰器(快捷方式)
|
|
59
|
+
* 禁止指定的IP范围访问
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* @IpBlacklist(['203.0.113.0/24'], 'Blocked malicious IPs')
|
|
64
|
+
* @Get('sensitive')
|
|
65
|
+
* sensitiveData() {}
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @param ipRanges 禁止的IP范围列表(CIDR格式)
|
|
69
|
+
* @param description 规则描述
|
|
70
|
+
*/
|
|
71
|
+
const IpBlacklist = (ipRanges, description) => {
|
|
72
|
+
return (0, exports.IpFilter)({
|
|
73
|
+
mode: 'blacklist',
|
|
74
|
+
ipRanges,
|
|
75
|
+
priority: 100,
|
|
76
|
+
description,
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
exports.IpBlacklist = IpBlacklist;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ip-filter.guard';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./ip-filter.guard"), exports);
|