@nest-util/nest-crud 1.0.6 → 1.0.9

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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/lib/controllers/nest-crud.controller.d.ts +4 -3
  6. package/dist/lib/controllers/nest-crud.controller.d.ts.map +1 -1
  7. package/dist/lib/controllers/nest-crud.controller.js +12 -9
  8. package/dist/lib/events/audit-event.interface.d.ts +32 -0
  9. package/dist/lib/events/audit-event.interface.d.ts.map +1 -0
  10. package/dist/lib/events/audit-event.interface.js +2 -0
  11. package/dist/lib/events/audit-event.listener.d.ts +8 -0
  12. package/dist/lib/events/audit-event.listener.d.ts.map +1 -0
  13. package/dist/lib/events/audit-event.listener.js +58 -0
  14. package/dist/lib/events/audit-event.module.d.ts +6 -0
  15. package/dist/lib/events/audit-event.module.d.ts.map +1 -0
  16. package/dist/lib/events/audit-event.module.js +44 -0
  17. package/dist/lib/events/handlers/console.handler.d.ts +5 -0
  18. package/dist/lib/events/handlers/console.handler.d.ts.map +1 -0
  19. package/dist/lib/events/handlers/console.handler.js +43 -0
  20. package/dist/lib/events/index.d.ts +5 -0
  21. package/dist/lib/events/index.d.ts.map +1 -0
  22. package/dist/lib/events/index.js +7 -0
  23. package/dist/lib/interceptors/audit-log.interceptor.d.ts +4 -1
  24. package/dist/lib/interceptors/audit-log.interceptor.d.ts.map +1 -1
  25. package/dist/lib/interceptors/audit-log.interceptor.js +46 -2
  26. package/dist/lib/interfaces/crud.interface.d.ts +4 -3
  27. package/dist/lib/interfaces/crud.interface.d.ts.map +1 -1
  28. package/dist/lib/interfaces/find-mine.interface.d.ts +26 -0
  29. package/dist/lib/interfaces/find-mine.interface.d.ts.map +1 -1
  30. package/dist/lib/services/nest-crud.service.d.ts +13 -4
  31. package/dist/lib/services/nest-crud.service.d.ts.map +1 -1
  32. package/dist/lib/services/nest-crud.service.js +100 -65
  33. package/dist/lib/testing/crud-controller.test-suites.js +3 -3
  34. 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.2 @nest-util/nest-auth@^1.0.2 typeorm@^1.1.0 @nestjs/typeorm @nestjs/swagger @nestjs/jwt @nestjs/passport class-validator class-transformer bcrypt
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
@@ -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);
@@ -3,6 +3,7 @@ import { CrudEndpoint, CrudInterface } from '../interfaces/crud.interface';
3
3
  import { PaginationDto } from '../dtos/pagination.dto';
4
4
  import { FilterDto } from '../dtos/filter.dto';
5
5
  import { ListAuditLogsDto } from '../dtos/list-audit-logs.dto';
6
+ import type { OwnershipUser } from '../interfaces/find-mine.interface';
6
7
  export declare const AUTH_PERMISSIONS_METADATA_KEY = "auth:permissions";
7
8
  export type CrudEndpointPermissions = string | readonly string[];
8
9
  export type CrudPermissionsMap = Partial<Record<CrudEndpoint, CrudEndpointPermissions>>;
@@ -16,10 +17,10 @@ export interface IBaseController<CD, UD, RD> {
16
17
  data: RD[];
17
18
  meta?: unknown;
18
19
  } | RD[]>;
19
- findOne(id: number): Promise<RD>;
20
+ findOne(id: number, user?: OwnershipUser): Promise<RD>;
20
21
  create(dto: CD): Promise<RD>;
21
- update(id: number, dto: UD): Promise<RD>;
22
- remove(id: number): Promise<boolean>;
22
+ update(id: number, dto: UD, user?: OwnershipUser): Promise<RD>;
23
+ remove(id: number, user?: OwnershipUser): Promise<boolean>;
23
24
  findAuditLogs?(query: ListAuditLogsDto): Promise<unknown>;
