@nest-util/nest-crud 1.2.2 → 2.0.1
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/dist/lib/controllers/nest-crud.controller.d.ts.map +1 -1
- package/dist/lib/controllers/nest-crud.controller.js +29 -10
- package/dist/lib/dtos/request-modification.dto.d.ts.map +1 -1
- package/dist/lib/dtos/request-modification.dto.js +1 -1
- package/dist/lib/entities/approval-status.entity.d.ts +4 -4
- package/dist/lib/entities/approval-status.entity.d.ts.map +1 -1
- package/dist/lib/entities/approval-status.entity.js +4 -4
- package/dist/lib/helpers/exception-filter.helper.d.ts.map +1 -1
- package/dist/lib/helpers/exception-filter.helper.js +3 -7
- package/dist/lib/interceptors/response.interceptor.d.ts +5 -1
- package/dist/lib/interceptors/response.interceptor.d.ts.map +1 -1
- package/dist/lib/interceptors/response.interceptor.js +21 -5
- package/dist/lib/interfaces/approval-pipeline.interface.d.ts +27 -10
- package/dist/lib/interfaces/approval-pipeline.interface.d.ts.map +1 -1
- package/dist/lib/interfaces/approval-pipeline.interface.js +5 -4
- package/dist/lib/interfaces/crud.interface.d.ts +2 -1
- package/dist/lib/interfaces/crud.interface.d.ts.map +1 -1
- package/dist/lib/services/nest-crud.service.d.ts +14 -0
- package/dist/lib/services/nest-crud.service.d.ts.map +1 -1
- package/dist/lib/services/nest-crud.service.js +113 -32
- package/package.json +4 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nest-crud.controller.d.ts","sourceRoot":"","sources":["../../../src/lib/controllers/nest-crud.controller.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"nest-crud.controller.d.ts","sourceRoot":"","sources":["../../../src/lib/controllers/nest-crud.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ exports.AUTH_PERMISSIONS_METADATA_KEY = void 0;
|
|
|
4
4
|
exports.CreateNestedCrudController = CreateNestedCrudController;
|
|
5
5
|
const tslib_1 = require("tslib");
|
|
6
6
|
const common_1 = require("@nestjs/common");
|
|
7
|
+
const nest_error_1 = require("@nest-util/nest-error");
|
|
7
8
|
const swagger_1 = require("@nestjs/swagger");
|
|
8
9
|
const response_message_decorator_1 = require("../decorators/response-message.decorator");
|
|
9
10
|
const audit_log_decorator_1 = require("../decorators/audit-log.decorator");
|
|
@@ -49,7 +50,7 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
|
|
|
49
50
|
}
|
|
50
51
|
ensureEndpointEnabled(endpoint) {
|
|
51
52
|
if (this.service.disabledEndpoints?.includes(endpoint)) {
|
|
52
|
-
throw
|
|
53
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
findAll(query) {
|
|
@@ -57,7 +58,7 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
|
|
|
57
58
|
// Cursor-based pagination: dispatch to cursor endpoint
|
|
58
59
|
if (query.cursor !== undefined) {
|
|
59
60
|
if (!this.service.findAllWithCursor) {
|
|
60
|
-
throw
|
|
61
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
61
62
|
}
|
|
62
63
|
return this.service.findAllWithCursor(query);
|
|
63
64
|
}
|
|
@@ -66,7 +67,7 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
|
|
|
66
67
|
findMine(user, query) {
|
|
67
68
|
this.ensureEndpointEnabled('findMine');
|
|
68
69
|
if (!this.service.findMine) {
|
|
69
|
-
throw
|
|
70
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
70
71
|
}
|
|
71
72
|
return this.service.findMine(user.id, query);
|
|
72
73
|
}
|
|
@@ -81,45 +82,52 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
|
|
|
81
82
|
changeStatus(id, dto, user) {
|
|
82
83
|
this.ensureEndpointEnabled('changeStatus');
|
|
83
84
|
if (!this.service.changeStatus) {
|
|
84
|
-
throw
|
|
85
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
85
86
|
}
|
|
86
87
|
return this.service.changeStatus(id, dto.status, user);
|
|
87
88
|
}
|
|
88
89
|
getApproval(id, user) {
|
|
89
90
|
this.ensureEndpointEnabled('getApproval');
|
|
90
91
|
if (!this.service.getApproval) {
|
|
91
|
-
throw
|
|
92
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
92
93
|
}
|
|
93
94
|
return this.service.getApproval(id, user);
|
|
94
95
|
}
|
|
95
96
|
approveApproval(id, user) {
|
|
96
97
|
this.ensureEndpointEnabled('approveApproval');
|
|
97
98
|
if (!this.service.approveApproval) {
|
|
98
|
-
throw
|
|
99
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
99
100
|
}
|
|
100
101
|
return this.service.approveApproval(id, user);
|
|
101
102
|
}
|
|
102
103
|
rejectApproval(id, user) {
|
|
103
104
|
this.ensureEndpointEnabled('rejectApproval');
|
|
104
105
|
if (!this.service.rejectApproval) {
|
|
105
|
-
throw
|
|
106
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
106
107
|
}
|
|
107
108
|
return this.service.rejectApproval(id, user);
|
|
108
109
|
}
|
|
109
110
|
requestModification(id, dto, user) {
|
|
110
111
|
this.ensureEndpointEnabled('requestModification');
|
|
111
112
|
if (!this.service.requestModification) {
|
|
112
|
-
throw
|
|
113
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
113
114
|
}
|
|
114
115
|
return this.service.requestModification(id, dto, user);
|
|
115
116
|
}
|
|
116
117
|
resubmitApproval(id, user) {
|
|
117
118
|
this.ensureEndpointEnabled('resubmitApproval');
|
|
118
119
|
if (!this.service.resubmitApproval) {
|
|
119
|
-
throw
|
|
120
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
120
121
|
}
|
|
121
122
|
return this.service.resubmitApproval(id, user);
|
|
122
123
|
}
|
|
124
|
+
submitApproval(id, user) {
|
|
125
|
+
this.ensureEndpointEnabled('submitApproval');
|
|
126
|
+
if (!this.service.submitApproval) {
|
|
127
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
128
|
+
}
|
|
129
|
+
return this.service.submitApproval(id, user);
|
|
130
|
+
}
|
|
123
131
|
remove(id, user) {
|
|
124
132
|
this.ensureEndpointEnabled('remove');
|
|
125
133
|
return this.service.remove(id, user);
|
|
@@ -127,7 +135,7 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
|
|
|
127
135
|
findAuditLogs(query) {
|
|
128
136
|
this.ensureEndpointEnabled('findAuditLogs');
|
|
129
137
|
if (!this.service.findAuditLogs) {
|
|
130
|
-
throw
|
|
138
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
131
139
|
}
|
|
132
140
|
return this.service.findAuditLogs(query);
|
|
133
141
|
}
|
|
@@ -279,6 +287,17 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
|
|
|
279
287
|
tslib_1.__metadata("design:paramtypes", [Number, Object]),
|
|
280
288
|
tslib_1.__metadata("design:returntype", void 0)
|
|
281
289
|
], BaseController.prototype, "resubmitApproval", null);
|
|
290
|
+
tslib_1.__decorate([
|
|
291
|
+
(0, common_1.Post)(':id/approval/submit'),
|
|
292
|
+
(0, response_message_decorator_1.Message)('submitted'),
|
|
293
|
+
(0, audit_log_decorator_1.Audit)({ action: 'SUBMIT' }),
|
|
294
|
+
(0, swagger_1.ApiResponse)({ description: 'The updated approval status' }),
|
|
295
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
|
|
296
|
+
tslib_1.__param(1, (0, nest_auth_1.CurrentUser)()),
|
|
297
|
+
tslib_1.__metadata("design:type", Function),
|
|
298
|
+
tslib_1.__metadata("design:paramtypes", [Number, Object]),
|
|
299
|
+
tslib_1.__metadata("design:returntype", void 0)
|
|
300
|
+
], BaseController.prototype, "submitApproval", null);
|
|
282
301
|
tslib_1.__decorate([
|
|
283
302
|
(0, common_1.Delete)(':id'),
|
|
284
303
|
(0, response_message_decorator_1.Message)('deleted'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-modification.dto.d.ts","sourceRoot":"","sources":["../../../src/lib/dtos/request-modification.dto.ts"],"names":[],"mappings":"AAWA,qBAAa,mBAAmB;IAG9B,KAAK,EAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"request-modification.dto.d.ts","sourceRoot":"","sources":["../../../src/lib/dtos/request-modification.dto.ts"],"names":[],"mappings":"AAWA,qBAAa,mBAAmB;IAG9B,KAAK,EAAG,MAAM,CAAC;IAQf,YAAY,CAAC,EAAE,OAAO,CAAC;IAIvB,WAAW,EAAG,OAAO,CAAC;IAKtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,sBAAsB;IASjC,aAAa,EAAG,mBAAmB,EAAE,CAAC;IAQtC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
|
|
@@ -15,7 +15,7 @@ tslib_1.__decorate([
|
|
|
15
15
|
], ModificationItemDto.prototype, "field", void 0);
|
|
16
16
|
tslib_1.__decorate([
|
|
17
17
|
(0, swagger_1.ApiProperty)({
|
|
18
|
-
description: 'Value currently stored on the record (
|
|
18
|
+
description: 'Value currently stored on the record. Auto-filled from the live entity when omitted (supports relation objects).',
|
|
19
19
|
required: false,
|
|
20
20
|
}),
|
|
21
21
|
(0, class_validator_1.IsOptional)(),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ModificationItem } from '../interfaces/approval-pipeline.interface';
|
|
2
2
|
/**
|
|
3
|
-
* Tracks the approval lifecycle of a created record. One row per
|
|
4
|
-
*
|
|
5
|
-
* name (`entity`) and its primary key (`entityId`).
|
|
3
|
+
* Tracks the approval lifecycle of a created record. One row per
|
|
4
|
+
* draft/submitted/approved entity, polymorphically referencing the target row
|
|
5
|
+
* via the table name (`entity`) and its primary key (`entityId`).
|
|
6
6
|
*/
|
|
7
7
|
export declare class ApprovalStatusEntity {
|
|
8
8
|
id: number;
|
|
@@ -10,7 +10,7 @@ export declare class ApprovalStatusEntity {
|
|
|
10
10
|
entity: string;
|
|
11
11
|
/** Stringified primary key of the target row (works for int and uuid). */
|
|
12
12
|
entityId: string;
|
|
13
|
-
/**
|
|
13
|
+
/** draft | submitted | approved | rejected | modification_requested | resubmitted */
|
|
14
14
|
status: string;
|
|
15
15
|
/** User who submitted the record for approval. */
|
|
16
16
|
requestedBy?: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-status.entity.d.ts","sourceRoot":"","sources":["../../../src/lib/entities/approval-status.entity.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAElF;;;;GAIG;AACH,qBACa,oBAAoB;IAE/B,EAAE,EAAG,MAAM,CAAC;IAEZ,uCAAuC;IAGvC,MAAM,EAAG,MAAM,CAAC;IAEhB,0EAA0E;IAG1E,QAAQ,EAAG,MAAM,CAAC;IAElB,
|
|
1
|
+
{"version":3,"file":"approval-status.entity.d.ts","sourceRoot":"","sources":["../../../src/lib/entities/approval-status.entity.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAElF;;;;GAIG;AACH,qBACa,oBAAoB;IAE/B,EAAE,EAAG,MAAM,CAAC;IAEZ,uCAAuC;IAGvC,MAAM,EAAG,MAAM,CAAC;IAEhB,0EAA0E;IAG1E,QAAQ,EAAG,MAAM,CAAC;IAElB,qFAAqF;IAGrF,MAAM,EAAG,MAAM,CAAC;IAEhB,kDAAkD;IAGlD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG5B,WAAW,EAAG,IAAI,CAAC;IAEnB,2EAA2E;IAE3E,oBAAoB,CAAC,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC;IAEjD,gDAAgD;IAEhD,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG1B,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAExB,2DAA2D;IAE3D,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG9B,aAAa,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAG5B,SAAS,EAAG,IAAI,CAAC;IAGjB,SAAS,EAAG,IAAI,CAAC;CAClB"}
|
|
@@ -4,9 +4,9 @@ exports.ApprovalStatusEntity = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const typeorm_1 = require("typeorm");
|
|
6
6
|
/**
|
|
7
|
-
* Tracks the approval lifecycle of a created record. One row per
|
|
8
|
-
*
|
|
9
|
-
* name (`entity`) and its primary key (`entityId`).
|
|
7
|
+
* Tracks the approval lifecycle of a created record. One row per
|
|
8
|
+
* draft/submitted/approved entity, polymorphically referencing the target row
|
|
9
|
+
* via the table name (`entity`) and its primary key (`entityId`).
|
|
10
10
|
*/
|
|
11
11
|
let ApprovalStatusEntity = class ApprovalStatusEntity {
|
|
12
12
|
};
|
|
@@ -27,7 +27,7 @@ tslib_1.__decorate([
|
|
|
27
27
|
], ApprovalStatusEntity.prototype, "entityId", void 0);
|
|
28
28
|
tslib_1.__decorate([
|
|
29
29
|
(0, typeorm_1.Index)(),
|
|
30
|
-
(0, typeorm_1.Column)({ default: '
|
|
30
|
+
(0, typeorm_1.Column)({ default: 'draft' }),
|
|
31
31
|
tslib_1.__metadata("design:type", String)
|
|
32
32
|
], ApprovalStatusEntity.prototype, "status", void 0);
|
|
33
33
|
tslib_1.__decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exception-filter.helper.d.ts","sourceRoot":"","sources":["../../../src/lib/helpers/exception-filter.helper.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAEb,eAAe,EAEhB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAU3C,qBACa,sBAAuB,YAAW,eAAe;IAC5D,KAAK,CAAC,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"exception-filter.helper.d.ts","sourceRoot":"","sources":["../../../src/lib/helpers/exception-filter.helper.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAEb,eAAe,EAEhB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAU3C,qBACa,sBAAuB,YAAW,eAAe;IAC5D,KAAK,CAAC,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;CAwB9D"}
|
|
@@ -14,13 +14,9 @@ let TypeOrmExceptionFilter = class TypeOrmExceptionFilter {
|
|
|
14
14
|
let message = 'Internal server error';
|
|
15
15
|
if (isUniqueViolation) {
|
|
16
16
|
status = common_1.HttpStatus.UNPROCESSABLE_ENTITY;
|
|
17
|
-
message
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.replace('Key ', '')
|
|
21
|
-
.replace(/[()]/g, '')
|
|
22
|
-
.trim();
|
|
23
|
-
}
|
|
17
|
+
// Generic message only — the raw driverError.detail (column / constraint /
|
|
18
|
+
// values) is intentionally NOT leaked to the client.
|
|
19
|
+
message = 'A record with this value already exists';
|
|
24
20
|
}
|
|
25
21
|
response.status(status).json({
|
|
26
22
|
status: 'error',
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
2
2
|
import { Observable } from 'rxjs';
|
|
3
3
|
import { Reflector } from '@nestjs/core';
|
|
4
|
+
import { I18nService, LangResolverService } from '@nest-util/nest-error';
|
|
4
5
|
export declare class ResponseInterceptor implements NestInterceptor {
|
|
5
6
|
private readonly reflector;
|
|
6
|
-
|
|
7
|
+
private readonly i18n?;
|
|
8
|
+
private readonly langResolver?;
|
|
9
|
+
constructor(reflector: Reflector, i18n?: I18nService | undefined, langResolver?: LangResolverService | undefined);
|
|
7
10
|
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
11
|
+
private localize;
|
|
8
12
|
}
|
|
9
13
|
//# sourceMappingURL=response.interceptor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.interceptor.d.ts","sourceRoot":"","sources":["../../../src/lib/interceptors/response.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,gBAAgB,EAChB,WAAW,
|
|
1
|
+
{"version":3,"file":"response.interceptor.d.ts","sourceRoot":"","sources":["../../../src/lib/interceptors/response.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,gBAAgB,EAChB,WAAW,EAEZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAElC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAOzC,OAAO,EACL,WAAW,EACX,mBAAmB,EAEpB,MAAM,uBAAuB,CAAC;AAE/B,qBACa,mBAAoB,YAAW,eAAe;IAEvD,OAAO,CAAC,QAAQ,CAAC,SAAS;IACd,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;gBAFzB,SAAS,EAAE,SAAS,EACR,IAAI,CAAC,EAAE,WAAW,YAAA,EAClB,YAAY,CAAC,EAAE,mBAAmB,YAAA;IAGjE,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC;IA8B5E,OAAO,CAAC,QAAQ;CAwBjB"}
|
|
@@ -6,9 +6,12 @@ const common_1 = require("@nestjs/common");
|
|
|
6
6
|
const operators_1 = require("rxjs/operators");
|
|
7
7
|
const core_1 = require("@nestjs/core");
|
|
8
8
|
const response_message_decorator_1 = require("../decorators/response-message.decorator");
|
|
9
|
+
const nest_error_1 = require("@nest-util/nest-error");
|
|
9
10
|
let ResponseInterceptor = class ResponseInterceptor {
|
|
10
|
-
constructor(reflector) {
|
|
11
|
+
constructor(reflector, i18n, langResolver) {
|
|
11
12
|
this.reflector = reflector;
|
|
13
|
+
this.i18n = i18n;
|
|
14
|
+
this.langResolver = langResolver;
|
|
12
15
|
}
|
|
13
16
|
intercept(context, next) {
|
|
14
17
|
const handler = context.getHandler();
|
|
@@ -20,9 +23,7 @@ let ResponseInterceptor = class ResponseInterceptor {
|
|
|
20
23
|
const name = isList
|
|
21
24
|
? entityConfig?.plural ?? 'Resources'
|
|
22
25
|
: entityConfig?.singular ?? 'Resource';
|
|
23
|
-
const finalMessage = action
|
|
24
|
-
? `${name} ${action} successfully`
|
|
25
|
-
: 'Request successful';
|
|
26
|
+
const finalMessage = this.localize(name, action, context);
|
|
26
27
|
return {
|
|
27
28
|
message: finalMessage,
|
|
28
29
|
data: data?.data ?? data ?? null,
|
|
@@ -31,9 +32,24 @@ let ResponseInterceptor = class ResponseInterceptor {
|
|
|
31
32
|
};
|
|
32
33
|
}));
|
|
33
34
|
}
|
|
35
|
+
localize(name, action, context) {
|
|
36
|
+
if (!this.i18n || !this.langResolver) {
|
|
37
|
+
return action ? `${name} ${action} successfully` : 'Request successful';
|
|
38
|
+
}
|
|
39
|
+
const request = context.switchToHttp().getRequest();
|
|
40
|
+
const lang = this.langResolver.resolve(request ?? { headers: {} });
|
|
41
|
+
const localizedAction = action
|
|
42
|
+
? this.i18n.translate(action, {}, lang)
|
|
43
|
+
: this.i18n.translate(nest_error_1.ErrorKey.MSG_REQUEST_SUCCESS, {}, lang);
|
|
44
|
+
return this.i18n.translate(nest_error_1.ErrorKey.SUCCESS_FORMAT, { entity: name, action: localizedAction }, lang);
|
|
45
|
+
}
|
|
34
46
|
};
|
|
35
47
|
exports.ResponseInterceptor = ResponseInterceptor;
|
|
36
48
|
exports.ResponseInterceptor = ResponseInterceptor = tslib_1.__decorate([
|
|
37
49
|
(0, common_1.Injectable)(),
|
|
38
|
-
tslib_1.
|
|
50
|
+
tslib_1.__param(1, (0, common_1.Optional)()),
|
|
51
|
+
tslib_1.__param(2, (0, common_1.Optional)()),
|
|
52
|
+
tslib_1.__metadata("design:paramtypes", [core_1.Reflector,
|
|
53
|
+
nest_error_1.I18nService,
|
|
54
|
+
nest_error_1.LangResolverService])
|
|
39
55
|
], ResponseInterceptor);
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lifecycle of an approval status row.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* │
|
|
6
|
-
* └─> modification_requested ──> resubmitted ──> approved / rejected
|
|
4
|
+
* draft ──> submitted ────────> approved
|
|
5
|
+
* │ │ └────────────> rejected
|
|
6
|
+
* │ └─> modification_requested ──> resubmitted ──> approved / rejected
|
|
7
7
|
* │ │
|
|
8
8
|
* └────────────────────────────────────┘
|
|
9
9
|
* (modifications may be requested again)
|
|
10
10
|
*/
|
|
11
11
|
export declare const APPROVAL_STATUS: {
|
|
12
|
-
readonly
|
|
12
|
+
readonly draft: "draft";
|
|
13
|
+
readonly submitted: "submitted";
|
|
13
14
|
readonly approved: "approved";
|
|
14
15
|
readonly rejected: "rejected";
|
|
15
16
|
readonly modificationRequested: "modification_requested";
|
|
@@ -17,13 +18,17 @@ export declare const APPROVAL_STATUS: {
|
|
|
17
18
|
};
|
|
18
19
|
export type ApprovalStatus = (typeof APPROVAL_STATUS)[keyof typeof APPROVAL_STATUS];
|
|
19
20
|
/**
|
|
20
|
-
* A single requested modification on a
|
|
21
|
+
* A single requested modification on a record under review. Each item names the
|
|
21
22
|
* field to change and the value the reviewer wants instead.
|
|
22
23
|
*/
|
|
23
24
|
export interface ModificationItem {
|
|
24
|
-
/** Entity field to modify, e.g. 'title'. */
|
|
25
|
+
/** Entity field to modify, e.g. 'title' or 'category' (a relation). */
|
|
25
26
|
field: string;
|
|
26
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Value currently stored on the record. Auto-captured from the live entity
|
|
29
|
+
* when omitted (supports relation objects; circular refs are sanitized to
|
|
30
|
+
* '[Circular]').
|
|
31
|
+
*/
|
|
27
32
|
currentValue?: unknown;
|
|
28
33
|
/** Value the reviewer wants instead. */
|
|
29
34
|
wantedValue: unknown;
|
|
@@ -54,22 +59,34 @@ export interface ApprovalHistoryView {
|
|
|
54
59
|
note?: string | null;
|
|
55
60
|
}
|
|
56
61
|
export interface ApprovalPipelinePermissions {
|
|
57
|
-
/** Required permission to approve (
|
|
62
|
+
/** Required permission to approve (submitted/resubmitted -> approved). */
|
|
58
63
|
approve?: string;
|
|
59
|
-
/** Required permission to reject (
|
|
64
|
+
/** Required permission to reject (submitted/resubmitted -> rejected). */
|
|
60
65
|
reject?: string;
|
|
61
66
|
/** Required permission to request modifications. */
|
|
62
67
|
requestModification?: string;
|
|
63
68
|
/** Required permission to resubmit after modifications. */
|
|
64
69
|
resubmit?: string;
|
|
70
|
+
/** Required permission to submit a draft for approval. */
|
|
71
|
+
submit?: string;
|
|
65
72
|
}
|
|
66
73
|
export interface ApprovalPipelineConfig {
|
|
67
74
|
/**
|
|
68
|
-
* When true, every created record also gets a
|
|
75
|
+
* When true, every created record also gets a draft approval status row
|
|
69
76
|
* (created in the same transaction). Defaults to true when the option is
|
|
70
77
|
* provided.
|
|
71
78
|
*/
|
|
72
79
|
enabled?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Initial status assigned to a record's approval row on create.
|
|
82
|
+
* - 'draft' (default): the record waits in draft until `submitApproval`
|
|
83
|
+
* is called; the `submit` permission applies and `requestedBy` is filled
|
|
84
|
+
* on submit.
|
|
85
|
+
* - 'submitted': the record is created already in the reviewable state
|
|
86
|
+
* (the creating user becomes the requester), so no explicit submit step
|
|
87
|
+
* is needed.
|
|
88
|
+
*/
|
|
89
|
+
initialStatus?: 'draft' | 'submitted';
|
|
73
90
|
/**
|
|
74
91
|
* Optional permission strings required to perform each action. When unset,
|
|
75
92
|
* the action is allowed for any authenticated (or anonymous) caller.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-pipeline.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/interfaces/approval-pipeline.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"approval-pipeline.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/interfaces/approval-pipeline.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe;;;;;;;CAOlB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAEpF;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wCAAwC;IACxC,WAAW,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,IAAI,CAAC;IAClB,oBAAoB,CAAC,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,IAAI,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;IAEtC;;;OAGG;IACH,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAE1C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
|
|
@@ -4,15 +4,16 @@ exports.APPROVAL_STATUS = void 0;
|
|
|
4
4
|
/**
|
|
5
5
|
* Lifecycle of an approval status row.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* │
|
|
9
|
-
* └─> modification_requested ──> resubmitted ──> approved / rejected
|
|
7
|
+
* draft ──> submitted ────────> approved
|
|
8
|
+
* │ │ └────────────> rejected
|
|
9
|
+
* │ └─> modification_requested ──> resubmitted ──> approved / rejected
|
|
10
10
|
* │ │
|
|
11
11
|
* └────────────────────────────────────┘
|
|
12
12
|
* (modifications may be requested again)
|
|
13
13
|
*/
|
|
14
14
|
exports.APPROVAL_STATUS = {
|
|
15
|
-
|
|
15
|
+
draft: 'draft',
|
|
16
|
+
submitted: 'submitted',
|
|
16
17
|
approved: 'approved',
|
|
17
18
|
rejected: 'rejected',
|
|
18
19
|
modificationRequested: 'modification_requested',
|
|
@@ -4,7 +4,7 @@ import { CursorPaginationDto } from '../dtos/cursor-pagination.dto';
|
|
|
4
4
|
import { OwnershipUser } from './find-mine.interface';
|
|
5
5
|
import { StatusValue } from './status-pipeline.interface';
|
|
6
6
|
import { ApprovalHistoryView, ApprovalStatusView, RequestModificationPayload } from './approval-pipeline.interface';
|
|
7
|
-
export type CrudEndpoint = 'findAll' | 'findOne' | 'create' | 'update' | 'changeStatus' | 'remove' | 'findAuditLogs' | 'findMine' | 'getApproval' | 'approveApproval' | 'rejectApproval' | 'requestModification' | 'resubmitApproval';
|
|
7
|
+
export type CrudEndpoint = 'findAll' | 'findOne' | 'create' | 'update' | 'changeStatus' | 'remove' | 'findAuditLogs' | 'findMine' | 'getApproval' | 'approveApproval' | 'rejectApproval' | 'requestModification' | 'resubmitApproval' | 'submitApproval';
|
|
8
8
|
export interface AuditLogQuery {
|
|
9
9
|
user_id?: string;
|
|
10
10
|
start_date?: string;
|
|
@@ -46,5 +46,6 @@ export interface CrudInterface<CreateDto, UpdateDto, ResponseDto> {
|
|
|
46
46
|
rejectApproval?(id: number, user?: OwnershipUser): Promise<ApprovalStatusView>;
|
|
47
47
|
requestModification?(id: number, dto: RequestModificationPayload, user?: OwnershipUser): Promise<ApprovalStatusView>;
|
|
48
48
|
resubmitApproval?(id: number, user?: OwnershipUser): Promise<ApprovalStatusView>;
|
|
49
|
+
submitApproval?(id: number, user?: OwnershipUser): Promise<ApprovalStatusView>;
|
|
49
50
|
}
|
|
50
51
|
//# sourceMappingURL=crud.interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/interfaces/crud.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,0BAA0B,EAC3B,MAAM,+BAA+B,CAAC;AAEvC,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,cAAc,GACd,QAAQ,GACR,eAAe,GACf,UAAU,GACV,aAAa,GACb,iBAAiB,GACjB,gBAAgB,GAChB,qBAAqB,GACrB,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"crud.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/interfaces/crud.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,0BAA0B,EAC3B,MAAM,+BAA+B,CAAC;AAEvC,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,cAAc,GACd,QAAQ,GACR,eAAe,GACf,UAAU,GACV,aAAa,GACb,iBAAiB,GACjB,gBAAgB,GAChB,qBAAqB,GACrB,kBAAkB,GAClB,gBAAgB,CAAC;AAErB,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB,CAAC,CAAC;IACvC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,aAAa,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW;IAC9D,iBAAiB,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAE5C,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC;QACjD,IAAI,EAAE,WAAW,EAAE,CAAC;QACpB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC,CAAC;IAEH,iBAAiB,CAAC,CAChB,KAAK,EAAE,mBAAmB,GAAG,SAAS,GACrC,OAAO,CAAC,sBAAsB,CAAC,WAAW,CAAC,CAAC,CAAC;IAEhD,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAEhE,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAEnE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAE/E,YAAY,CAAC,CACX,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,WAAW,EACnB,IAAI,CAAC,EAAE,aAAa,GACnB,OAAO,CAAC,WAAW,CAAC,CAAC;IAExB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE3D,aAAa,CAAC,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvD,QAAQ,CAAC,CACP,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,KAAK,EAAE,aAAa,GAAG,SAAS,GAC/B,OAAO,CAAC;QAAE,IAAI,EAAE,WAAW,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAEpD,WAAW,CAAC,CACV,EAAE,EAAE,MAAM,EACV,IAAI,CAAC,EAAE,aAAa,GACnB,OAAO,CAAC;QAAE,QAAQ,EAAE,kBAAkB,CAAC;QAAC,OAAO,EAAE,mBAAmB,EAAE,CAAA;KAAE,CAAC,CAAC;IAE7E,eAAe,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAEhF,cAAc,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAE/E,mBAAmB,CAAC,CAClB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,0BAA0B,EAC/B,IAAI,CAAC,EAAE,aAAa,GACnB,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAE/B,gBAAgB,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAEjF,cAAc,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAChF"}
|
|
@@ -110,12 +110,26 @@ export declare class NestCrudService<Entity extends ObjectLiteral, CreateDto = P
|
|
|
110
110
|
rejectApproval(id: number, user?: OwnershipUser): Promise<ApprovalStatusView>;
|
|
111
111
|
requestModification(id: number, payload: RequestModificationPayload, user?: OwnershipUser): Promise<ApprovalStatusView>;
|
|
112
112
|
resubmitApproval(id: number, user?: OwnershipUser): Promise<ApprovalStatusView>;
|
|
113
|
+
submitApproval(id: number, user?: OwnershipUser): Promise<ApprovalStatusView>;
|
|
113
114
|
private isApprovalPipelineConfigured;
|
|
114
115
|
private hasApprovalVisibilityFilter;
|
|
115
116
|
private getApprovalRepository;
|
|
116
117
|
private getHistoryRepository;
|
|
117
118
|
private createWithApproval;
|
|
118
119
|
private resolveEntityForApproval;
|
|
120
|
+
/**
|
|
121
|
+
* Loads the target entity with its configured `include` relations so that
|
|
122
|
+
* relation fields can be read as objects when capturing modification values.
|
|
123
|
+
*/
|
|
124
|
+
private getEntityWithRelations;
|
|
125
|
+
/** Reads a (possibly nested, dot-path) field from an entity. */
|
|
126
|
+
private readCurrentValue;
|
|
127
|
+
/**
|
|
128
|
+
* Deep-clones a value so it can be safely stored in a jsonb column. Object
|
|
129
|
+
* graphs with circular references (common for loaded relations) are
|
|
130
|
+
* sanitized: a circular branch is replaced with the string '[Circular]'.
|
|
131
|
+
*/
|
|
132
|
+
private sanitizeForJsonb;
|
|
119
133
|
private loadApprovalStatus;
|
|
120
134
|
private transitionApproval;
|
|
121
135
|
private assertTransitionAllowed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nest-crud.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/nest-crud.service.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"nest-crud.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/nest-crud.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.NestCrudService = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const common_1 = require("@nestjs/common");
|
|
6
|
+
const nest_error_1 = require("@nest-util/nest-error");
|
|
6
7
|
const audit_log_entity_1 = require("../entities/audit-log.entity");
|
|
7
8
|
const filter_helper_1 = require("../helpers/filter.helper");
|
|
8
9
|
const pagination_helper_1 = require("../helpers/pagination.helper");
|
|
@@ -91,7 +92,7 @@ let NestCrudService = class NestCrudService {
|
|
|
91
92
|
id,
|
|
92
93
|
});
|
|
93
94
|
if (!entity) {
|
|
94
|
-
throw
|
|
95
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RELATION_NOT_FOUND, { property: String(relation.property) });
|
|
95
96
|
}
|
|
96
97
|
payload[String(relation.property)] = entity;
|
|
97
98
|
delete payload[idField];
|
|
@@ -238,7 +239,7 @@ let NestCrudService = class NestCrudService {
|
|
|
238
239
|
async findOwnedEntity(id, user) {
|
|
239
240
|
const userId = user?.id;
|
|
240
241
|
if (userId === undefined || userId === null) {
|
|
241
|
-
throw
|
|
242
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.FORBIDDEN, nest_error_1.ErrorKey.AUTH_UNAUTHORIZED);
|
|
242
243
|
}
|
|
243
244
|
const qb = this.repo.createQueryBuilder('e');
|
|
244
245
|
this.applyOwnershipCondition(qb, userId);
|
|
@@ -264,9 +265,7 @@ let NestCrudService = class NestCrudService {
|
|
|
264
265
|
}
|
|
265
266
|
if (this.statusCreateAllow.length > 0 &&
|
|
266
267
|
!this.statusCreateAllow.includes(requested)) {
|
|
267
|
-
throw
|
|
268
|
-
.map(String)
|
|
269
|
-
.join(', ') || 'none'}`);
|
|
268
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.BAD_REQUEST, nest_error_1.ErrorKey.CRUD_INVALID_STATUS, { requested: String(requested) });
|
|
270
269
|
}
|
|
271
270
|
}
|
|
272
271
|
validateStatusTransition(existing, payload, user) {
|
|
@@ -285,13 +284,13 @@ let NestCrudService = class NestCrudService {
|
|
|
285
284
|
}
|
|
286
285
|
const entry = this.statusTransitions.get(current);
|
|
287
286
|
if (!entry) {
|
|
288
|
-
throw
|
|
287
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.BAD_REQUEST, nest_error_1.ErrorKey.CRUD_STATUS_TRANSITION_INVALID, { from: String(current), to: String(requestedValue) });
|
|
289
288
|
}
|
|
290
289
|
if (!entry.to.has(requestedValue)) {
|
|
291
|
-
throw
|
|
290
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.BAD_REQUEST, nest_error_1.ErrorKey.CRUD_STATUS_TRANSITION_INVALID, { from: String(current), to: String(requestedValue) });
|
|
292
291
|
}
|
|
293
292
|
if (entry.permission && !this.canBypassOwnershipForTransition(user, entry.permission)) {
|
|
294
|
-
throw
|
|
293
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.FORBIDDEN, nest_error_1.ErrorKey.CRUD_STATUS_TRANSITION_FORBIDDEN, { permission: entry.permission });
|
|
295
294
|
}
|
|
296
295
|
return {
|
|
297
296
|
from: current,
|
|
@@ -336,7 +335,7 @@ let NestCrudService = class NestCrudService {
|
|
|
336
335
|
}
|
|
337
336
|
async findMine(userId, query) {
|
|
338
337
|
if (!this.userOwnershipField && !this.findMineQuery) {
|
|
339
|
-
throw
|
|
338
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.BAD_REQUEST, nest_error_1.ErrorKey.CRUD_FIND_MINE_NOT_CONFIGURED);
|
|
340
339
|
}
|
|
341
340
|
const qb = this.repo.createQueryBuilder('e');
|
|
342
341
|
this.applyOwnershipCondition(qb, userId);
|
|
@@ -423,7 +422,7 @@ let NestCrudService = class NestCrudService {
|
|
|
423
422
|
});
|
|
424
423
|
}
|
|
425
424
|
if (!entity) {
|
|
426
|
-
throw
|
|
425
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
427
426
|
}
|
|
428
427
|
const result = this.toResponseDto
|
|
429
428
|
? this.toResponseDto(entity)
|
|
@@ -436,7 +435,7 @@ let NestCrudService = class NestCrudService {
|
|
|
436
435
|
this.userOwnershipField &&
|
|
437
436
|
!this.canBypassOwnership(user)) {
|
|
438
437
|
if (!user || user.id === undefined || user.id === null) {
|
|
439
|
-
throw
|
|
438
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.FORBIDDEN, nest_error_1.ErrorKey.AUTH_UNAUTHORIZED);
|
|
440
439
|
}
|
|
441
440
|
const field = String(this.userOwnershipField);
|
|
442
441
|
const matchingRelation = this.relations.find((r) => String(r.property) === field);
|
|
@@ -448,7 +447,7 @@ let NestCrudService = class NestCrudService {
|
|
|
448
447
|
if (payloadValue !== undefined && payloadValue !== null) {
|
|
449
448
|
// Value present — must match the authenticated user
|
|
450
449
|
if (String(payloadValue) !== String(user.id)) {
|
|
451
|
-
throw
|
|
450
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
452
451
|
}
|
|
453
452
|
}
|
|
454
453
|
else {
|
|
@@ -477,18 +476,18 @@ let NestCrudService = class NestCrudService {
|
|
|
477
476
|
}
|
|
478
477
|
async changeStatus(id, status, user) {
|
|
479
478
|
if (!this.isStatusPipelineConfigured()) {
|
|
480
|
-
throw
|
|
479
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_STATUS_NOT_CONFIGURED);
|
|
481
480
|
}
|
|
482
481
|
const payload = { [String(this.statusField)]: status };
|
|
483
482
|
return this.applyUpdateCore(id, payload, user);
|
|
484
483
|
}
|
|
485
484
|
async getApproval(id, user) {
|
|
486
485
|
if (!this.isApprovalPipelineConfigured()) {
|
|
487
|
-
throw
|
|
486
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_APPROVAL_NOT_CONFIGURED);
|
|
488
487
|
}
|
|
489
488
|
const approval = await this.loadApprovalStatus(id, user);
|
|
490
489
|
if (!approval) {
|
|
491
|
-
throw
|
|
490
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
492
491
|
}
|
|
493
492
|
const history = await this.getHistoryRepository().find({
|
|
494
493
|
where: { approvalStatusId: approval.id },
|
|
@@ -511,6 +510,9 @@ let NestCrudService = class NestCrudService {
|
|
|
511
510
|
async resubmitApproval(id, user) {
|
|
512
511
|
return this.transitionApproval(id, 'resubmit', user);
|
|
513
512
|
}
|
|
513
|
+
async submitApproval(id, user) {
|
|
514
|
+
return this.transitionApproval(id, 'submit', user);
|
|
515
|
+
}
|
|
514
516
|
isApprovalPipelineConfigured() {
|
|
515
517
|
return Boolean(this.approvalPipeline && this.approvalPipeline.enabled !== false);
|
|
516
518
|
}
|
|
@@ -532,11 +534,14 @@ let NestCrudService = class NestCrudService {
|
|
|
532
534
|
const entityRepo = manager.getRepository(this.repo.target);
|
|
533
535
|
const saved = await entityRepo.save(resolved);
|
|
534
536
|
const approvalRepo = manager.getRepository(approval_status_entity_1.ApprovalStatusEntity);
|
|
537
|
+
const initialStatus = this.approvalPipeline?.initialStatus ?? approval_pipeline_interface_1.APPROVAL_STATUS.draft;
|
|
535
538
|
const approval = approvalRepo.create({
|
|
536
539
|
entity: this.repo.metadata.tableName,
|
|
537
540
|
entityId: String(saved.id),
|
|
538
|
-
status:
|
|
539
|
-
|
|
541
|
+
status: initialStatus,
|
|
542
|
+
...(initialStatus === approval_pipeline_interface_1.APPROVAL_STATUS.submitted
|
|
543
|
+
? { requestedBy: user?.id != null ? String(user.id) : null }
|
|
544
|
+
: {}),
|
|
540
545
|
});
|
|
541
546
|
await approvalRepo.save(approval);
|
|
542
547
|
return saved;
|
|
@@ -549,6 +554,63 @@ let NestCrudService = class NestCrudService {
|
|
|
549
554
|
}
|
|
550
555
|
return this.repo.findOneBy({ id });
|
|
551
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Loads the target entity with its configured `include` relations so that
|
|
559
|
+
* relation fields can be read as objects when capturing modification values.
|
|
560
|
+
*/
|
|
561
|
+
async getEntityWithRelations(id, user) {
|
|
562
|
+
if (this.enforceOwnershipFor(user)) {
|
|
563
|
+
return this.findOwnedEntity(id, user);
|
|
564
|
+
}
|
|
565
|
+
const relations = this.include.length
|
|
566
|
+
? this.buildRelationsObject(this.include)
|
|
567
|
+
: undefined;
|
|
568
|
+
return this.repo.findOne({
|
|
569
|
+
where: { id },
|
|
570
|
+
relations: relations,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
/** Reads a (possibly nested, dot-path) field from an entity. */
|
|
574
|
+
readCurrentValue(entity, field) {
|
|
575
|
+
if (!entity || !field) {
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
const parts = field.split('.');
|
|
579
|
+
let current = entity;
|
|
580
|
+
for (const part of parts) {
|
|
581
|
+
if (current == null) {
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
current = current[part];
|
|
585
|
+
}
|
|
586
|
+
return current;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Deep-clones a value so it can be safely stored in a jsonb column. Object
|
|
590
|
+
* graphs with circular references (common for loaded relations) are
|
|
591
|
+
* sanitized: a circular branch is replaced with the string '[Circular]'.
|
|
592
|
+
*/
|
|
593
|
+
sanitizeForJsonb(value) {
|
|
594
|
+
const seen = new WeakSet();
|
|
595
|
+
const walk = (node) => {
|
|
596
|
+
if (node === null || typeof node !== 'object') {
|
|
597
|
+
return node;
|
|
598
|
+
}
|
|
599
|
+
if (seen.has(node)) {
|
|
600
|
+
return '[Circular]';
|
|
601
|
+
}
|
|
602
|
+
seen.add(node);
|
|
603
|
+
if (Array.isArray(node)) {
|
|
604
|
+
return node.map((item) => walk(item));
|
|
605
|
+
}
|
|
606
|
+
const out = {};
|
|
607
|
+
for (const key of Object.keys(node)) {
|
|
608
|
+
out[key] = walk(node[key]);
|
|
609
|
+
}
|
|
610
|
+
return out;
|
|
611
|
+
};
|
|
612
|
+
return walk(value);
|
|
613
|
+
}
|
|
552
614
|
async loadApprovalStatus(id, user) {
|
|
553
615
|
const entity = await this.resolveEntityForApproval(id, user);
|
|
554
616
|
if (!entity) {
|
|
@@ -562,52 +624,71 @@ let NestCrudService = class NestCrudService {
|
|
|
562
624
|
}
|
|
563
625
|
async transitionApproval(id, action, user, modifications, note) {
|
|
564
626
|
if (!this.isApprovalPipelineConfigured()) {
|
|
565
|
-
throw
|
|
627
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_APPROVAL_NOT_CONFIGURED);
|
|
566
628
|
}
|
|
567
629
|
const permission = this.approvalPipeline?.permissions?.[action];
|
|
568
630
|
if (permission && !this.userHasPermission(user, permission)) {
|
|
569
|
-
throw
|
|
631
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.FORBIDDEN, nest_error_1.ErrorKey.CRUD_APPROVAL_FORBIDDEN, { permission, action });
|
|
570
632
|
}
|
|
571
633
|
const approval = await this.loadApprovalStatus(id, user);
|
|
572
634
|
if (!approval) {
|
|
573
|
-
throw
|
|
635
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
574
636
|
}
|
|
575
637
|
const current = approval.status;
|
|
576
638
|
const now = new Date();
|
|
577
639
|
const userId = user?.id != null ? String(user.id) : null;
|
|
578
|
-
const
|
|
579
|
-
approval_pipeline_interface_1.APPROVAL_STATUS.
|
|
640
|
+
const reviewable = [
|
|
641
|
+
approval_pipeline_interface_1.APPROVAL_STATUS.submitted,
|
|
580
642
|
approval_pipeline_interface_1.APPROVAL_STATUS.resubmitted,
|
|
581
643
|
];
|
|
582
644
|
switch (action) {
|
|
645
|
+
case 'submit':
|
|
646
|
+
// Already in a reviewable/terminal state (e.g. the pipeline was
|
|
647
|
+
// configured with initialStatus: 'submitted'): no submit transition is
|
|
648
|
+
// needed, so this is a harmless no-op returning the current status.
|
|
649
|
+
if (current !== approval_pipeline_interface_1.APPROVAL_STATUS.draft) {
|
|
650
|
+
return this.toApprovalStatusView(approval);
|
|
651
|
+
}
|
|
652
|
+
approval.status = approval_pipeline_interface_1.APPROVAL_STATUS.submitted;
|
|
653
|
+
approval.requestedBy = userId;
|
|
654
|
+
break;
|
|
583
655
|
case 'approve':
|
|
584
|
-
this.assertTransitionAllowed(action, current,
|
|
656
|
+
this.assertTransitionAllowed(action, current, reviewable);
|
|
585
657
|
approval.status = approval_pipeline_interface_1.APPROVAL_STATUS.approved;
|
|
586
658
|
approval.decidedBy = userId;
|
|
587
659
|
approval.decidedAt = now;
|
|
588
660
|
approval.currentModifications = null;
|
|
589
661
|
break;
|
|
590
662
|
case 'reject':
|
|
591
|
-
this.assertTransitionAllowed(action, current,
|
|
663
|
+
this.assertTransitionAllowed(action, current, reviewable);
|
|
592
664
|
approval.status = approval_pipeline_interface_1.APPROVAL_STATUS.rejected;
|
|
593
665
|
approval.decidedBy = userId;
|
|
594
666
|
approval.decidedAt = now;
|
|
595
667
|
approval.currentModifications = null;
|
|
596
668
|
break;
|
|
597
|
-
case 'requestModification':
|
|
598
|
-
this.assertTransitionAllowed(action, current,
|
|
669
|
+
case 'requestModification': {
|
|
670
|
+
this.assertTransitionAllowed(action, current, reviewable);
|
|
599
671
|
approval.status = approval_pipeline_interface_1.APPROVAL_STATUS.modificationRequested;
|
|
600
|
-
|
|
672
|
+
const entity = await this.getEntityWithRelations(id, user);
|
|
673
|
+
const capturedModifications = (modifications ?? []).map((m) => ({
|
|
674
|
+
...m,
|
|
675
|
+
currentValue: this.sanitizeForJsonb(m.currentValue !== undefined
|
|
676
|
+
? m.currentValue
|
|
677
|
+
: this.readCurrentValue(entity, m.field)),
|
|
678
|
+
wantedValue: this.sanitizeForJsonb(m.wantedValue),
|
|
679
|
+
}));
|
|
680
|
+
approval.currentModifications = capturedModifications;
|
|
601
681
|
await this.getHistoryRepository().save(this.getHistoryRepository().create({
|
|
602
682
|
approvalStatusId: approval.id,
|
|
603
|
-
modifications:
|
|
683
|
+
modifications: capturedModifications,
|
|
604
684
|
requestedBy: userId,
|
|
605
685
|
note: note ?? null,
|
|
606
686
|
}));
|
|
607
687
|
break;
|
|
688
|
+
}
|
|
608
689
|
case 'resubmit':
|
|
609
690
|
if (current !== approval_pipeline_interface_1.APPROVAL_STATUS.modificationRequested) {
|
|
610
|
-
throw
|
|
691
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.BAD_REQUEST, nest_error_1.ErrorKey.CRUD_APPROVAL_INVALID_TRANSITION, { action: 'resubmit', current: String(current) });
|
|
611
692
|
}
|
|
612
693
|
approval.status = approval_pipeline_interface_1.APPROVAL_STATUS.resubmitted;
|
|
613
694
|
approval.currentModifications = null;
|
|
@@ -620,7 +701,7 @@ let NestCrudService = class NestCrudService {
|
|
|
620
701
|
}
|
|
621
702
|
assertTransitionAllowed(action, current, allowedFrom) {
|
|
622
703
|
if (!allowedFrom.includes(current)) {
|
|
623
|
-
throw
|
|
704
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.BAD_REQUEST, nest_error_1.ErrorKey.CRUD_APPROVAL_INVALID_TRANSITION, { action, current: String(current) });
|
|
624
705
|
}
|
|
625
706
|
}
|
|
626
707
|
userHasPermission(user, permission) {
|
|
@@ -689,7 +770,7 @@ let NestCrudService = class NestCrudService {
|
|
|
689
770
|
});
|
|
690
771
|
}
|
|
691
772
|
if (!existing) {
|
|
692
|
-
throw
|
|
773
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
693
774
|
}
|
|
694
775
|
await this.executeHook(this.hooks.beforeUpdate, { payload, entity: existing, id });
|
|
695
776
|
const transition = this.validateStatusTransition(existing, payload, user);
|
|
@@ -715,7 +796,7 @@ let NestCrudService = class NestCrudService {
|
|
|
715
796
|
});
|
|
716
797
|
}
|
|
717
798
|
if (!existing) {
|
|
718
|
-
throw
|
|
799
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.NOT_FOUND, nest_error_1.ErrorKey.CRUD_RESOURCE_NOT_FOUND);
|
|
719
800
|
}
|
|
720
801
|
await this.executeHook(this.hooks.beforeRemove, { entity: existing, id });
|
|
721
802
|
const result = await this.repo.delete(id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nest-util/nest-crud",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -28,10 +28,12 @@
|
|
|
28
28
|
"tslib": "^2.8.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@nest-util/nest-auth": "workspace:*"
|
|
31
|
+
"@nest-util/nest-auth": "workspace:*",
|
|
32
|
+
"@nest-util/nest-error": "workspace:*"
|
|
32
33
|
},
|
|
33
34
|
"peerDependencies": {
|
|
34
35
|
"@nest-util/nest-auth": "workspace:*",
|
|
36
|
+
"@nest-util/nest-error": "workspace:*",
|
|
35
37
|
"@nestjs/common": "^11.0.0",
|
|
36
38
|
"@nestjs/core": "^11.0.0",
|
|
37
39
|
"@nestjs/event-emitter": "^3.0.0",
|