@nest-util/nest-crud 1.0.5 → 1.0.8
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/README.md +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/lib/events/audit-event.interface.d.ts +32 -0
- package/dist/lib/events/audit-event.interface.d.ts.map +1 -0
- package/dist/lib/events/audit-event.interface.js +2 -0
- package/dist/lib/events/audit-event.listener.d.ts +8 -0
- package/dist/lib/events/audit-event.listener.d.ts.map +1 -0
- package/dist/lib/events/audit-event.listener.js +58 -0
- package/dist/lib/events/audit-event.module.d.ts +6 -0
- package/dist/lib/events/audit-event.module.d.ts.map +1 -0
- package/dist/lib/events/audit-event.module.js +44 -0
- package/dist/lib/events/handlers/console.handler.d.ts +5 -0
- package/dist/lib/events/handlers/console.handler.d.ts.map +1 -0
- package/dist/lib/events/handlers/console.handler.js +43 -0
- package/dist/lib/events/index.d.ts +5 -0
- package/dist/lib/events/index.d.ts.map +1 -0
- package/dist/lib/events/index.js +7 -0
- package/dist/lib/interceptors/audit-log.interceptor.d.ts +4 -1
- package/dist/lib/interceptors/audit-log.interceptor.d.ts.map +1 -1
- package/dist/lib/interceptors/audit-log.interceptor.js +46 -2
- package/dist/lib/testing/crud-controller.test-suites.d.ts.map +1 -1
- package/dist/lib/testing/crud-controller.test-suites.js +3 -0
- package/dist/lib/testing/crud-service.test-suites.d.ts.map +1 -1
- package/dist/lib/testing/crud-service.test-suites.js +4 -0
- package/dist/lib/testing/testing.interface.d.ts +1 -0
- package/dist/lib/testing/testing.interface.d.ts.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ This library was generated with [Nx](https://nx.dev).
|
|
|
7
7
|
We recommend using **pnpm** as your package manager.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
pnpm add @nest-util/nest-crud@^1.0.
|
|
10
|
+
pnpm add @nest-util/nest-crud@^1.0.7 @nest-util/nest-auth@^1.1.0 typeorm@^1.1.0 @nestjs/typeorm @nestjs/swagger @nestjs/jwt @nestjs/passport class-validator class-transformer bcrypt
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Features
|
package/dist/index.d.ts
CHANGED
|
@@ -18,4 +18,5 @@ export * from './lib/helpers/exception-filter.helper';
|
|
|
18
18
|
export * from './lib/helpers/cursor-pagination.helper';
|
|
19
19
|
export * from './lib/entities/audit-log.entity';
|
|
20
20
|
export * from './lib/services/audit-log.service';
|
|
21
|
+
export * from './lib/events';
|
|
21
22
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,kCAAkC,CAAC;AACjD,cAAc,wCAAwC,CAAC;AACvD,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,sCAAsC,CAAC;AACrD,cAAc,yCAAyC,CAAC;AACxD,cAAc,0CAA0C,CAAC;AACzD,cAAc,iCAAiC,CAAC;AAChD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kCAAkC,CAAC;AACjD,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,kCAAkC,CAAC;AACjD,cAAc,wCAAwC,CAAC;AACvD,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,sCAAsC,CAAC;AACrD,cAAc,yCAAyC,CAAC;AACxD,cAAc,0CAA0C,CAAC;AACzD,cAAc,iCAAiC,CAAC;AAChD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,kCAAkC,CAAC;AACjD,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,wCAAwC,CAAC;AACvD,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC;AACjD,cAAc,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -21,3 +21,4 @@ tslib_1.__exportStar(require("./lib/helpers/exception-filter.helper"), exports);
|
|
|
21
21
|
tslib_1.__exportStar(require("./lib/helpers/cursor-pagination.helper"), exports);
|
|
22
22
|
tslib_1.__exportStar(require("./lib/entities/audit-log.entity"), exports);
|
|
23
23
|
tslib_1.__exportStar(require("./lib/services/audit-log.service"), exports);
|
|
24
|
+
tslib_1.__exportStar(require("./lib/events"), exports);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface AuditEvent {
|
|
2
|
+
/** Dot-separated event name, e.g. 'auth.user.login.success' */
|
|
3
|
+
action: string;
|
|
4
|
+
/** Entity type: 'user', 'role', 'post', etc. */
|
|
5
|
+
entity: string;
|
|
6
|
+
/** ID of the affected entity (optional for list/search events) */
|
|
7
|
+
entityId?: string | number;
|
|
8
|
+
/** ID of the user performing the action */
|
|
9
|
+
userId?: string | number;
|
|
10
|
+
/** Client IP address */
|
|
11
|
+
ip?: string;
|
|
12
|
+
/** User-Agent header */
|
|
13
|
+
userAgent?: string;
|
|
14
|
+
/** Multi-tenant ID */
|
|
15
|
+
tenantId?: string;
|
|
16
|
+
/** Event timestamp */
|
|
17
|
+
timestamp: Date;
|
|
18
|
+
/** Arbitrary event-specific data */
|
|
19
|
+
metadata?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export interface AuditEventHandler {
|
|
22
|
+
handle(event: AuditEvent): void | Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export interface AuditEventModuleOptions {
|
|
25
|
+
/** Handlers to dispatch events to */
|
|
26
|
+
handlers: AuditEventHandler[];
|
|
27
|
+
/** Glob patterns to include (default: ['*']) */
|
|
28
|
+
include?: string[];
|
|
29
|
+
/** Glob patterns to exclude (default: []) */
|
|
30
|
+
exclude?: string[];
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=audit-event.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-event.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/events/audit-event.interface.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,wBAAwB;IACxB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,SAAS,EAAE,IAAI,CAAC;IAChB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,uBAAuB;IACtC,qCAAqC;IACrC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AuditEvent, AuditEventHandler, AuditEventModuleOptions } from './audit-event.interface';
|
|
2
|
+
export declare class AuditEventListener {
|
|
3
|
+
private handlers;
|
|
4
|
+
private options;
|
|
5
|
+
configure(handlers: AuditEventHandler[], options: AuditEventModuleOptions): void;
|
|
6
|
+
handleEvent(event: AuditEvent): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=audit-event.listener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-event.listener.d.ts","sourceRoot":"","sources":["../../../src/lib/events/audit-event.listener.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAgCtG,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,OAAO,CAA6C;IAE5D,SAAS,CAAC,QAAQ,EAAE,iBAAiB,EAAE,EAAE,OAAO,EAAE,uBAAuB,GAAG,IAAI;IAK1E,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAiBpD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuditEventListener = void 0;
|
|
4
|
+
const RE_SPECIAL = /[.+?^${}()|[\]\\]/g;
|
|
5
|
+
function matchesGlob(pattern, value) {
|
|
6
|
+
let regexStr = '^';
|
|
7
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
8
|
+
const ch = pattern[i];
|
|
9
|
+
if (ch === '*') {
|
|
10
|
+
if (i + 1 < pattern.length && pattern[i + 1] === '*') {
|
|
11
|
+
regexStr += '.*';
|
|
12
|
+
i++;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
regexStr += '[^.]*';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
regexStr += ch.replace(RE_SPECIAL, '\\$&');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
regexStr += '$';
|
|
23
|
+
return new RegExp(regexStr).test(value);
|
|
24
|
+
}
|
|
25
|
+
function shouldDispatch(event, options) {
|
|
26
|
+
const { include = ['*'], exclude = [] } = options;
|
|
27
|
+
const action = event.action;
|
|
28
|
+
const included = include.some((pattern) => matchesGlob(pattern, action));
|
|
29
|
+
if (!included)
|
|
30
|
+
return false;
|
|
31
|
+
const excluded = exclude.some((pattern) => matchesGlob(pattern, action));
|
|
32
|
+
return !excluded;
|
|
33
|
+
}
|
|
34
|
+
class AuditEventListener {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.handlers = [];
|
|
37
|
+
this.options = { handlers: [] };
|
|
38
|
+
}
|
|
39
|
+
configure(handlers, options) {
|
|
40
|
+
this.handlers = handlers;
|
|
41
|
+
this.options = options;
|
|
42
|
+
}
|
|
43
|
+
async handleEvent(event) {
|
|
44
|
+
const auditEvent = event;
|
|
45
|
+
if (!shouldDispatch(auditEvent, this.options))
|
|
46
|
+
return;
|
|
47
|
+
const results = this.handlers.map(async (handler) => {
|
|
48
|
+
try {
|
|
49
|
+
await handler.handle(auditEvent);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error(`[AuditEvent] Handler ${handler.constructor.name} failed for "${auditEvent.action}":`, err);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
await Promise.allSettled(results);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.AuditEventListener = AuditEventListener;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import type { AuditEventModuleOptions } from './audit-event.interface';
|
|
3
|
+
export declare class AuditEventModule {
|
|
4
|
+
static forRoot(options: AuditEventModuleOptions): DynamicModule;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=audit-event.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-event.module.d.ts","sourceRoot":"","sources":["../../../src/lib/events/audit-event.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAkB,MAAM,gBAAgB,CAAC;AAE/D,OAAO,KAAK,EAAc,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAMnF,qBAEa,gBAAgB;IAC3B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,uBAAuB,GAAG,aAAa;CA8BhE"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var AuditEventModule_1;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.AuditEventModule = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const event_emitter_1 = require("@nestjs/event-emitter");
|
|
8
|
+
const audit_event_listener_1 = require("./audit-event.listener");
|
|
9
|
+
const AUDIT_EVENT_OPTIONS = 'AUDIT_EVENT_OPTIONS';
|
|
10
|
+
const AUDIT_EVENT_HANDLERS = 'AUDIT_EVENT_HANDLERS';
|
|
11
|
+
let AuditEventModule = AuditEventModule_1 = class AuditEventModule {
|
|
12
|
+
static forRoot(options) {
|
|
13
|
+
const listenerProvider = {
|
|
14
|
+
provide: audit_event_listener_1.AuditEventListener,
|
|
15
|
+
useFactory: (eventEmitter) => {
|
|
16
|
+
const listener = new audit_event_listener_1.AuditEventListener();
|
|
17
|
+
listener.configure(options.handlers, options);
|
|
18
|
+
eventEmitter.onAny(async (...args) => {
|
|
19
|
+
const event = args[1];
|
|
20
|
+
if (event?.action) {
|
|
21
|
+
await listener.handleEvent(event);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return listener;
|
|
25
|
+
},
|
|
26
|
+
inject: [event_emitter_1.EventEmitter2],
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
module: AuditEventModule_1,
|
|
30
|
+
imports: [event_emitter_1.EventEmitterModule.forRoot()],
|
|
31
|
+
providers: [
|
|
32
|
+
listenerProvider,
|
|
33
|
+
{ provide: AUDIT_EVENT_OPTIONS, useValue: options },
|
|
34
|
+
{ provide: AUDIT_EVENT_HANDLERS, useValue: options.handlers },
|
|
35
|
+
],
|
|
36
|
+
exports: [event_emitter_1.EventEmitterModule],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.AuditEventModule = AuditEventModule;
|
|
41
|
+
exports.AuditEventModule = AuditEventModule = AuditEventModule_1 = tslib_1.__decorate([
|
|
42
|
+
(0, common_1.Global)(),
|
|
43
|
+
(0, common_1.Module)({})
|
|
44
|
+
], AuditEventModule);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console.handler.d.ts","sourceRoot":"","sources":["../../../../src/lib/events/handlers/console.handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAwCzE,qBAAa,cAAe,YAAW,iBAAiB;IACtD,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CAGhC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConsoleHandler = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const COLORS = {
|
|
6
|
+
reset: '\x1b[0m',
|
|
7
|
+
dim: '\x1b[2m',
|
|
8
|
+
red: '\x1b[31m',
|
|
9
|
+
green: '\x1b[32m',
|
|
10
|
+
yellow: '\x1b[33m',
|
|
11
|
+
blue: '\x1b[34m',
|
|
12
|
+
cyan: '\x1b[36m',
|
|
13
|
+
gray: '\x1b[90m',
|
|
14
|
+
};
|
|
15
|
+
function colorize(text, color) {
|
|
16
|
+
return `${color}${text}${COLORS.reset}`;
|
|
17
|
+
}
|
|
18
|
+
function formatEvent(event) {
|
|
19
|
+
const ts = event.timestamp.toISOString();
|
|
20
|
+
const isSuccess = event.action.endsWith('.success') || (!event.action.includes('.failed') && !event.action.includes('.error') && !event.action.includes('.denied') && !event.action.includes('.conflict') && !event.action.includes('.locked') && !event.action.includes('.disabled'));
|
|
21
|
+
const statusColor = isSuccess ? COLORS.green : COLORS.red;
|
|
22
|
+
const parts = [
|
|
23
|
+
colorize(ts, COLORS.dim),
|
|
24
|
+
colorize(event.action, statusColor),
|
|
25
|
+
colorize(`entity=${event.entity}`, COLORS.cyan),
|
|
26
|
+
];
|
|
27
|
+
if (event.entityId != null) {
|
|
28
|
+
parts.push(colorize(`id=${event.entityId}`, COLORS.blue));
|
|
29
|
+
}
|
|
30
|
+
if (event.userId != null) {
|
|
31
|
+
parts.push(colorize(`userId=${event.userId}`, COLORS.yellow));
|
|
32
|
+
}
|
|
33
|
+
if (event.ip) {
|
|
34
|
+
parts.push(colorize(`ip=${event.ip}`, COLORS.gray));
|
|
35
|
+
}
|
|
36
|
+
return parts.join(' ');
|
|
37
|
+
}
|
|
38
|
+
class ConsoleHandler {
|
|
39
|
+
handle(event) {
|
|
40
|
+
common_1.Logger.log(formatEvent(event));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.ConsoleHandler = ConsoleHandler;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/events/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./audit-event.interface"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./audit-event.module"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./audit-event.listener"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./handlers/console.handler"), exports);
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
2
2
|
import { Reflector } from '@nestjs/core';
|
|
3
3
|
import { Observable } from 'rxjs';
|
|
4
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
4
5
|
import { AuditService } from '../services/audit-log.service';
|
|
5
6
|
export declare class AuditInterceptor implements NestInterceptor {
|
|
6
7
|
private readonly auditService;
|
|
7
8
|
private readonly reflector;
|
|
8
|
-
|
|
9
|
+
private readonly eventEmitter?;
|
|
10
|
+
constructor(auditService: AuditService, reflector: Reflector, eventEmitter?: EventEmitter2 | undefined);
|
|
9
11
|
intercept(context: ExecutionContext, next: CallHandler<unknown>): Observable<unknown>;
|
|
12
|
+
private emitEvent;
|
|
10
13
|
private resolveEntityName;
|
|
11
14
|
}
|
|
12
15
|
//# sourceMappingURL=audit-log.interceptor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-log.interceptor.d.ts","sourceRoot":"","sources":["../../../src/lib/interceptors/audit-log.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,gBAAgB,EAChB,WAAW,
|
|
1
|
+
{"version":3,"file":"audit-log.interceptor.d.ts","sourceRoot":"","sources":["../../../src/lib/interceptors/audit-log.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,gBAAgB,EAChB,WAAW,EAEZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAA+B,MAAM,MAAM,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAU7D,qBACa,gBAAiB,YAAW,eAAe;IAEpD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS;IACd,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;gBAFzB,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,SAAS,EACR,YAAY,CAAC,EAAE,aAAa,YAAA;IAG3D,SAAS,CACP,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,GACzB,UAAU,CAAC,OAAO,CAAC;IAkFtB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,iBAAiB;CAwB1B"}
|
|
@@ -5,14 +5,16 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const common_1 = require("@nestjs/common");
|
|
6
6
|
const core_1 = require("@nestjs/core");
|
|
7
7
|
const rxjs_1 = require("rxjs");
|
|
8
|
+
const event_emitter_1 = require("@nestjs/event-emitter");
|
|
8
9
|
const audit_log_service_1 = require("../services/audit-log.service");
|
|
9
10
|
const audit_log_decorator_1 = require("../decorators/audit-log.decorator");
|
|
10
11
|
const CONTROLLER_ENTITY_NAME_KEY = 'entityName';
|
|
11
12
|
const ENTITY_ENTITY_NAME_KEY = 'custom:entityName';
|
|
12
13
|
let AuditInterceptor = class AuditInterceptor {
|
|
13
|
-
constructor(auditService, reflector) {
|
|
14
|
+
constructor(auditService, reflector, eventEmitter) {
|
|
14
15
|
this.auditService = auditService;
|
|
15
16
|
this.reflector = reflector;
|
|
17
|
+
this.eventEmitter = eventEmitter;
|
|
16
18
|
}
|
|
17
19
|
intercept(context, next) {
|
|
18
20
|
const handler = context.getHandler();
|
|
@@ -24,14 +26,23 @@ let AuditInterceptor = class AuditInterceptor {
|
|
|
24
26
|
const userId = request.user?.id;
|
|
25
27
|
const ip = request.ip;
|
|
26
28
|
const userAgent = request.headers?.['user-agent'];
|
|
29
|
+
const entityIdFromParams = request.params?.id;
|
|
27
30
|
let entityName = auditOptions.entity;
|
|
28
31
|
if (!entityName) {
|
|
29
32
|
entityName = this.resolveEntityName(context);
|
|
30
33
|
}
|
|
34
|
+
const basePayload = {
|
|
35
|
+
entity: entityName,
|
|
36
|
+
userId,
|
|
37
|
+
ip,
|
|
38
|
+
userAgent,
|
|
39
|
+
};
|
|
31
40
|
return next.handle().pipe((0, rxjs_1.tap)(async (result) => {
|
|
41
|
+
const entityId = result?.id ?? entityIdFromParams;
|
|
32
42
|
await this.auditService.log({
|
|
33
43
|
action: auditOptions.action,
|
|
34
44
|
entity: entityName,
|
|
45
|
+
entityId,
|
|
35
46
|
userId,
|
|
36
47
|
ip,
|
|
37
48
|
userAgent,
|
|
@@ -42,8 +53,39 @@ let AuditInterceptor = class AuditInterceptor {
|
|
|
42
53
|
response: result,
|
|
43
54
|
},
|
|
44
55
|
});
|
|
56
|
+
this.emitEvent({
|
|
57
|
+
...basePayload,
|
|
58
|
+
action: `crud.${entityName.toLowerCase()}.${auditOptions.action.toLowerCase()}.success`,
|
|
59
|
+
entityId,
|
|
60
|
+
timestamp: new Date(),
|
|
61
|
+
metadata: {
|
|
62
|
+
body: request.body,
|
|
63
|
+
params: request.params,
|
|
64
|
+
query: request.query,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}), (0, rxjs_1.catchError)((error) => {
|
|
68
|
+
const entityId = entityIdFromParams;
|
|
69
|
+
this.emitEvent({
|
|
70
|
+
...basePayload,
|
|
71
|
+
action: `crud.${entityName.toLowerCase()}.${auditOptions.action.toLowerCase()}.error`,
|
|
72
|
+
entityId,
|
|
73
|
+
timestamp: new Date(),
|
|
74
|
+
metadata: {
|
|
75
|
+
body: request.body,
|
|
76
|
+
params: request.params,
|
|
77
|
+
query: request.query,
|
|
78
|
+
error: { message: error.message, statusCode: error.status },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
return (0, rxjs_1.throwError)(() => error);
|
|
45
82
|
}));
|
|
46
83
|
}
|
|
84
|
+
emitEvent(event) {
|
|
85
|
+
if (!this.eventEmitter)
|
|
86
|
+
return;
|
|
87
|
+
this.eventEmitter.emit(event.action, event);
|
|
88
|
+
}
|
|
47
89
|
resolveEntityName(context) {
|
|
48
90
|
const controllerClass = context.getClass();
|
|
49
91
|
const controllerMeta = Reflect.getMetadata(CONTROLLER_ENTITY_NAME_KEY, controllerClass);
|
|
@@ -67,6 +109,8 @@ let AuditInterceptor = class AuditInterceptor {
|
|
|
67
109
|
exports.AuditInterceptor = AuditInterceptor;
|
|
68
110
|
exports.AuditInterceptor = AuditInterceptor = tslib_1.__decorate([
|
|
69
111
|
(0, common_1.Injectable)(),
|
|
112
|
+
tslib_1.__param(2, (0, common_1.Optional)()),
|
|
70
113
|
tslib_1.__metadata("design:paramtypes", [audit_log_service_1.AuditService,
|
|
71
|
-
core_1.Reflector
|
|
114
|
+
core_1.Reflector,
|
|
115
|
+
event_emitter_1.EventEmitter2])
|
|
72
116
|
], AuditInterceptor);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud-controller.test-suites.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/crud-controller.test-suites.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAMxC,OAAO,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGhF,wBAAgB,mBAAmB,CACjC,OAAO,SAAS,aAAa,EAC7B,UAAU,EACV,UAAU,EACV,YAAY,EAEZ,MAAM,EAAE,wBAAwB,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,GAC9E,eAAe,
|
|
1
|
+
{"version":3,"file":"crud-controller.test-suites.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/crud-controller.test-suites.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAMxC,OAAO,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGhF,wBAAgB,mBAAmB,CACjC,OAAO,SAAS,aAAa,EAC7B,UAAU,EACV,UAAU,EACV,YAAY,EAEZ,MAAM,EAAE,wBAAwB,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,GAC9E,eAAe,CAuNjB"}
|
|
@@ -29,6 +29,9 @@ function crudControllerTests(config) {
|
|
|
29
29
|
}
|
|
30
30
|
moduleRef = await testing_1.Test.createTestingModule({
|
|
31
31
|
controllers: [TestController],
|
|
32
|
+
providers: [
|
|
33
|
+
{ provide: 'AUTH_OPTIONS', useValue: config.authOptions ?? {} },
|
|
34
|
+
],
|
|
32
35
|
}).compile();
|
|
33
36
|
controller = moduleRef.get(TestController);
|
|
34
37
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud-service.test-suites.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/crud-service.test-suites.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtE,wBAAgB,gBAAgB,CAC9B,OAAO,SAAS,aAAa,EAC7B,UAAU,EACV,UAAU,EACV,YAAY,EACZ,MAAM,EAAE,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"crud-service.test-suites.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/crud-service.test-suites.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtE,wBAAgB,gBAAgB,CAC9B,OAAO,SAAS,aAAa,EAC7B,UAAU,EACV,UAAU,EACV,YAAY,EACZ,MAAM,EAAE,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,GAAG,eAAe,CA6VxF"}
|
|
@@ -69,6 +69,8 @@ function crudServiceTests(config) {
|
|
|
69
69
|
expect(lastQb.leftJoinAndSelect).toHaveBeenCalled();
|
|
70
70
|
});
|
|
71
71
|
it('should apply orderBy when specified', async () => {
|
|
72
|
+
if (config.allowedSortFields?.length && !config.allowedSortFields.includes('id'))
|
|
73
|
+
return;
|
|
72
74
|
lastQb = (0, mock_repository_1.createMockQb)();
|
|
73
75
|
lastQb.getManyAndCount.mockResolvedValue([[], 0]);
|
|
74
76
|
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
@@ -81,6 +83,8 @@ function crudServiceTests(config) {
|
|
|
81
83
|
expect(lastQb.orderBy).toHaveBeenCalledWith('e.id', 'ASC');
|
|
82
84
|
});
|
|
83
85
|
it('should default orderBy to DESC', async () => {
|
|
86
|
+
if (config.allowedSortFields?.length && !config.allowedSortFields.includes('id'))
|
|
87
|
+
return;
|
|
84
88
|
lastQb = (0, mock_repository_1.createMockQb)();
|
|
85
89
|
lastQb.getManyAndCount.mockResolvedValue([[], 0]);
|
|
86
90
|
repository.createQueryBuilder.mockReturnValue(lastQb);
|
|
@@ -29,6 +29,7 @@ export interface CrudControllerTestConfig<TEntity extends ObjectLiteral, TCreate
|
|
|
29
29
|
controllerFactory: () => Type<any>;
|
|
30
30
|
serviceClass: Type<any>;
|
|
31
31
|
permissions?: Record<CrudEndpoint, string | string[]>;
|
|
32
|
+
authOptions?: Record<string, unknown>;
|
|
32
33
|
}
|
|
33
34
|
export interface CrudTestContext {
|
|
34
35
|
module: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/testing.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE5D,MAAM,WAAW,cAAc,CAC7B,OAAO,SAAS,aAAa,EAC7B,UAAU,EACV,UAAU,EACV,YAAY;IAEZ,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,SAAS,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACjC,cAAc,CAAC,EAAE,SAAS,CAAC,MAAM,OAAO,CAAC,EAAE,CAAC;IAC5C,iBAAiB,CAAC,EAAE,SAAS,CAAC,MAAM,OAAO,CAAC,EAAE,CAAC;IAC/C,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC;IACnC,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IAC3D,iBAAiB,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC5C,KAAK,CAAC,EAAE,kBAAkB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3D,aAAa,CAAC,EAAE,CACd,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,KACxB,YAAY,GAAG,YAAY,EAAE,CAAC;IACnC,SAAS,CAAC,EAAE,kBAAkB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC;IACnE,IAAI,EAAE;QACJ,aAAa,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QACnC,aAAa,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QACnC,UAAU,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9B,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC;QACzB,iBAAiB,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;KAC/D,CAAC;CACH;AAED,MAAM,WAAW,wBAAwB,CACvC,OAAO,SAAS,aAAa,EAC7B,UAAU,EACV,UAAU,EACV,YAAY,CACZ,SAAQ,IAAI,CACV,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,EAC7D,cAAc,CACf;IACD,iBAAiB,EAAE,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"testing.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/testing.interface.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nest-util/nest-crud",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"@nest-util/nest-auth": "workspace:*",
|
|
35
35
|
"@nestjs/common": "^11.0.0",
|
|
36
36
|
"@nestjs/core": "^11.0.0",
|
|
37
|
+
"@nestjs/event-emitter": "^3.0.0",
|
|
37
38
|
"@nestjs/swagger": "^11.2.6",
|
|
38
39
|
"@nestjs/typeorm": "^11.0.1",
|
|
39
40
|
"class-transformer": "^0.5.1",
|
|
@@ -45,6 +46,9 @@
|
|
|
45
46
|
"peerDependenciesMeta": {
|
|
46
47
|
"@nest-util/nest-auth": {
|
|
47
48
|
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"@nestjs/event-emitter": {
|
|
51
|
+
"optional": true
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
}
|