24
25
  }
25
26
  export declare function CreateNestedCrudController<CD, UD, RD>(createDto: Type<CD>, updateDto: Type<UD>, responseDto: Type<RD>, options?: CrudControllerFactoryOptions): Type<IBaseController<CD, UD, RD>>;
@@ -1 +1 @@
1
- {"version":3,"file":"nest-crud.controller.d.ts","sourceRoot":"","sources":["../../../src/lib/controllers/nest-crud.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,IAAI,EACL,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAG/D,eAAO,MAAM,6BAA6B,qBAAqB,CAAC;AAEhE,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;AAEjE,MAAM,MAAM,kBAAkB,GAAG,OAAO,CACtC,MAAM,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAC9C,CAAC;AAEF,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;IACzC,OAAO,EAAE,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IACnC,OAAO,CACL,KAAK,EAAE,aAAa,GAAG,SAAS,GAC/B,OAAO,CAAC;QAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,aAAa,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAyDD,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EACnD,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,EACnB,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,EACnB,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC,EACrB,OAAO,CAAC,EAAE,4BAA4B,GACrC,IAAI,CAAC,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CA8HnC"}
1
+ {"version":3,"file":"nest-crud.controller.d.ts","sourceRoot":"","sources":["../../../src/lib/controllers/nest-crud.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,IAAI,EACL,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAEvE,eAAO,MAAM,6BAA6B,qBAAqB,CAAC;AAEhE,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;AAEjE,MAAM,MAAM,kBAAkB,GAAG,OAAO,CACtC,MAAM,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAC9C,CAAC;AAEF,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;IACzC,OAAO,EAAE,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IACnC,OAAO,CACL,KAAK,EAAE,aAAa,GAAG,SAAS,GAC/B,OAAO,CAAC;QAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;IACvD,MAAM,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/D,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,aAAa,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAyDD,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EACnD,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,EACnB,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,EACnB,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC,EACrB,OAAO,CAAC,EAAE,4BAA4B,GACrC,IAAI,CAAC,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAwInC"}
@@ -72,13 +72,13 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
72
72
  this.ensureEndpointEnabled('create');
73
73
  return this.service.create(dto);
74
74
  }
75
- update(id, dto) {
75
+ update(id, dto, user) {
76
76
  this.ensureEndpointEnabled('update');
77
- return this.service.update(id, dto);
77
+ return this.service.update(id, dto, user);
78
78
  }
79
- remove(id) {
79
+ remove(id, user) {
80
80
  this.ensureEndpointEnabled('remove');
81
- return this.service.remove(id);
81
+ return this.service.remove(id, user);
82
82
  }
83
83
  findAuditLogs(query) {
84
84
  this.ensureEndpointEnabled('findAuditLogs');
@@ -87,9 +87,9 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
87
87
  }
88
88
  return this.service.findAuditLogs(query);
89
89
  }
90
- findOne(id) {
90
+ findOne(id, user) {
91
91
  this.ensureEndpointEnabled('findOne');
92
- return this.service.findOne(id);
92
+ return this.service.findOne(id, user);
93
93
  }
94
94
  }
95
95
  tslib_1.__decorate([
@@ -160,8 +160,9 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
160
160
  (0, swagger_1.ApiResponse)({ type: responseDto }),
161
161
  tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
162
162
  tslib_1.__param(1, (0, common_1.Body)()),
163
+ tslib_1.__param(2, (0, nest_auth_1.CurrentUser)()),
163
164
  tslib_1.__metadata("design:type", Function),
164
- tslib_1.__metadata("design:paramtypes", [Number, Object]),
165
+ tslib_1.__metadata("design:paramtypes", [Number, Object, Object]),
165
166
  tslib_1.__metadata("design:returntype", void 0)
166
167
  ], BaseController.prototype, "update", null);
167
168
  tslib_1.__decorate([
@@ -169,8 +170,9 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
169
170
  (0, response_message_decorator_1.Message)('deleted'),
170
171
  (0, audit_log_decorator_1.Audit)({ action: 'DELETE' }),
171
172
  tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
173
+ tslib_1.__param(1, (0, nest_auth_1.CurrentUser)()),
172
174
  tslib_1.__metadata("design:type", Function),
173
- tslib_1.__metadata("design:paramtypes", [Number]),
175
+ tslib_1.__metadata("design:paramtypes", [Number, Object]),
174
176
  tslib_1.__metadata("design:returntype", void 0)
175
177
  ], BaseController.prototype, "remove", null);
176
178
  tslib_1.__decorate([
@@ -186,8 +188,9 @@ function CreateNestedCrudController(createDto, updateDto, responseDto, options)
186
188
  (0, response_message_decorator_1.Message)('fetched'),
187
189
  (0, swagger_1.ApiResponse)({ type: responseDto }),
188
190
  tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
191
+ tslib_1.__param(1, (0, nest_auth_1.CurrentUser)()),
189
192
  tslib_1.__metadata("design:type", Function),
190
- tslib_1.__metadata("design:paramtypes", [Number]),
193
+ tslib_1.__metadata("design:paramtypes", [Number, Object]),
191
194
  tslib_1.__metadata("design:returntype", void 0)
192
195
  ], BaseController.prototype, "findOne", null);
193
196
  applyPermissionMetadata(BaseController, options?.permissions);
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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,5 @@
1
+ import { AuditEvent, AuditEventHandler } from '../audit-event.interface';
2
+ export declare class ConsoleHandler implements AuditEventHandler {
3
+ handle(event: AuditEvent): void;
4
+ }
5
+ //# sourceMappingURL=console.handler.d.ts.map
@@ -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,5 @@
1
+ export * from './audit-event.interface';
2
+ export * from './audit-event.module';
3
+ export * from './audit-event.listener';
4
+ export * from './handlers/console.handler';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -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
- constructor(auditService: AuditService, reflector: Reflector);
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,EACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAAO,MAAM,MAAM,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAS7D,qBACa,gBAAiB,YAAW,eAAe;IAEpD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,SAAS;IAGvC,SAAS,CACP,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,GACzB,UAAU,CAAC,OAAO,CAAC;IAyCtB,OAAO,CAAC,iBAAiB;CAwB1B"}
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,6 +1,7 @@
1
1
  import { FilterDto } from '../dtos/filter.dto';
2
2
  import { PaginationDto } from '../dtos/pagination.dto';
3
3
  import { CursorPaginationDto } from '../dtos/cursor-pagination.dto';
4
+ import { OwnershipUser } from './find-mine.interface';
4
5
  export type CrudEndpoint = 'findAll' | 'findOne' | 'create' | 'update' | 'remove' | 'findAuditLogs' | 'findMine';
5
6
  export interface AuditLogQuery {
6
7
  user_id?: string;
@@ -25,10 +26,10 @@ export interface CrudInterface<CreateDto, UpdateDto, ResponseDto> {
25
26
  meta?: unknown;
26
27
  }>;
27
28
  findAllWithCursor?(query: CursorPaginationDto & FilterDto): Promise<CursorPaginationResult<ResponseDto>>;
28
- findOne(id: number): Promise<ResponseDto>;
29
+ findOne(id: number, user?: OwnershipUser): Promise<ResponseDto>;
29
30
  create(dto: CreateDto): Promise<ResponseDto>;
30
- update(id: number, dto: UpdateDto): Promise<ResponseDto>;
31
- remove(id: number): Promise<boolean>;
31
+ update(id: number, dto: UpdateDto, user?: OwnershipUser): Promise<ResponseDto>;
32
+ remove(id: number, user?: OwnershipUser): Promise<boolean>;
32
33
  findAuditLogs?(query: AuditLogQuery): Promise<unknown>;
33
34
  findMine?(userId: string | number, query: PaginationDto & FilterDto): Promise<{
34
35
  data: ResponseDto[];
@@ -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;AAEpE,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,eAAe,GACf,UAAU,CAAC;AAEf,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,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAE1C,MAAM,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAE7C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAEzD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAErC,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;CACrD"}
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;AAEtD,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,eAAe,GACf,UAAU,CAAC;AAEf,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,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAE7C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAE/E,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;CACrD"}
@@ -1,6 +1,32 @@
1
1
  import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
2
+ /**
3
+ * Minimal structural view of the authenticated user passed to ownership
4
+ * enforcement. Compatible with `AuthUser` from `@nest-util/nest-auth`.
5
+ */
6
+ export interface OwnershipUser {
7
+ id?: string | number;
8
+ permissions?: readonly string[];
9
+ [key: string]: unknown;
10
+ }
2
11
  export interface FindMineConfig<TEntity extends ObjectLiteral> {
3
12
  userOwnershipField?: keyof TEntity;
4
13
  findMineQuery?: (qb: SelectQueryBuilder<TEntity>, userId: string | number) => void;
14
+ /**
15
+ * When true (and an ownership field/query is configured), the generic
16
+ * `findOne`, `update`, and `remove` operations are scoped to records owned
17
+ * by the passed-in user. Non-owned records resolve to 404; missing user
18
+ * resolves to 403. Defaults to false - existing behavior is preserved.
19
+ */
20
+ enforceOwnership?: boolean;
21
+ /**
22
+ * Permission strings that grant full access and bypass ownership checks.
23
+ * Example: ['admin.access'].
24
+ */
25
+ ownershipBypassPermissions?: readonly string[];
26
+ /**
27
+ * Custom predicate that grants full access and bypasses ownership checks.
28
+ * Takes precedence alongside ownershipBypassPermissions.
29
+ */
30
+ ownershipBypass?: (user: OwnershipUser) => boolean;
5
31
  }
6
32
  //# sourceMappingURL=find-mine.interface.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"find-mine.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/interfaces/find-mine.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE5D,MAAM,WAAW,cAAc,CAAC,OAAO,SAAS,aAAa;IAC3D,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC;IACnC,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;CACpF"}
1
+ {"version":3,"file":"find-mine.interface.d.ts","sourceRoot":"","sources":["../../../src/lib/interfaces/find-mine.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE5D;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc,CAAC,OAAO,SAAS,aAAa;IAC3D,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC;IACnC,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IAEnF;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;OAGG;IACH,0BAA0B,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAE/C;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC;CACpD"}
@@ -7,7 +7,7 @@ import { FilterDto } from '../dtos/filter.dto';
7
7
  import { CrudEndpoint, CrudInterface, CursorPaginationResult } from '../interfaces/crud.interface';
8
8
  import { CursorStrategy } from '../interfaces/cursor-strategy.interface';
9
9
  import { CrudHooks, TransactionConfig } from '../interfaces/hooks.interface';
10
- import { FindMineConfig } from '../interfaces/find-mine.interface';
10
+ import { FindMineConfig, OwnershipUser } from '../interfaces/find-mine.interface';
11
11
  export interface CrudServiceOptions<Entity extends ObjectLiteral, ResponseDto> extends FindMineConfig<Entity> {
12
12
  repository: Repository<Entity>;
13
13
  allowedFilters?: readonly (keyof Entity)[];
@@ -45,11 +45,20 @@ export declare class NestCrudService<Entity extends ObjectLiteral, CreateDto = P
45
45
  protected readonly transactionConfig: TransactionConfig;
46
46
  protected readonly userOwnershipField?: keyof Entity;
47
47
  protected readonly findMineQuery?: (qb: SelectQueryBuilder<Entity>, userId: string | number) => void;
48
+ protected readonly enforceOwnership: boolean;
49
+ protected readonly ownershipBypassPermissions: readonly string[];
50
+ protected readonly ownershipBypass?: (user: OwnershipUser) => boolean;
48
51
  constructor(options: CrudServiceOptions<Entity, ResponseDto>);
49
52
  private resolveRelations;
50
53
  private executeInTransaction;
51
54
  private executeHook;
52
55
  private buildRelationsObject;
56
+ private applyIncludeJoins;
57
+ private isOwnershipConfigured;
58
+ private canBypassOwnership;
59
+ private enforceOwnershipFor;
60
+ private applyOwnershipCondition;
61
+ private findOwnedEntity;
53
62
  findAll(query: PaginationDto & FilterDto): Promise<{
54
63
  data: ResponseDto[];
55
64
  meta: {
@@ -66,10 +75,10 @@ export declare class NestCrudService<Entity extends ObjectLiteral, CreateDto = P
66
75
  meta?: unknown;
67
76
  }>;
68
77
  findAllWithCursor(query: CursorPaginationDto & FilterDto): Promise<CursorPaginationResult<ResponseDto>>;
69
- findOne(id: number): Promise<ResponseDto>;
78
+ findOne(id: number, user?: OwnershipUser): Promise<ResponseDto>;
70
79
  create(payload: CreateDto): Promise<ResponseDto>;
71
- update(id: number, payload: UpdateDto): Promise<ResponseDto>;
72
- remove(id: number): Promise<boolean>;
80
+ update(id: number, payload: UpdateDto, user?: OwnershipUser): Promise<ResponseDto>;
81
+ remove(id: number, user?: OwnershipUser): Promise<boolean>;
73
82
  findAuditLogs(query: {
74
83
  user_id?: string;
75
84
  start_date?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"nest-crud.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/nest-crud.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"nest-crud.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/nest-crud.service.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,9 @@ let NestCrudService = class NestCrudService {
24
24
  this.transactionConfig = options.transactionConfig ?? {};
25
25
  this.userOwnershipField = options.userOwnershipField;
26
26
  this.findMineQuery = options.findMineQuery;
27
+ this.enforceOwnership = options.enforceOwnership ?? false;
28
+ this.ownershipBypassPermissions = options.ownershipBypassPermissions ?? [];
29
+ this.ownershipBypass = options.ownershipBypass;
27
30
  }
28
31
  async resolveRelations(payload) {
29
32
  if (!this.relations.length)
@@ -92,22 +95,67 @@ let NestCrudService = class NestCrudService {
92
95
  }
93
96
  return result;
94
97
  }
98
+ applyIncludeJoins(qb) {
99
+ if (this.include.length === 0)
100
+ return;
101
+ this.include.forEach((relation) => {
102
+ const parts = relation.split('.');
103
+ if (parts.length === 1) {
104
+ qb.leftJoinAndSelect(`e.${parts[0]}`, parts[0]);
105
+ }
106
+ else {
107
+ const parentAlias = parts.slice(0, -1).join('_');
108
+ const field = parts[parts.length - 1];
109
+ const alias = parts.join('_');
110
+ qb.leftJoinAndSelect(`${parentAlias}.${field}`, alias);
111
+ }
112
+ });
113
+ }
114
+ isOwnershipConfigured() {
115
+ return Boolean(this.userOwnershipField || this.findMineQuery);
116
+ }
117
+ canBypassOwnership(user) {
118
+ if (!user)
119
+ return false;
120
+ if (this.ownershipBypass && this.ownershipBypass(user)) {
121
+ return true;
122
+ }
123
+ if (this.ownershipBypassPermissions.length > 0) {
124
+ const userPermissions = Array.isArray(user.permissions)
125
+ ? user.permissions
126
+ : [];
127
+ return this.ownershipBypassPermissions.some((permission) => userPermissions.includes(permission));
128
+ }
129
+ return false;
130
+ }
131
+ enforceOwnershipFor(user) {
132
+ return Boolean(this.enforceOwnership &&
133
+ this.isOwnershipConfigured() &&
134
+ !this.canBypassOwnership(user));
135
+ }
136
+ applyOwnershipCondition(qb, userId) {
137
+ if (this.findMineQuery) {
138
+ this.findMineQuery(qb, userId);
139
+ }
140
+ else if (this.userOwnershipField) {
141
+ qb.where(`e.${String(this.userOwnershipField)} = :userId`, { userId });
142
+ }
143
+ }
144
+ async findOwnedEntity(id, user) {
145
+ const userId = user?.id;
146
+ if (userId === undefined || userId === null) {
147
+ throw new common_1.ForbiddenException('Authentication required to access this resource');
148
+ }
149
+ const qb = this.repo.createQueryBuilder('e');
150
+ this.applyOwnershipCondition(qb, userId);
151
+ qb.andWhere('e.id = :id', { id });
152
+ this.applyIncludeJoins(qb);
153
+ const rows = await qb.getMany();
154
+ return rows[0] ?? null;
155
+ }
95
156
  async findAll(query) {
96
157
  const qb = this.repo.createQueryBuilder('e');
97
- if (this.include.length > 0) {
98
- this.include.forEach((relation) => {
99
- const parts = relation.split('.');
100
- if (parts.length === 1) {
101
- qb.leftJoinAndSelect(`e.${parts[0]}`, parts[0]);
102
- }
103
- else {
104
- const parentAlias = parts.slice(0, -1).join('_');
105
- const field = parts[parts.length - 1];
106
- const alias = parts.join('_');
107
- qb.leftJoinAndSelect(`${parentAlias}.${field}`, alias);
108
- }
109
- });
110
- }
158
+ this.applyIncludeJoins(qb);
111
159
  (0, filter_helper_1.applyFilters)(qb, query.filter, this.allowedFilters);
112
160
  const paginationMeta = (0, pagination_helper_1.applyPagination)(qb, query);
113
161
  if (query.orderBy) {
@@ -130,26 +178,8 @@ let NestCrudService = class NestCrudService {
130
178
  throw new common_1.BadRequestException('findMine not configured');
131
179
  }
132
180
  const qb = this.repo.createQueryBuilder('e');
133
- if (this.findMineQuery) {
134
- this.findMineQuery(qb, userId);
135
- }
136
- else if (this.userOwnershipField) {
137
- qb.where(`e.${String(this.userOwnershipField)} = :userId`, { userId });
138
- }
139
- if (this.include.length > 0) {
140
- this.include.forEach((relation) => {
141
- const parts = relation.split('.');
142
- if (parts.length === 1) {
143
- qb.leftJoinAndSelect(`e.${parts[0]}`, parts[0]);
144
- }
145
- else {
146
- const parentAlias = parts.slice(0, -1).join('_');
147
- const field = parts[parts.length - 1];
148
- const alias = parts.join('_');
149
- qb.leftJoinAndSelect(`${parentAlias}.${field}`, alias);
150
- }
151
- });
152
- }
181
+ this.applyOwnershipCondition(qb, userId);
182
+ this.applyIncludeJoins(qb);
153
183
  (0, filter_helper_1.applyFilters)(qb, query.filter, this.allowedFilters);
154
184
  const paginationMeta = (0, pagination_helper_1.applyPagination)(qb, query);
155
185
  if (query.orderBy) {
@@ -173,20 +203,7 @@ let NestCrudService = class NestCrudService {
173
203
  const orderDirection = 'DESC';
174
204
  const qb = this.repo.createQueryBuilder('e');
175
205
  // Join relations
176
- if (this.include.length > 0) {
177
- this.include.forEach((relation) => {
178
- const parts = relation.split('.');
179
- if (parts.length === 1) {
180
- qb.leftJoinAndSelect(`e.${parts[0]}`, parts[0]);
181
- }
182
- else {
183
- const parentAlias = parts.slice(0, -1).join('_');
184
- const field = parts[parts.length - 1];
185
- const alias = parts.join('_');
186
- qb.leftJoinAndSelect(`${parentAlias}.${field}`, alias);
187
- }
188
- });
189
- }
206
+ this.applyIncludeJoins(qb);
190
207
  // Apply filters
191
208
  (0, filter_helper_1.applyFilters)(qb, query.filter, this.allowedFilters);
192
209
  // Apply cursor filter
@@ -232,15 +249,21 @@ let NestCrudService = class NestCrudService {
232
249
  },
233
250
  };
234
251
  }
235
- async findOne(id) {
252
+ async findOne(id, user) {
236
253
  await this.executeHook(this.hooks.beforeFindOne, { id });
237
- const relationsObj = this.include.length > 0
238
- ? this.buildRelationsObject(this.include)
239
- : undefined;
240
- const entity = await this.repo.findOne({
241
- where: { id },
242
- relations: relationsObj,
243
- });
254
+ let entity;
255
+ if (this.enforceOwnershipFor(user)) {
256
+ entity = await this.findOwnedEntity(id, user);
257
+ }
258
+ else {
259
+ const relationsObj = this.include.length > 0
260
+ ? this.buildRelationsObject(this.include)
261
+ : undefined;
262
+ entity = await this.repo.findOne({
263
+ where: { id },
264
+ relations: relationsObj,
265
+ });
266
+ }
244
267
  if (!entity) {
245
268
  throw new common_1.NotFoundException('Resource not found');
246
269
  }
@@ -261,10 +284,16 @@ let NestCrudService = class NestCrudService {
261
284
  await this.executeHook(this.hooks.afterCreate, { entity, payload: payloadSnapshot });
262
285
  return result;
263
286
  }
264
- async update(id, payload) {
265
- const existing = await this.repo.findOneBy({
266
- id,
267
- });
287
+ async update(id, payload, user) {
288
+ let existing;
289
+ if (this.enforceOwnershipFor(user)) {
290
+ existing = await this.findOwnedEntity(id, user);
291
+ }
292
+ else {
293
+ existing = await this.repo.findOneBy({
294
+ id,
295
+ });
296
+ }
268
297
  if (!existing) {
269
298
  throw new common_1.NotFoundException('Resource not found');
270
299
  }
@@ -273,14 +302,20 @@ let NestCrudService = class NestCrudService {
273
302
  const resolved = await this.resolveRelations(payload);
274
303
  this.repo.merge(existing, resolved);
275
304
  await this.repo.save(existing);
276
- const result = await this.findOne(id);
305
+ const result = await this.findOne(id, user);
277
306
  await this.executeHook(this.hooks.afterUpdate, { entity: result, payload: payloadSnapshot, id });
278
307
  return result;
279
308
  }
280
- async remove(id) {
281
- const existing = await this.repo.findOneBy({
282
- id,
283
- });
309
+ async remove(id, user) {
310
+ let existing;
311
+ if (this.enforceOwnershipFor(user)) {
312
+ existing = await this.findOwnedEntity(id, user);
313
+ }
314
+ else {
315
+ existing = await this.repo.findOneBy({
316
+ id,
317
+ });
318
+ }
284
319
  if (!existing) {
285
320
  throw new common_1.NotFoundException('Resource not found');
286
321
  }
@@ -71,7 +71,7 @@ function crudControllerTests(config) {
71
71
  const expectedResult = { id: 1, name: 'Test' };
72
72
  service.findOne.mockResolvedValue(expectedResult);
73
73
  const result = await controller.findOne(1);
74
- expect(service.findOne).toHaveBeenCalledWith(1);
74
+ expect(service.findOne).toHaveBeenCalledWith(1, undefined);
75
75
  expect(result).toEqual(expectedResult);
76
76
  });
77
77
  });
@@ -91,7 +91,7 @@ function crudControllerTests(config) {
91
91
  const expectedResult = { id: 1 };
92
92
  service.update.mockResolvedValue(expectedResult);
93
93
  const result = await controller.update(1, dto);
94
- expect(service.update).toHaveBeenCalledWith(1, dto);
94
+ expect(service.update).toHaveBeenCalledWith(1, dto, undefined);
95
95
  expect(result).toEqual(expectedResult);
96
96
  });
97
97
  });
@@ -99,7 +99,7 @@ function crudControllerTests(config) {
99
99
  it('should call service.remove', async () => {
100
100
  service.remove.mockResolvedValue(true);
101
101
  const result = await controller.remove(1);
102
- expect(service.remove).toHaveBeenCalledWith(1);
102
+ expect(service.remove).toHaveBeenCalledWith(1, undefined);
103
103
  expect(result).toBe(true);
104
104
  });
105
105
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nest-util/nest-crud",
3
- "version": "1.0.6",
3
+ "version": "1.0.9",
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
  }