@nest-util/nest-crud 2.0.2 → 2.0.4
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/helpers/relation-path.helper.d.ts +19 -0
- package/dist/lib/helpers/relation-path.helper.d.ts.map +1 -0
- package/dist/lib/helpers/relation-path.helper.js +48 -0
- package/dist/lib/helpers/value-hash.helper.d.ts +17 -0
- package/dist/lib/helpers/value-hash.helper.d.ts.map +1 -0
- package/dist/lib/helpers/value-hash.helper.js +56 -0
- package/dist/lib/interfaces/approval-pipeline.interface.d.ts +34 -0
- package/dist/lib/interfaces/approval-pipeline.interface.d.ts.map +1 -1
- package/dist/lib/services/nest-crud.service.d.ts +15 -0
- package/dist/lib/services/nest-crud.service.d.ts.map +1 -1
- package/dist/lib/services/nest-crud.service.js +98 -12
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EntityMetadata } from 'typeorm';
|
|
2
|
+
/**
|
|
3
|
+
* Expands modification-field dot paths into every relation prefix required to
|
|
4
|
+
* load the referenced values, so relation fields (e.g. `addresses` or
|
|
5
|
+
* `company.addresses.city`) can be read without the consumer configuring
|
|
6
|
+
* `include`.
|
|
7
|
+
*
|
|
8
|
+
* Non-relation columns yield nothing. Unknown paths are ignored.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveRelationPaths(metadata: EntityMetadata, fields: readonly string[]): string[];
|
|
11
|
+
/** Checks whether a (root) property name exists on the entity as a column,
|
|
12
|
+
* embedded column, or relation. */
|
|
13
|
+
export declare function isKnownProperty(metadata: EntityMetadata, propertyName: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Normalizes a captured modification value so it is always jsonb-serializable
|
|
16
|
+
* and distinguishable from "not loaded": `undefined` becomes explicit `null`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeCapturedValue(value: unknown): unknown;
|
|
19
|
+
//# sourceMappingURL=relation-path.helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relation-path.helper.d.ts","sourceRoot":"","sources":["../../../src/lib/helpers/relation-path.helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,SAAS,MAAM,EAAE,GACxB,MAAM,EAAE,CAyBV;AAED;mCACmC;AACnC,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,cAAc,EACxB,YAAY,EAAE,MAAM,GACnB,OAAO,CAST;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE9D"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveRelationPaths = resolveRelationPaths;
|
|
4
|
+
exports.isKnownProperty = isKnownProperty;
|
|
5
|
+
exports.normalizeCapturedValue = normalizeCapturedValue;
|
|
6
|
+
/**
|
|
7
|
+
* Expands modification-field dot paths into every relation prefix required to
|
|
8
|
+
* load the referenced values, so relation fields (e.g. `addresses` or
|
|
9
|
+
* `company.addresses.city`) can be read without the consumer configuring
|
|
10
|
+
* `include`.
|
|
11
|
+
*
|
|
12
|
+
* Non-relation columns yield nothing. Unknown paths are ignored.
|
|
13
|
+
*/
|
|
14
|
+
function resolveRelationPaths(metadata, fields) {
|
|
15
|
+
const resolved = new Set();
|
|
16
|
+
for (const field of fields) {
|
|
17
|
+
if (!field) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const segments = field.split('.');
|
|
21
|
+
let relations = metadata.relations ?? [];
|
|
22
|
+
let prefix = [];
|
|
23
|
+
for (const segment of segments) {
|
|
24
|
+
const match = relations.find((relation) => relation.propertyName === segment);
|
|
25
|
+
if (!match) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
prefix = [...prefix, match.propertyName];
|
|
29
|
+
resolved.add(prefix.join('.'));
|
|
30
|
+
relations = match.inverseEntityMetadata.relations;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [...resolved];
|
|
34
|
+
}
|
|
35
|
+
/** Checks whether a (root) property name exists on the entity as a column,
|
|
36
|
+
* embedded column, or relation. */
|
|
37
|
+
function isKnownProperty(metadata, propertyName) {
|
|
38
|
+
return ((metadata.columns ?? []).some((column) => column.propertyPath === propertyName ||
|
|
39
|
+
column.propertyPath.startsWith(`${propertyName}.`)) ||
|
|
40
|
+
(metadata.relations ?? []).some((relation) => relation.propertyName === propertyName));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Normalizes a captured modification value so it is always jsonb-serializable
|
|
44
|
+
* and distinguishable from "not loaded": `undefined` becomes explicit `null`.
|
|
45
|
+
*/
|
|
46
|
+
function normalizeCapturedValue(value) {
|
|
47
|
+
return value === undefined ? null : value;
|
|
48
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Produces a deterministic, stable serialization of an arbitrary value so it
|
|
3
|
+
* can be hashed consistently across processes and after jsonb round-trips:
|
|
4
|
+
* - object keys are sorted recursively
|
|
5
|
+
* - Date instances become ISO strings
|
|
6
|
+
* - undefined / null / missing all normalize to null
|
|
7
|
+
* - arrays are order-insensitive: elements are hashed by their own canonical
|
|
8
|
+
* form and the resulting strings are sorted (a reordered-but-identical
|
|
9
|
+
* collection hashes equal — change detection, not order verification)
|
|
10
|
+
* - circular references become the string '[Circular]'
|
|
11
|
+
*/
|
|
12
|
+
export declare function canonicalJson(value: unknown): string;
|
|
13
|
+
/** SHA-256 hex hash of a value's canonical JSON form. */
|
|
14
|
+
export declare function hashValue(value: unknown): string;
|
|
15
|
+
/** Structural equality used as legacy fallback when no hash is stored. */
|
|
16
|
+
export declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
17
|
+
//# sourceMappingURL=value-hash.helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value-hash.helper.d.ts","sourceRoot":"","sources":["../../../src/lib/helpers/value-hash.helper.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA8BpD;AAED,yDAAyD;AACzD,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAEzD"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonicalJson = canonicalJson;
|
|
4
|
+
exports.hashValue = hashValue;
|
|
5
|
+
exports.deepEqual = deepEqual;
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
/**
|
|
8
|
+
* Produces a deterministic, stable serialization of an arbitrary value so it
|
|
9
|
+
* can be hashed consistently across processes and after jsonb round-trips:
|
|
10
|
+
* - object keys are sorted recursively
|
|
11
|
+
* - Date instances become ISO strings
|
|
12
|
+
* - undefined / null / missing all normalize to null
|
|
13
|
+
* - arrays are order-insensitive: elements are hashed by their own canonical
|
|
14
|
+
* form and the resulting strings are sorted (a reordered-but-identical
|
|
15
|
+
* collection hashes equal — change detection, not order verification)
|
|
16
|
+
* - circular references become the string '[Circular]'
|
|
17
|
+
*/
|
|
18
|
+
function canonicalJson(value) {
|
|
19
|
+
const seen = new WeakSet();
|
|
20
|
+
const walk = (node) => {
|
|
21
|
+
if (node === undefined || node === null) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (node instanceof Date) {
|
|
25
|
+
return Number.isNaN(node.getTime()) ? null : node.toISOString();
|
|
26
|
+
}
|
|
27
|
+
if (typeof node === 'object') {
|
|
28
|
+
if (seen.has(node)) {
|
|
29
|
+
return '[Circular]';
|
|
30
|
+
}
|
|
31
|
+
seen.add(node);
|
|
32
|
+
if (Array.isArray(node)) {
|
|
33
|
+
const items = node
|
|
34
|
+
.map((item) => JSON.stringify(walk(item)))
|
|
35
|
+
.sort()
|
|
36
|
+
.map((item) => JSON.parse(item));
|
|
37
|
+
return { __set: items };
|
|
38
|
+
}
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const key of Object.keys(node).sort()) {
|
|
41
|
+
out[key] = walk(node[key]);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
return node;
|
|
46
|
+
};
|
|
47
|
+
return JSON.stringify(walk(value));
|
|
48
|
+
}
|
|
49
|
+
/** SHA-256 hex hash of a value's canonical JSON form. */
|
|
50
|
+
function hashValue(value) {
|
|
51
|
+
return (0, crypto_1.createHash)('sha256').update(canonicalJson(value)).digest('hex');
|
|
52
|
+
}
|
|
53
|
+
/** Structural equality used as legacy fallback when no hash is stored. */
|
|
54
|
+
function deepEqual(a, b) {
|
|
55
|
+
return canonicalJson(a) === canonicalJson(b);
|
|
56
|
+
}
|
|
@@ -31,6 +31,12 @@ export interface ModificationItem {
|
|
|
31
31
|
* '[Circular]').
|
|
32
32
|
*/
|
|
33
33
|
currentValue?: unknown;
|
|
34
|
+
/**
|
|
35
|
+
* SHA-256 hash of the captured value's canonical JSON form. Stored so the
|
|
36
|
+
* resubmit check can cheaply detect whether the field changed since the
|
|
37
|
+
* request without deep-comparing (possibly large) relation snapshots.
|
|
38
|
+
*/
|
|
39
|
+
currentValueHash?: string;
|
|
34
40
|
/** Value the reviewer wants instead. */
|
|
35
41
|
wantedValue: unknown;
|
|
36
42
|
/** Optional human-readable explanation. */
|
|
@@ -106,6 +112,34 @@ export interface ApprovalPipelineConfig {
|
|
|
106
112
|
* `after*`) the previous status.
|
|
107
113
|
*/
|
|
108
114
|
hooks?: ApprovalHooks;
|
|
115
|
+
/**
|
|
116
|
+
* Verifies that the requester actually applied the requested changes before
|
|
117
|
+
* allowing a resubmit. For each modification item the live field value is
|
|
118
|
+
* hashed and compared with the hash captured at request time — an identical
|
|
119
|
+
* hash means the field was left untouched.
|
|
120
|
+
*/
|
|
121
|
+
resubmitCheck?: ApprovalResubmitCheckConfig;
|
|
122
|
+
}
|
|
123
|
+
export interface ApprovalResubmitCheckConfig {
|
|
124
|
+
/**
|
|
125
|
+
* 'all' (default): every non-ignored modification field must have changed.
|
|
126
|
+
* 'any': at least one must have changed.
|
|
127
|
+
*/
|
|
128
|
+
mode?: 'all' | 'any';
|
|
129
|
+
/** Modification fields exempt from the "must have changed" gate. */
|
|
130
|
+
ignoreFields?: readonly string[];
|
|
131
|
+
/**
|
|
132
|
+
* Full override of the built-in comparison. Return false to block the
|
|
133
|
+
* resubmit with CRUD_APPROVAL_RESUBMIT_NOT_SATISFIED.
|
|
134
|
+
*/
|
|
135
|
+
customChecker?: (context: {
|
|
136
|
+
modifications: ModificationItem[];
|
|
137
|
+
satisfied: {
|
|
138
|
+
field: string;
|
|
139
|
+
satisfied: boolean;
|
|
140
|
+
}[];
|
|
141
|
+
entity: unknown;
|
|
142
|
+
}) => boolean | Promise<boolean>;
|
|
109
143
|
}
|
|
110
144
|
export interface RequestModificationPayload {
|
|
111
145
|
modifications: ModificationItem[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-pipeline.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/interfaces/approval-pipeline.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;;;;;;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;IAE5C;;;;OAIG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"approval-pipeline.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/interfaces/approval-pipeline.interface.ts"],"names":[],"mappings":"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"}
|
|
@@ -129,8 +129,16 @@ export declare class NestCrudService<Entity extends ObjectLiteral, CreateDto = P
|
|
|
129
129
|
* Deep-clones a value so it can be safely stored in a jsonb column. Object
|
|
130
130
|
* graphs with circular references (common for loaded relations) are
|
|
131
131
|
* sanitized: a circular branch is replaced with the string '[Circular]'.
|
|
132
|
+
* Keys whose value is `undefined` are dropped, mirroring jsonb persistence.
|
|
132
133
|
*/
|
|
133
134
|
private sanitizeForJsonb;
|
|
135
|
+
/**
|
|
136
|
+
* Hashes a modification value through the exact pipeline its stored snapshot
|
|
137
|
+
* goes through — sanitize clone plus a JSON round-trip simulating jsonb
|
|
138
|
+
* persistence (Dates → ISO strings, undefined dropped) — so the resubmit
|
|
139
|
+
* check compares like with like regardless of TypeORM hydration.
|
|
140
|
+
*/
|
|
141
|
+
private hashModificationValue;
|
|
134
142
|
private loadApprovalStatus;
|
|
135
143
|
private transitionApproval;
|
|
136
144
|
private fireApprovalBeforeHook;
|
|
@@ -138,6 +146,13 @@ export declare class NestCrudService<Entity extends ObjectLiteral, CreateDto = P
|
|
|
138
146
|
private assertTransitionAllowed;
|
|
139
147
|
private userHasPermission;
|
|
140
148
|
private applyApprovalVisibility;
|
|
149
|
+
/**
|
|
150
|
+
* Ensures the requester actually changed the fields covered by the stored
|
|
151
|
+
* modification request before a resubmit is allowed. Each item's live value
|
|
152
|
+
* hash is compared against the hash captured at request time; an identical
|
|
153
|
+
* hash means the field was left untouched.
|
|
154
|
+
*/
|
|
155
|
+
private assertResubmitSatisfied;
|
|
141
156
|
private approvalIdCastExpression;
|
|
142
157
|
private toApprovalStatusView;
|
|
143
158
|
private toApprovalHistoryView;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nest-crud.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/nest-crud.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAE9D,OAAO,EAAe,aAAa,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"nest-crud.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/nest-crud.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,8 @@ const nest_error_1 = require("@nest-util/nest-error");
|
|
|
7
7
|
const audit_log_entity_1 = require("../entities/audit-log.entity");
|
|
8
8
|
const filter_helper_1 = require("../helpers/filter.helper");
|
|
9
9
|
const pagination_helper_1 = require("../helpers/pagination.helper");
|
|
10
|
+
const relation_path_helper_1 = require("../helpers/relation-path.helper");
|
|
11
|
+
const value_hash_helper_1 = require("../helpers/value-hash.helper");
|
|
10
12
|
const cursor_pagination_helper_1 = require("../helpers/cursor-pagination.helper");
|
|
11
13
|
const approval_status_entity_1 = require("../entities/approval-status.entity");
|
|
12
14
|
const modification_request_history_entity_1 = require("../entities/modification-request-history.entity");
|
|
@@ -559,12 +561,15 @@ let NestCrudService = class NestCrudService {
|
|
|
559
561
|
* Loads the target entity with its configured `include` relations so that
|
|
560
562
|
* relation fields can be read as objects when capturing modification values.
|
|
561
563
|
*/
|
|
562
|
-
async getEntityWithRelations(id, user) {
|
|
564
|
+
async getEntityWithRelations(id, user, extraRelations) {
|
|
563
565
|
if (this.enforceOwnershipFor(user)) {
|
|
564
566
|
return this.findOwnedEntity(id, user);
|
|
565
567
|
}
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
+
const relationPaths = [
|
|
569
|
+
...new Set([...this.include, ...(extraRelations ?? [])]),
|
|
570
|
+
];
|
|
571
|
+
const relations = relationPaths.length
|
|
572
|
+
? this.buildRelationsObject(relationPaths)
|
|
568
573
|
: undefined;
|
|
569
574
|
return this.repo.findOne({
|
|
570
575
|
where: { id },
|
|
@@ -590,6 +595,7 @@ let NestCrudService = class NestCrudService {
|
|
|
590
595
|
* Deep-clones a value so it can be safely stored in a jsonb column. Object
|
|
591
596
|
* graphs with circular references (common for loaded relations) are
|
|
592
597
|
* sanitized: a circular branch is replaced with the string '[Circular]'.
|
|
598
|
+
* Keys whose value is `undefined` are dropped, mirroring jsonb persistence.
|
|
593
599
|
*/
|
|
594
600
|
sanitizeForJsonb(value) {
|
|
595
601
|
const seen = new WeakSet();
|
|
@@ -597,6 +603,9 @@ let NestCrudService = class NestCrudService {
|
|
|
597
603
|
if (node === null || typeof node !== 'object') {
|
|
598
604
|
return node;
|
|
599
605
|
}
|
|
606
|
+
if (node instanceof Date) {
|
|
607
|
+
return Number.isNaN(node.getTime()) ? null : node.toISOString();
|
|
608
|
+
}
|
|
600
609
|
if (seen.has(node)) {
|
|
601
610
|
return '[Circular]';
|
|
602
611
|
}
|
|
@@ -606,12 +615,29 @@ let NestCrudService = class NestCrudService {
|
|
|
606
615
|
}
|
|
607
616
|
const out = {};
|
|
608
617
|
for (const key of Object.keys(node)) {
|
|
609
|
-
|
|
618
|
+
const item = node[key];
|
|
619
|
+
if (item === undefined) {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
out[key] = walk(item);
|
|
610
623
|
}
|
|
611
624
|
return out;
|
|
612
625
|
};
|
|
613
626
|
return walk(value);
|
|
614
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* Hashes a modification value through the exact pipeline its stored snapshot
|
|
630
|
+
* goes through — sanitize clone plus a JSON round-trip simulating jsonb
|
|
631
|
+
* persistence (Dates → ISO strings, undefined dropped) — so the resubmit
|
|
632
|
+
* check compares like with like regardless of TypeORM hydration.
|
|
633
|
+
*/
|
|
634
|
+
hashModificationValue(value) {
|
|
635
|
+
const sanitized = this.sanitizeForJsonb(value);
|
|
636
|
+
const roundTripped = typeof sanitized === 'object' && sanitized !== null
|
|
637
|
+
? JSON.parse(JSON.stringify(sanitized))
|
|
638
|
+
: sanitized;
|
|
639
|
+
return (0, value_hash_helper_1.hashValue)(roundTripped);
|
|
640
|
+
}
|
|
615
641
|
async loadApprovalStatus(id, user) {
|
|
616
642
|
const entity = await this.resolveEntityForApproval(id, user);
|
|
617
643
|
if (!entity) {
|
|
@@ -678,14 +704,23 @@ let NestCrudService = class NestCrudService {
|
|
|
678
704
|
case 'requestModification': {
|
|
679
705
|
this.assertTransitionAllowed(action, current, reviewable);
|
|
680
706
|
approval.status = approval_pipeline_interface_1.APPROVAL_STATUS.modificationRequested;
|
|
681
|
-
const
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
707
|
+
const fields = (modifications ?? []).map((m) => m.field);
|
|
708
|
+
const extraRelations = (0, relation_path_helper_1.resolveRelationPaths)(this.repo.metadata, fields);
|
|
709
|
+
const entity = await this.getEntityWithRelations(id, user, extraRelations);
|
|
710
|
+
const capturedModifications = (modifications ?? []).map((m) => {
|
|
711
|
+
if (!(0, relation_path_helper_1.isKnownProperty)(this.repo.metadata, m.field.split('.')[0])) {
|
|
712
|
+
console.warn(`[NestCrudService] Approval modification field "${m.field}" does not exist on ${this.repo.metadata.tableName}; capturing null`);
|
|
713
|
+
}
|
|
714
|
+
const rawValue = m.currentValue !== undefined
|
|
685
715
|
? m.currentValue
|
|
686
|
-
: this.readCurrentValue(entity, m.field)
|
|
687
|
-
|
|
688
|
-
|
|
716
|
+
: this.readCurrentValue(entity, m.field);
|
|
717
|
+
return {
|
|
718
|
+
...m,
|
|
719
|
+
currentValue: this.sanitizeForJsonb((0, relation_path_helper_1.normalizeCapturedValue)(rawValue)),
|
|
720
|
+
currentValueHash: this.hashModificationValue(rawValue),
|
|
721
|
+
wantedValue: this.sanitizeForJsonb(m.wantedValue),
|
|
722
|
+
};
|
|
723
|
+
});
|
|
689
724
|
approval.currentModifications = capturedModifications;
|
|
690
725
|
await this.getHistoryRepository().save(this.getHistoryRepository().create({
|
|
691
726
|
approvalStatusId: approval.id,
|
|
@@ -695,15 +730,17 @@ let NestCrudService = class NestCrudService {
|
|
|
695
730
|
}));
|
|
696
731
|
break;
|
|
697
732
|
}
|
|
698
|
-
case 'resubmit':
|
|
733
|
+
case 'resubmit': {
|
|
699
734
|
if (current !== approval_pipeline_interface_1.APPROVAL_STATUS.modificationRequested) {
|
|
700
735
|
throw (0, nest_error_1.keyed)(common_1.HttpStatus.BAD_REQUEST, nest_error_1.ErrorKey.CRUD_APPROVAL_INVALID_TRANSITION, { action: 'resubmit', current: String(current) });
|
|
701
736
|
}
|
|
737
|
+
await this.assertResubmitSatisfied(approval, id, user);
|
|
702
738
|
approval.status = approval_pipeline_interface_1.APPROVAL_STATUS.resubmitted;
|
|
703
739
|
approval.currentModifications = null;
|
|
704
740
|
approval.resubmittedBy = userId;
|
|
705
741
|
approval.resubmittedAt = now;
|
|
706
742
|
break;
|
|
743
|
+
}
|
|
707
744
|
}
|
|
708
745
|
const saved = await this.getApprovalRepository().save(approval);
|
|
709
746
|
const afterContext = {
|
|
@@ -776,6 +813,55 @@ let NestCrudService = class NestCrudService {
|
|
|
776
813
|
approvalVisibleStatuses: [...visible],
|
|
777
814
|
});
|
|
778
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Ensures the requester actually changed the fields covered by the stored
|
|
818
|
+
* modification request before a resubmit is allowed. Each item's live value
|
|
819
|
+
* hash is compared against the hash captured at request time; an identical
|
|
820
|
+
* hash means the field was left untouched.
|
|
821
|
+
*/
|
|
822
|
+
async assertResubmitSatisfied(approval, id, user) {
|
|
823
|
+
const checkConfig = this.approvalPipeline?.resubmitCheck;
|
|
824
|
+
const modifications = approval.currentModifications ?? [];
|
|
825
|
+
if (modifications.length === 0) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
const fields = modifications.map((m) => m.field);
|
|
829
|
+
const extraRelations = (0, relation_path_helper_1.resolveRelationPaths)(this.repo.metadata, fields);
|
|
830
|
+
const entity = await this.getEntityWithRelations(id, user, extraRelations);
|
|
831
|
+
const ignored = new Set(checkConfig?.ignoreFields ?? []);
|
|
832
|
+
const satisfied = modifications.map((m) => {
|
|
833
|
+
if (ignored.has(m.field)) {
|
|
834
|
+
return { field: m.field, satisfied: true };
|
|
835
|
+
}
|
|
836
|
+
const live = this.readCurrentValue(entity, m.field);
|
|
837
|
+
const satisfiedField = m.currentValueHash !== undefined
|
|
838
|
+
? this.hashModificationValue(live) !== m.currentValueHash
|
|
839
|
+
: m.currentValue !== undefined
|
|
840
|
+
? !(0, value_hash_helper_1.deepEqual)(live, m.currentValue)
|
|
841
|
+
: true;
|
|
842
|
+
return { field: m.field, satisfied: satisfiedField };
|
|
843
|
+
});
|
|
844
|
+
let allowed;
|
|
845
|
+
if (checkConfig?.customChecker) {
|
|
846
|
+
allowed = await checkConfig.customChecker({
|
|
847
|
+
modifications,
|
|
848
|
+
satisfied,
|
|
849
|
+
entity,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
const required = satisfied.filter((s) => s.satisfied);
|
|
854
|
+
allowed =
|
|
855
|
+
checkConfig?.mode === 'any' ? required.length > 0 : required.length === satisfied.length;
|
|
856
|
+
}
|
|
857
|
+
if (!allowed) {
|
|
858
|
+
throw (0, nest_error_1.keyed)(common_1.HttpStatus.BAD_REQUEST, nest_error_1.ErrorKey.CRUD_APPROVAL_RESUBMIT_NOT_SATISFIED, {
|
|
859
|
+
unchangedFields: satisfied
|
|
860
|
+
.filter((s) => !s.satisfied)
|
|
861
|
+
.map((s) => s.field),
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}
|
|
779
865
|
approvalIdCastExpression() {
|
|
780
866
|
const type = this.repo.manager.connection.options.type;
|
|
781
867
|
if (type === 'mysql' || type === 'mariadb' || type === 'aurora-mysql') {
|