@nest-util/nest-crud 0.0.3

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 (38) hide show
  1. package/README.md +22 -0
  2. package/dist/index.d.ts +11 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +16 -0
  5. package/dist/lib/controllers/nest-crud.controller.d.ts +25 -0
  6. package/dist/lib/controllers/nest-crud.controller.d.ts.map +1 -0
  7. package/dist/lib/controllers/nest-crud.controller.js +159 -0
  8. package/dist/lib/decorators/response-message.decorator.d.ts +9 -0
  9. package/dist/lib/decorators/response-message.decorator.d.ts.map +1 -0
  10. package/dist/lib/decorators/response-message.decorator.js +10 -0
  11. package/dist/lib/dtos/filter.dto.d.ts +4 -0
  12. package/dist/lib/dtos/filter.dto.d.ts.map +1 -0
  13. package/dist/lib/dtos/filter.dto.js +30 -0
  14. package/dist/lib/dtos/pagination.dto.d.ts +7 -0
  15. package/dist/lib/dtos/pagination.dto.d.ts.map +1 -0
  16. package/dist/lib/dtos/pagination.dto.js +54 -0
  17. package/dist/lib/helpers/exception-filter.helper.d.ts +6 -0
  18. package/dist/lib/helpers/exception-filter.helper.d.ts.map +1 -0
  19. package/dist/lib/helpers/exception-filter.helper.js +35 -0
  20. package/dist/lib/helpers/filter.helper.d.ts +3 -0
  21. package/dist/lib/helpers/filter.helper.d.ts.map +1 -0
  22. package/dist/lib/helpers/filter.helper.js +148 -0
  23. package/dist/lib/helpers/pagination.helper.d.ts +7 -0
  24. package/dist/lib/helpers/pagination.helper.d.ts.map +1 -0
  25. package/dist/lib/helpers/pagination.helper.js +15 -0
  26. package/dist/lib/interceptors/response.interceptor.d.ts +9 -0
  27. package/dist/lib/interceptors/response.interceptor.d.ts.map +1 -0
  28. package/dist/lib/interceptors/response.interceptor.js +39 -0
  29. package/dist/lib/interfaces/crud.interface.d.ts +23 -0
  30. package/dist/lib/interfaces/crud.interface.d.ts.map +1 -0
  31. package/dist/lib/interfaces/crud.interface.js +2 -0
  32. package/dist/lib/nest-crud.module.d.ts +3 -0
  33. package/dist/lib/nest-crud.module.d.ts.map +1 -0
  34. package/dist/lib/nest-crud.module.js +15 -0
  35. package/dist/lib/services/nest-crud.service.d.ts +69 -0
  36. package/dist/lib/services/nest-crud.service.d.ts.map +1 -0
  37. package/dist/lib/services/nest-crud.service.js +154 -0
  38. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # nest-crud
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Installation
6
+
7
+ To install this library in your project, use the permanent URL from the latest GitHub Release:
8
+
9
+ ```bash
10
+ pnpm add https://github.com/nigussolomon/nest-util/releases/download/latest/nest-util-nest-crud-0.0.1.tgz
11
+ ```
12
+
13
+ > [!TIP]
14
+ > This tarball contains only the compiled code and its specific dependencies, making it as clean as a standard npm package.
15
+
16
+ ## Building
17
+
18
+ Run `nx build nest-crud` to build the library.
19
+
20
+ ## Running unit tests
21
+
22
+ Run `nx test nest-crud` to execute the unit tests via [Jest](https://jestjs.io).
@@ -0,0 +1,11 @@
1
+ export * from './lib/nest-crud.module';
2
+ export * from './lib/services/nest-crud.service';
3
+ export * from './lib/controllers/nest-crud.controller';
4
+ export * from './lib/dtos/filter.dto';
5
+ export * from './lib/dtos/pagination.dto';
6
+ export * from './lib/decorators/response-message.decorator';
7
+ export * from './lib/interceptors/response.interceptor';
8
+ export * from './lib/interfaces/crud.interface';
9
+ export * from './lib/helpers/exception-filter.helper';
10
+ export { Audit, AuditLogEntity } from '@nest-util/nest-audit';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,6CAA6C,CAAC;AAC5D,cAAc,yCAAyC,CAAC;AACxD,cAAc,iCAAiC,CAAC;AAChD,cAAc,uCAAuC,CAAC;AACtD,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuditLogEntity = exports.Audit = void 0;
4
+ const tslib_1 = require("tslib");
5
+ tslib_1.__exportStar(require("./lib/nest-crud.module"), exports);
6
+ tslib_1.__exportStar(require("./lib/services/nest-crud.service"), exports);
7
+ tslib_1.__exportStar(require("./lib/controllers/nest-crud.controller"), exports);
8
+ tslib_1.__exportStar(require("./lib/dtos/filter.dto"), exports);
9
+ tslib_1.__exportStar(require("./lib/dtos/pagination.dto"), exports);
10
+ tslib_1.__exportStar(require("./lib/decorators/response-message.decorator"), exports);
11
+ tslib_1.__exportStar(require("./lib/interceptors/response.interceptor"), exports);
12
+ tslib_1.__exportStar(require("./lib/interfaces/crud.interface"), exports);
13
+ tslib_1.__exportStar(require("./lib/helpers/exception-filter.helper"), exports);
14
+ var nest_audit_1 = require("@nest-util/nest-audit");
15
+ Object.defineProperty(exports, "Audit", { enumerable: true, get: function () { return nest_audit_1.Audit; } });
16
+ Object.defineProperty(exports, "AuditLogEntity", { enumerable: true, get: function () { return nest_audit_1.AuditLogEntity; } });
@@ -0,0 +1,25 @@
1
+ import { Type } from '@nestjs/common';
2
+ import { CrudEndpoint, CrudInterface } from '../interfaces/crud.interface';
3
+ import { PaginationDto } from '../dtos/pagination.dto';
4
+ import { FilterDto } from '../dtos/filter.dto';
5
+ import { ListAuditLogsDto } from '@nest-util/nest-audit';
6
+ export declare const AUTH_PERMISSIONS_METADATA_KEY = "auth:permissions";
7
+ export type CrudEndpointPermissions = string | readonly string[];
8
+ export type CrudPermissionsMap = Partial<Record<CrudEndpoint, CrudEndpointPermissions>>;
9
+ export interface CrudControllerFactoryOptions {
10
+ permissions?: CrudPermissionsMap;
11
+ }
12
+ export interface IBaseController<CD, UD, RD> {
13
+ service: CrudInterface<CD, UD, RD>;
14
+ findAll(query: PaginationDto & FilterDto): Promise<{
15
+ data: RD[];
16
+ meta?: unknown;
17
+ } | RD[]>;
18
+ findOne(id: number): Promise<RD>;
19
+ create(dto: CD): Promise<RD>;
20
+ update(id: number, dto: UD): Promise<RD>;
21
+ remove(id: number): Promise<boolean>;
22
+ findAuditLogs?(query: ListAuditLogsDto): Promise<unknown>;
23
+ }
24
+ export declare function CreateNestedCrudController<CD, UD, RD>(createDto: Type<CD>, updateDto: Type<UD>, responseDto: Type<RD>, options?: CrudControllerFactoryOptions): Type<IBaseController<CD, UD, RD>>;
25
+ //# sourceMappingURL=nest-crud.controller.d.ts.map
@@ -0,0 +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;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAS,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,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;CAClC;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,CA2FnC"}
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AUTH_PERMISSIONS_METADATA_KEY = void 0;
4
+ exports.CreateNestedCrudController = CreateNestedCrudController;
5
+ const tslib_1 = require("tslib");
6
+ const common_1 = require("@nestjs/common");
7
+ const swagger_1 = require("@nestjs/swagger");
8
+ const response_message_decorator_1 = require("../decorators/response-message.decorator");
9
+ const nest_audit_1 = require("@nest-util/nest-audit");
10
+ exports.AUTH_PERMISSIONS_METADATA_KEY = 'auth:permissions';
11
+ const toPermissionList = (permissionValue) => {
12
+ if (Array.isArray(permissionValue)) {
13
+ return [
14
+ ...permissionValue.filter((permission) => Boolean(permission)),
15
+ ];
16
+ }
17
+ return typeof permissionValue === 'string' && permissionValue
18
+ ? [permissionValue]
19
+ : [];
20
+ };
21
+ const applyPermissionMetadata = (controllerClass, permissions) => {
22
+ if (!permissions) {
23
+ return;
24
+ }
25
+ for (const endpoint of Object.keys(permissions)) {
26
+ const permissionValue = permissions[endpoint];
27
+ if (!permissionValue) {
28
+ continue;
29
+ }
30
+ const permissionList = toPermissionList(permissionValue);
31
+ if (permissionList.length === 0) {
32
+ continue;
33
+ }
34
+ const handler = controllerClass.prototype[endpoint];
35
+ if (typeof handler !== 'function') {
36
+ continue;
37
+ }
38
+ Reflect.defineMetadata(exports.AUTH_PERMISSIONS_METADATA_KEY, permissionList, handler);
39
+ }
40
+ };
41
+ function CreateNestedCrudController(createDto, updateDto, responseDto, options) {
42
+ class BaseController {
43
+ constructor(service) {
44
+ this.service = service;
45
+ }
46
+ ensureEndpointEnabled(endpoint) {
47
+ if (this.service.disabledEndpoints?.includes(endpoint)) {
48
+ throw new common_1.NotFoundException('Resource not found');
49
+ }
50
+ }
51
+ findAll(query) {
52
+ this.ensureEndpointEnabled('findAll');
53
+ return this.service.findAll(query);
54
+ }
55
+ create(dto) {
56
+ this.ensureEndpointEnabled('create');
57
+ return this.service.create(dto);
58
+ }
59
+ update(id, dto) {
60
+ this.ensureEndpointEnabled('update');
61
+ return this.service.update(id, dto);
62
+ }
63
+ remove(id) {
64
+ this.ensureEndpointEnabled('remove');
65
+ return this.service.remove(id);
66
+ }
67
+ findAuditLogs(query) {
68
+ this.ensureEndpointEnabled('findAuditLogs');
69
+ if (!this.service.findAuditLogs) {
70
+ throw new common_1.NotFoundException('Resource not found');
71
+ }
72
+ return this.service.findAuditLogs(query);
73
+ }
74
+ findOne(id) {
75
+ this.ensureEndpointEnabled('findOne');
76
+ return this.service.findOne(id);
77
+ }
78
+ }
79
+ tslib_1.__decorate([
80
+ (0, common_1.Get)(),
81
+ (0, response_message_decorator_1.Message)('fetched'),
82
+ (0, swagger_1.ApiResponse)({ type: [responseDto] }),
83
+ (0, swagger_1.ApiQuery)({
84
+ name: 'filter',
85
+ required: false,
86
+ style: 'deepObject',
87
+ explode: true,
88
+ type: 'object',
89
+ description: 'Filters in format filter[field_operator]=value. Operators: eq, ne, cont, notcont, starts, ends, gte, lte, gt, lt, in, nin, isnull. Grouping keys: and, or',
90
+ example: {
91
+ name_cont: 'Bob',
92
+ or: [{ name_eq: 'Bob' }, { name_eq: 'Carol' }],
93
+ },
94
+ }),
95
+ (0, swagger_1.ApiQuery)({ name: 'page', required: false, type: Number }),
96
+ (0, swagger_1.ApiQuery)({ name: 'limit', required: false, type: Number }),
97
+ (0, swagger_1.ApiQuery)({ name: 'orderBy', required: false, type: String }),
98
+ (0, swagger_1.ApiQuery)({
99
+ name: 'orderDirection',
100
+ required: false,
101
+ enum: ['ASC', 'DESC'],
102
+ }),
103
+ tslib_1.__param(0, (0, common_1.Query)()),
104
+ tslib_1.__metadata("design:type", Function),
105
+ tslib_1.__metadata("design:paramtypes", [Object]),
106
+ tslib_1.__metadata("design:returntype", void 0)
107
+ ], BaseController.prototype, "findAll", null);
108
+ tslib_1.__decorate([
109
+ (0, common_1.Post)(),
110
+ (0, response_message_decorator_1.Message)('created'),
111
+ (0, nest_audit_1.Audit)({ action: 'CREATE' }),
112
+ (0, swagger_1.ApiBody)({ type: createDto }),
113
+ (0, swagger_1.ApiResponse)({ type: responseDto }),
114
+ tslib_1.__param(0, (0, common_1.Body)()),
115
+ tslib_1.__metadata("design:type", Function),
116
+ tslib_1.__metadata("design:paramtypes", [Object]),
117
+ tslib_1.__metadata("design:returntype", void 0)
118
+ ], BaseController.prototype, "create", null);
119
+ tslib_1.__decorate([
120
+ (0, common_1.Patch)(':id'),
121
+ (0, response_message_decorator_1.Message)('updated'),
122
+ (0, nest_audit_1.Audit)({ action: 'UPDATE' }),
123
+ (0, swagger_1.ApiBody)({ type: updateDto }),
124
+ (0, swagger_1.ApiResponse)({ type: responseDto }),
125
+ tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
126
+ tslib_1.__param(1, (0, common_1.Body)()),
127
+ tslib_1.__metadata("design:type", Function),
128
+ tslib_1.__metadata("design:paramtypes", [Number, Object]),
129
+ tslib_1.__metadata("design:returntype", void 0)
130
+ ], BaseController.prototype, "update", null);
131
+ tslib_1.__decorate([
132
+ (0, common_1.Delete)(':id'),
133
+ (0, response_message_decorator_1.Message)('deleted'),
134
+ (0, nest_audit_1.Audit)({ action: 'DELETE' }),
135
+ tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
136
+ tslib_1.__metadata("design:type", Function),
137
+ tslib_1.__metadata("design:paramtypes", [Number]),
138
+ tslib_1.__metadata("design:returntype", void 0)
139
+ ], BaseController.prototype, "remove", null);
140
+ tslib_1.__decorate([
141
+ (0, common_1.Get)('auditlogs'),
142
+ (0, response_message_decorator_1.Message)('fetched'),
143
+ tslib_1.__param(0, (0, common_1.Query)()),
144
+ tslib_1.__metadata("design:type", Function),
145
+ tslib_1.__metadata("design:paramtypes", [nest_audit_1.ListAuditLogsDto]),
146
+ tslib_1.__metadata("design:returntype", void 0)
147
+ ], BaseController.prototype, "findAuditLogs", null);
148
+ tslib_1.__decorate([
149
+ (0, common_1.Get)(':id'),
150
+ (0, response_message_decorator_1.Message)('fetched'),
151
+ (0, swagger_1.ApiResponse)({ type: responseDto }),
152
+ tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
153
+ tslib_1.__metadata("design:type", Function),
154
+ tslib_1.__metadata("design:paramtypes", [Number]),
155
+ tslib_1.__metadata("design:returntype", void 0)
156
+ ], BaseController.prototype, "findOne", null);
157
+ applyPermissionMetadata(BaseController, options?.permissions);
158
+ return BaseController;
159
+ }
@@ -0,0 +1,9 @@
1
+ export declare const MESSAGE_KEY = "customMessage";
2
+ export declare const Message: (message: string) => import("@nestjs/common").CustomDecorator<string>;
3
+ export declare const ENTITY_NAME_KEY = "entityName";
4
+ export interface EntityNames {
5
+ singular: string;
6
+ plural: string;
7
+ }
8
+ export declare const EntityName: (names: string | EntityNames) => import("@nestjs/common").CustomDecorator<string>;
9
+ //# sourceMappingURL=response-message.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-message.decorator.d.ts","sourceRoot":"","sources":["../../../src/lib/decorators/response-message.decorator.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,WAAW,kBAAkB,CAAC;AAC3C,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,qDAAsC,CAAC;AAE9E,eAAO,MAAM,eAAe,eAAe,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,GAAG,WAAW,qDAInD,CAAC"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntityName = exports.ENTITY_NAME_KEY = exports.Message = exports.MESSAGE_KEY = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.MESSAGE_KEY = 'customMessage';
6
+ const Message = (message) => (0, common_1.SetMetadata)(exports.MESSAGE_KEY, message);
7
+ exports.Message = Message;
8
+ exports.ENTITY_NAME_KEY = 'entityName';
9
+ const EntityName = (names) => (0, common_1.SetMetadata)(exports.ENTITY_NAME_KEY, typeof names === 'string' ? { singular: names, plural: `${names}s` } : names);
10
+ exports.EntityName = EntityName;
@@ -0,0 +1,4 @@
1
+ export declare class FilterDto {
2
+ filter?: Record<string, unknown>;
3
+ }
4
+ //# sourceMappingURL=filter.dto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.dto.d.ts","sourceRoot":"","sources":["../../../src/lib/dtos/filter.dto.ts"],"names":[],"mappings":"AAGA,qBAAa,SAAS;IAqBpB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FilterDto = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const class_transformer_1 = require("class-transformer");
6
+ const class_validator_1 = require("class-validator");
7
+ class FilterDto {
8
+ }
9
+ exports.FilterDto = FilterDto;
10
+ tslib_1.__decorate([
11
+ (0, class_validator_1.IsOptional)(),
12
+ (0, class_validator_1.IsObject)(),
13
+ (0, class_transformer_1.Transform)(({ value, obj }) => {
14
+ // 1. If Express already parsed it into an object: { name_cont: 'Alice' }
15
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
16
+ return value;
17
+ }
18
+ // 2. Fallback: If for some reason it's still a flat object with string keys
19
+ // like { "filter[name_cont]": "Alice" }
20
+ const filters = {};
21
+ Object.keys(obj).forEach((key) => {
22
+ const match = key.match(/^filter\[(.*)\]$/);
23
+ if (match && match[1]) {
24
+ filters[match[1]] = obj[key];
25
+ }
26
+ });
27
+ return Object.keys(filters).length > 0 ? filters : value;
28
+ }),
29
+ tslib_1.__metadata("design:type", Object)
30
+ ], FilterDto.prototype, "filter", void 0);
@@ -0,0 +1,7 @@
1
+ export declare class PaginationDto {
2
+ page?: number;
3
+ limit?: number;
4
+ orderBy?: string;
5
+ orderDirection?: 'ASC' | 'DESC';
6
+ }
7
+ //# sourceMappingURL=pagination.dto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination.dto.d.ts","sourceRoot":"","sources":["../../../src/lib/dtos/pagination.dto.ts"],"names":[],"mappings":"AAIA,qBAAa,aAAa;IAUxB,IAAI,CAAC,EAAE,MAAM,CAAK;IAWlB,KAAK,CAAC,EAAE,MAAM,CAAM;IAMpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAQjB,cAAc,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACjC"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PaginationDto = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const class_validator_1 = require("class-validator");
6
+ const class_transformer_1 = require("class-transformer");
7
+ const swagger_1 = require("@nestjs/swagger");
8
+ class PaginationDto {
9
+ constructor() {
10
+ this.page = 1;
11
+ this.limit = 10;
12
+ }
13
+ }
14
+ exports.PaginationDto = PaginationDto;
15
+ tslib_1.__decorate([
16
+ (0, swagger_1.ApiPropertyOptional)({
17
+ description: 'Page number',
18
+ minimum: 1,
19
+ default: 1,
20
+ }),
21
+ (0, class_validator_1.IsOptional)(),
22
+ (0, class_transformer_1.Type)(() => Number),
23
+ (0, class_validator_1.IsInt)(),
24
+ (0, class_validator_1.Min)(1),
25
+ tslib_1.__metadata("design:type", Number)
26
+ ], PaginationDto.prototype, "page", void 0);
27
+ tslib_1.__decorate([
28
+ (0, swagger_1.ApiPropertyOptional)({
29
+ description: 'Items per page',
30
+ minimum: 1,
31
+ default: 10,
32
+ }),
33
+ (0, class_validator_1.IsOptional)(),
34
+ (0, class_transformer_1.Type)(() => Number),
35
+ (0, class_validator_1.IsInt)(),
36
+ (0, class_validator_1.Min)(1),
37
+ tslib_1.__metadata("design:type", Number)
38
+ ], PaginationDto.prototype, "limit", void 0);
39
+ tslib_1.__decorate([
40
+ (0, swagger_1.ApiPropertyOptional)({
41
+ description: 'Field to order by',
42
+ }),
43
+ (0, class_validator_1.IsOptional)(),
44
+ tslib_1.__metadata("design:type", String)
45
+ ], PaginationDto.prototype, "orderBy", void 0);
46
+ tslib_1.__decorate([
47
+ (0, swagger_1.ApiPropertyOptional)({
48
+ description: 'Sort direction',
49
+ enum: ['ASC', 'DESC'],
50
+ default: 'DESC',
51
+ }),
52
+ (0, class_validator_1.IsOptional)(),
53
+ tslib_1.__metadata("design:type", String)
54
+ ], PaginationDto.prototype, "orderDirection", void 0);
@@ -0,0 +1,6 @@
1
+ import { ArgumentsHost, ExceptionFilter } from '@nestjs/common';
2
+ import { QueryFailedError } from 'typeorm';
3
+ export declare class TypeOrmExceptionFilter implements ExceptionFilter {
4
+ catch(exception: QueryFailedError, host: ArgumentsHost): void;
5
+ }
6
+ //# sourceMappingURL=exception-filter.helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exception-filter.helper.d.ts","sourceRoot":"","sources":["../../../src/lib/helpers/exception-filter.helper.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAEb,eAAe,EAEhB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAU3C,qBACa,sBAAuB,YAAW,eAAe;IAC5D,KAAK,CAAC,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;CA8B9D"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TypeOrmExceptionFilter = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const typeorm_1 = require("typeorm");
7
+ let TypeOrmExceptionFilter = class TypeOrmExceptionFilter {
8
+ catch(exception, host) {
9
+ const ctx = host.switchToHttp();
10
+ const response = ctx.getResponse();
11
+ const driverError = exception.driverError;
12
+ const isUniqueViolation = driverError.code === '23505' || driverError.errno === 1062;
13
+ let status = common_1.HttpStatus.INTERNAL_SERVER_ERROR;
14
+ let message = 'Internal server error';
15
+ if (isUniqueViolation) {
16
+ status = common_1.HttpStatus.UNPROCESSABLE_ENTITY;
17
+ message = 'Duplicate entry: A record with this value already exists.';
18
+ if (driverError.detail) {
19
+ message = driverError.detail
20
+ .replace('Key ', '')
21
+ .replace(/[()]/g, '')
22
+ .trim();
23
+ }
24
+ }
25
+ response.status(status).json({
26
+ status: 'error',
27
+ message: message,
28
+ statusCode: status,
29
+ });
30
+ }
31
+ };
32
+ exports.TypeOrmExceptionFilter = TypeOrmExceptionFilter;
33
+ exports.TypeOrmExceptionFilter = TypeOrmExceptionFilter = tslib_1.__decorate([
34
+ (0, common_1.Catch)(typeorm_1.QueryFailedError)
35
+ ], TypeOrmExceptionFilter);
@@ -0,0 +1,3 @@
1
+ import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
2
+ export declare function applyFilters<Entity extends ObjectLiteral>(qb: SelectQueryBuilder<Entity>, filters: Record<string, unknown> | undefined, allowedFilters: readonly (keyof Entity)[]): void;
3
+ //# sourceMappingURL=filter.helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.helper.d.ts","sourceRoot":"","sources":["../../../src/lib/helpers/filter.helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAsL5D,wBAAgB,YAAY,CAAC,MAAM,SAAS,aAAa,EACvD,EAAE,EAAE,kBAAkB,CAAC,MAAM,CAAC,EAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,cAAc,EAAE,SAAS,CAAC,MAAM,MAAM,CAAC,EAAE,GACxC,IAAI,CAoBN"}
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyFilters = applyFilters;
4
+ const SAFE_FIELD_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
5
+ const toArrayValue = (value) => {
6
+ if (Array.isArray(value)) {
7
+ return value;
8
+ }
9
+ if (typeof value === 'string') {
10
+ return value
11
+ .split(',')
12
+ .map((part) => part.trim())
13
+ .filter((part) => part.length > 0);
14
+ }
15
+ return value === undefined || value === null ? [] : [value];
16
+ };
17
+ const toBooleanValue = (value) => {
18
+ if (typeof value === 'boolean')
19
+ return value;
20
+ if (typeof value === 'number')
21
+ return value !== 0;
22
+ if (typeof value !== 'string')
23
+ return null;
24
+ const normalized = value.trim().toLowerCase();
25
+ if (['true', '1', 'yes'].includes(normalized))
26
+ return true;
27
+ if (['false', '0', 'no'].includes(normalized))
28
+ return false;
29
+ return null;
30
+ };
31
+ const parseFilterKey = (rawKey) => {
32
+ const separatorIndex = rawKey.lastIndexOf('_');
33
+ if (separatorIndex <= 0 || separatorIndex === rawKey.length - 1)
34
+ return null;
35
+ const field = rawKey.slice(0, separatorIndex);
36
+ const operator = rawKey.slice(separatorIndex + 1).toLowerCase();
37
+ return { field, operator };
38
+ };
39
+ const buildCondition = (field, operator, value, paramIndex) => {
40
+ const paramKey = `filter_${paramIndex.current++}`;
41
+ switch (operator) {
42
+ case 'eq':
43
+ return { sql: `e.${field} = :${paramKey}`, params: { [paramKey]: value } };
44
+ case 'ne':
45
+ return { sql: `e.${field} != :${paramKey}`, params: { [paramKey]: value } };
46
+ case 'cont':
47
+ return {
48
+ sql: `e.${field} ILIKE :${paramKey}`,
49
+ params: { [paramKey]: `%${value}%` },
50
+ };
51
+ case 'notcont':
52
+ return {
53
+ sql: `e.${field} NOT ILIKE :${paramKey}`,
54
+ params: { [paramKey]: `%${value}%` },
55
+ };
56
+ case 'starts':
57
+ return {
58
+ sql: `e.${field} ILIKE :${paramKey}`,
59
+ params: { [paramKey]: `${value}%` },
60
+ };
61
+ case 'ends':
62
+ return {
63
+ sql: `e.${field} ILIKE :${paramKey}`,
64
+ params: { [paramKey]: `%${value}` },
65
+ };
66
+ case 'gte':
67
+ return { sql: `e.${field} >= :${paramKey}`, params: { [paramKey]: value } };
68
+ case 'lte':
69
+ return { sql: `e.${field} <= :${paramKey}`, params: { [paramKey]: value } };
70
+ case 'gt':
71
+ return { sql: `e.${field} > :${paramKey}`, params: { [paramKey]: value } };
72
+ case 'lt':
73
+ return { sql: `e.${field} < :${paramKey}`, params: { [paramKey]: value } };
74
+ case 'in':
75
+ case 'nin': {
76
+ const values = toArrayValue(value);
77
+ if (values.length === 0)
78
+ return null;
79
+ return {
80
+ sql: operator === 'in'
81
+ ? `e.${field} IN (:...${paramKey})`
82
+ : `e.${field} NOT IN (:...${paramKey})`,
83
+ params: { [paramKey]: values },
84
+ };
85
+ }
86
+ case 'isnull': {
87
+ const isNull = toBooleanValue(value);
88
+ if (isNull === null)
89
+ return null;
90
+ return { sql: isNull ? `e.${field} IS NULL` : `e.${field} IS NOT NULL` };
91
+ }
92
+ default:
93
+ return null;
94
+ }
95
+ };
96
+ const buildExpression = (node, combinator, allowedFilters, paramIndex, params) => {
97
+ if (!node || typeof node !== 'object')
98
+ return null;
99
+ const conditions = [];
100
+ const entries = Array.isArray(node)
101
+ ? node.map((item, index) => [String(index), item])
102
+ : Object.entries(node);
103
+ for (const [rawKey, value] of entries) {
104
+ if (rawKey === 'and' || rawKey === 'or') {
105
+ const nestedCombinator = rawKey;
106
+ const nestedNodes = Array.isArray(value) ? value : [value];
107
+ const nestedExpressions = nestedNodes
108
+ .map((nestedNode) => buildExpression(nestedNode, 'and', allowedFilters, paramIndex, params))
109
+ .filter((sql) => Boolean(sql));
110
+ if (nestedExpressions.length > 0) {
111
+ conditions.push(`(${nestedExpressions.join(` ${nestedCombinator.toUpperCase()} `)})`);
112
+ }
113
+ continue;
114
+ }
115
+ const parsed = parseFilterKey(rawKey);
116
+ if (!parsed)
117
+ continue;
118
+ const { field, operator } = parsed;
119
+ if (!SAFE_FIELD_PATTERN.test(field))
120
+ continue;
121
+ if (!allowedFilters.includes(field))
122
+ continue;
123
+ const condition = buildCondition(field, operator, value, paramIndex);
124
+ if (!condition)
125
+ continue;
126
+ if (condition.params)
127
+ Object.assign(params, condition.params);
128
+ conditions.push(condition.sql);
129
+ }
130
+ if (conditions.length === 0)
131
+ return null;
132
+ if (conditions.length === 1)
133
+ return conditions[0];
134
+ return `(${conditions.join(` ${combinator.toUpperCase()} `)})`;
135
+ };
136
+ function applyFilters(qb, filters, allowedFilters) {
137
+ if (!filters)
138
+ return;
139
+ const params = {};
140
+ const expression = buildExpression(filters, 'and', allowedFilters.map(String), { current: 0 }, params);
141
+ if (!expression)
142
+ return;
143
+ if (Object.keys(params).length > 0) {
144
+ qb.andWhere(expression, params);
145
+ return;
146
+ }
147
+ qb.andWhere(expression);
148
+ }
@@ -0,0 +1,7 @@
1
+ import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
2
+ import { PaginationDto } from '../dtos/pagination.dto';
3
+ export declare function applyPagination<Entity extends ObjectLiteral>(qb: SelectQueryBuilder<Entity>, query: PaginationDto): {
4
+ page: number;
5
+ limit: number;
6
+ } | null;
7
+ //# sourceMappingURL=pagination.helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination.helper.d.ts","sourceRoot":"","sources":["../../../src/lib/helpers/pagination.helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,wBAAgB,eAAe,CAAC,MAAM,SAAS,aAAa,EAC1D,EAAE,EAAE,kBAAkB,CAAC,MAAM,CAAC,EAC9B,KAAK,EAAE,aAAa;;;SAgBrB"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyPagination = applyPagination;
4
+ function applyPagination(qb, query) {
5
+ const { page, limit } = query;
6
+ if (!page || !limit) {
7
+ return null;
8
+ }
9
+ const skip = (page - 1) * limit;
10
+ qb.skip(skip).take(limit);
11
+ return {
12
+ page,
13
+ limit,
14
+ };
15
+ }
@@ -0,0 +1,9 @@
1
+ import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
+ import { Observable } from 'rxjs';
3
+ import { Reflector } from '@nestjs/core';
4
+ export declare class ResponseInterceptor implements NestInterceptor {
5
+ private readonly reflector;
6
+ constructor(reflector: Reflector);
7
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
8
+ }
9
+ //# sourceMappingURL=response.interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.interceptor.d.ts","sourceRoot":"","sources":["../../../src/lib/interceptors/response.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,gBAAgB,EAChB,WAAW,EACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAElC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAOzC,qBACa,mBAAoB,YAAW,eAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,SAAS;IAEjD,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC;CA+B7E"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResponseInterceptor = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const operators_1 = require("rxjs/operators");
7
+ const core_1 = require("@nestjs/core");
8
+ const response_message_decorator_1 = require("../decorators/response-message.decorator");
9
+ let ResponseInterceptor = class ResponseInterceptor {
10
+ constructor(reflector) {
11
+ this.reflector = reflector;
12
+ }
13
+ intercept(context, next) {
14
+ const handler = context.getHandler();
15
+ const controller = context.getClass();
16
+ return next.handle().pipe((0, operators_1.map)((data) => {
17
+ const entityConfig = this.reflector.get(response_message_decorator_1.ENTITY_NAME_KEY, controller);
18
+ const action = this.reflector.get(response_message_decorator_1.MESSAGE_KEY, handler);
19
+ const isList = Array.isArray(data) || Array.isArray(data?.data);
20
+ const name = isList
21
+ ? entityConfig?.plural ?? 'Resources'
22
+ : entityConfig?.singular ?? 'Resource';
23
+ const finalMessage = action
24
+ ? `${name} ${action} successfully`
25
+ : 'Request successful';
26
+ return {
27
+ message: finalMessage,
28
+ data: data?.data ?? data ?? null,
29
+ meta: data?.meta,
30
+ status: 'success',
31
+ };
32
+ }));
33
+ }
34
+ };
35
+ exports.ResponseInterceptor = ResponseInterceptor;
36
+ exports.ResponseInterceptor = ResponseInterceptor = tslib_1.__decorate([
37
+ (0, common_1.Injectable)(),
38
+ tslib_1.__metadata("design:paramtypes", [core_1.Reflector])
39
+ ], ResponseInterceptor);
@@ -0,0 +1,23 @@
1
+ import { FilterDto } from '../dtos/filter.dto';
2
+ import { PaginationDto } from '../dtos/pagination.dto';
3
+ export type CrudEndpoint = 'findAll' | 'findOne' | 'create' | 'update' | 'remove' | 'findAuditLogs';
4
+ export interface AuditLogQuery {
5
+ user_id?: string;
6
+ start_date?: string;
7
+ end_date?: string;
8
+ page?: number;
9
+ limit?: number;
10
+ }
11
+ export interface CrudInterface<CreateDto, UpdateDto, ResponseDto> {
12
+ disabledEndpoints?: readonly CrudEndpoint[];
13
+ findAll(query: PaginationDto & FilterDto): Promise<{
14
+ data: ResponseDto[];
15
+ meta?: unknown;
16
+ }>;
17
+ findOne(id: number): Promise<ResponseDto>;
18
+ create(dto: CreateDto): Promise<ResponseDto>;
19
+ update(id: number, dto: UpdateDto): Promise<ResponseDto>;
20
+ remove(id: number): Promise<boolean>;
21
+ findAuditLogs?(query: AuditLogQuery): Promise<unknown>;
22
+ }
23
+ //# sourceMappingURL=crud.interface.d.ts.map
@@ -0,0 +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;AAEvD,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,eAAe,CAAC;AAEpB,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,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,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;CACxD"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ export declare class NestCrudModule {
2
+ }
3
+ //# sourceMappingURL=nest-crud.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nest-crud.module.d.ts","sourceRoot":"","sources":["../../src/lib/nest-crud.module.ts"],"names":[],"mappings":"AAGA,qBAIa,cAAc;CAAG"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NestCrudModule = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const nest_crud_service_1 = require("./services/nest-crud.service");
7
+ let NestCrudModule = class NestCrudModule {
8
+ };
9
+ exports.NestCrudModule = NestCrudModule;
10
+ exports.NestCrudModule = NestCrudModule = tslib_1.__decorate([
11
+ (0, common_1.Module)({
12
+ providers: [nest_crud_service_1.NestCrudService],
13
+ exports: [nest_crud_service_1.NestCrudService],
14
+ })
15
+ ], NestCrudModule);
@@ -0,0 +1,69 @@
1
+ import { Type } from '@nestjs/common';
2
+ import { ObjectLiteral, Repository } from 'typeorm';
3
+ import { AuditLogEntity } from '@nest-util/nest-audit';
4
+ import { PaginationDto } from '../dtos/pagination.dto';
5
+ import { FilterDto } from '../dtos/filter.dto';
6
+ import { CrudEndpoint, CrudInterface } from '../interfaces/crud.interface';
7
+ export interface CrudServiceOptions<Entity extends ObjectLiteral, ResponseDto> {
8
+ repository: Repository<Entity>;
9
+ allowedFilters?: readonly (keyof Entity)[];
10
+ allowedSortFields?: readonly (keyof Entity)[];
11
+ include?: readonly string[];
12
+ relations?: {
13
+ property: keyof Entity;
14
+ repo: Repository<ObjectLiteral>;
15
+ idField?: string;
16
+ }[];
17
+ toResponseDto?: (entity: Entity | Entity[]) => ResponseDto | ResponseDto[];
18
+ createDtoClass?: Type<unknown>;
19
+ updateDtoClass?: Type<unknown>;
20
+ disabledEndpoints?: readonly CrudEndpoint[];
21
+ }
22
+ export declare class NestCrudService<Entity extends ObjectLiteral, CreateDto = Partial<Entity>, UpdateDto = Partial<Entity>, ResponseDto = Entity> implements CrudInterface<CreateDto, UpdateDto, ResponseDto> {
23
+ protected readonly repo: Repository<Entity>;
24
+ protected readonly allowedFilters: readonly (keyof Entity)[];
25
+ protected readonly allowedSortFields: readonly (keyof Entity)[];
26
+ protected readonly include: readonly string[];
27
+ protected readonly relations: {
28
+ property: keyof Entity;
29
+ repo: Repository<ObjectLiteral>;
30
+ idField?: string;
31
+ }[];
32
+ protected readonly toResponseDto?: (entity: Entity | Entity[]) => ResponseDto | ResponseDto[];
33
+ protected readonly createDtoClass?: Type<unknown>;
34
+ protected readonly updateDtoClass?: Type<unknown>;
35
+ readonly disabledEndpoints: readonly CrudEndpoint[];
36
+ constructor(options: CrudServiceOptions<Entity, ResponseDto>);
37
+ private resolveRelations;
38
+ findAll(query: PaginationDto & FilterDto): Promise<{
39
+ data: ResponseDto[];
40
+ meta: {
41
+ total: number;
42
+ page: number;
43
+ limit: number;
44
+ };
45
+ } | {
46
+ data: ResponseDto[];
47
+ meta?: undefined;
48
+ }>;
49
+ findOne(id: number): Promise<ResponseDto>;
50
+ create(payload: CreateDto): Promise<ResponseDto>;
51
+ update(id: number, payload: UpdateDto): Promise<ResponseDto>;
52
+ remove(id: number): Promise<boolean>;
53
+ findAuditLogs(query: {
54
+ user_id?: string;
55
+ start_date?: string;
56
+ end_date?: string;
57
+ page?: number;
58
+ limit?: number;
59
+ }): Promise<{
60
+ data: AuditLogEntity[];
61
+ meta: {
62
+ total: number;
63
+ page: number;
64
+ limit: number;
65
+ totalPages: number;
66
+ };
67
+ }>;
68
+ }
69
+ //# sourceMappingURL=nest-crud.service.d.ts.map
@@ -0,0 +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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"}
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NestCrudService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const nest_audit_1 = require("@nest-util/nest-audit");
7
+ const filter_helper_1 = require("../helpers/filter.helper");
8
+ const pagination_helper_1 = require("../helpers/pagination.helper");
9
+ let NestCrudService = class NestCrudService {
10
+ constructor(options) {
11
+ this.repo = options.repository;
12
+ this.allowedFilters = options.allowedFilters ?? [];
13
+ this.allowedSortFields = options.allowedSortFields ?? [];
14
+ this.include = options.include ?? [];
15
+ this.relations = options.relations ?? [];
16
+ this.toResponseDto = options.toResponseDto;
17
+ this.createDtoClass = options.createDtoClass;
18
+ this.updateDtoClass = options.updateDtoClass;
19
+ this.disabledEndpoints = options.disabledEndpoints ?? [];
20
+ }
21
+ async resolveRelations(payload) {
22
+ if (!this.relations.length)
23
+ return payload;
24
+ for (const relation of this.relations) {
25
+ const idField = relation.idField ?? `${String(relation.property)}Id`;
26
+ if (!(idField in payload))
27
+ continue;
28
+ const id = payload[idField];
29
+ if (!id)
30
+ continue;
31
+ const entity = await relation.repo.findOneBy({
32
+ id,
33
+ });
34
+ if (!entity) {
35
+ throw new common_1.NotFoundException(`${String(relation.property)} not found`);
36
+ }
37
+ payload[String(relation.property)] = entity;
38
+ delete payload[idField];
39
+ }
40
+ return payload;
41
+ }
42
+ async findAll(query) {
43
+ const qb = this.repo.createQueryBuilder('e');
44
+ if (this.include.length > 0) {
45
+ this.include.forEach((relation) => {
46
+ const parts = relation.split('.');
47
+ if (parts.length === 1) {
48
+ qb.leftJoinAndSelect(`e.${parts[0]}`, parts[0]);
49
+ }
50
+ else {
51
+ const parentAlias = parts.slice(0, -1).join('_');
52
+ const field = parts[parts.length - 1];
53
+ const alias = parts.join('_');
54
+ qb.leftJoinAndSelect(`${parentAlias}.${field}`, alias);
55
+ }
56
+ });
57
+ }
58
+ (0, filter_helper_1.applyFilters)(qb, query.filter, this.allowedFilters);
59
+ const paginationMeta = (0, pagination_helper_1.applyPagination)(qb, query);
60
+ if (query.orderBy) {
61
+ const orderDirection = query.orderDirection === 'ASC' ? 'ASC' : 'DESC';
62
+ if (this.allowedSortFields.length === 0 ||
63
+ this.allowedSortFields.includes(query.orderBy)) {
64
+ qb.orderBy(`e.${query.orderBy}`, orderDirection);
65
+ }
66
+ }
67
+ const [entities, total] = await qb.getManyAndCount();
68
+ const data = this.toResponseDto
69
+ ? this.toResponseDto(entities)
70
+ : entities;
71
+ return paginationMeta
72
+ ? { data, meta: { ...paginationMeta, total } }
73
+ : { data };
74
+ }
75
+ async findOne(id) {
76
+ const entity = await this.repo.findOne({
77
+ where: { id },
78
+ relations: this.include,
79
+ });
80
+ if (!entity) {
81
+ throw new common_1.NotFoundException('Resource not found');
82
+ }
83
+ return this.toResponseDto
84
+ ? this.toResponseDto(entity)
85
+ : entity;
86
+ }
87
+ async create(payload) {
88
+ const resolved = await this.resolveRelations(payload);
89
+ const entity = await this.repo.save(resolved);
90
+ return this.toResponseDto
91
+ ? this.toResponseDto(entity)
92
+ : entity;
93
+ }
94
+ async update(id, payload) {
95
+ const existing = await this.repo.findOneBy({
96
+ id,
97
+ });
98
+ if (!existing) {
99
+ throw new common_1.NotFoundException('Resource not found');
100
+ }
101
+ const resolved = await this.resolveRelations(payload);
102
+ await this.repo.update(id, resolved);
103
+ return this.findOne(id);
104
+ }
105
+ async remove(id) {
106
+ const result = await this.repo.delete(id);
107
+ if (result.affected === 0) {
108
+ throw new common_1.NotFoundException('Resource not found');
109
+ }
110
+ return true;
111
+ }
112
+ async findAuditLogs(query) {
113
+ const page = query.page ?? 1;
114
+ const limit = query.limit ?? 10;
115
+ const qb = this.repo.manager
116
+ .getRepository(nest_audit_1.AuditLogEntity)
117
+ .createQueryBuilder('auditLog')
118
+ .where('auditLog.entity = :entity', {
119
+ entity: this.repo.metadata.name,
120
+ })
121
+ .orderBy('auditLog.createdAt', 'DESC');
122
+ if (query.user_id) {
123
+ qb.andWhere('auditLog.userId = :userId', {
124
+ userId: query.user_id,
125
+ });
126
+ }
127
+ if (query.start_date) {
128
+ qb.andWhere('auditLog.createdAt >= :startDate', {
129
+ startDate: new Date(query.start_date),
130
+ });
131
+ }
132
+ if (query.end_date) {
133
+ qb.andWhere('auditLog.createdAt <= :endDate', {
134
+ endDate: new Date(query.end_date),
135
+ });
136
+ }
137
+ qb.skip((page - 1) * limit).take(limit);
138
+ const [data, total] = await qb.getManyAndCount();
139
+ return {
140
+ data,
141
+ meta: {
142
+ total,
143
+ page,
144
+ limit,
145
+ totalPages: Math.ceil(total / limit) || 1,
146
+ },
147
+ };
148
+ }
149
+ };
150
+ exports.NestCrudService = NestCrudService;
151
+ exports.NestCrudService = NestCrudService = tslib_1.__decorate([
152
+ (0, common_1.Injectable)(),
153
+ tslib_1.__metadata("design:paramtypes", [Object])
154
+ ], NestCrudService);
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@nest-util/nest-crud",
3
+ "version": "0.0.3",
4
+ "main": "./dist/index.js",
5
+ "module": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ "./package.json": "./package.json",
9
+ ".": {
10
+ "@nest-util/source": "./src/index.ts",
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "!**/*.tsbuildinfo"
19
+ ],
20
+ "dependencies": {
21
+ "tslib": "^2.3.0"
22
+ },
23
+ "peerDependencies": {
24
+ "@nest-util/nest-audit": "workspace:*",
25
+ "@nestjs/common": "^11.0.0",
26
+ "@nestjs/core": "^11.0.0",
27
+ "@nestjs/swagger": "^11.2.6",
28
+ "class-transformer": "^0.5.1",
29
+ "class-validator": "^0.14.3",
30
+ "express": "^5.2.1",
31
+ "typeorm": "^0.3.28",
32
+ "rxjs": "^7.8.0"
33
+ }
34
+ }