@flusys/nestjs-shared 1.0.0-beta → 1.0.0-rc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -110
- package/cjs/classes/api-controller.class.js +9 -24
- package/cjs/classes/index.js +1 -0
- package/cjs/constants/index.js +14 -0
- package/cjs/constants/permissions.js +174 -0
- package/cjs/decorators/api-response.decorator.js +1 -1
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/sanitize-html.decorator.js +36 -0
- package/cjs/dtos/filter-and-pagination.dto.js +24 -34
- package/cjs/dtos/pagination.dto.js +4 -8
- package/cjs/dtos/response-payload.dto.js +0 -41
- package/cjs/entities/identity.js +4 -4
- package/cjs/entities/user-root.js +13 -14
- package/cjs/guards/permission.guard.js +39 -94
- package/cjs/interceptors/index.js +1 -0
- package/cjs/interceptors/set-create-by-on-body.interceptor.js +2 -30
- package/cjs/interceptors/set-delete-by-on-body.interceptor.js +2 -30
- package/cjs/interceptors/set-update-by-on-body.interceptor.js +2 -30
- package/cjs/interceptors/set-user-field-on-body.interceptor.js +43 -0
- package/cjs/interceptors/slug.interceptor.js +30 -9
- package/cjs/interfaces/datasource.interface.js +4 -0
- package/cjs/interfaces/index.js +2 -1
- package/cjs/interfaces/module-config.interface.js +4 -0
- package/cjs/modules/cache/cache.module.js +3 -3
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +30 -110
- package/cjs/modules/utils/utils.service.js +63 -145
- package/cjs/utils/error-handler.util.js +91 -13
- package/cjs/utils/html-sanitizer.util.js +74 -0
- package/cjs/utils/index.js +2 -0
- package/cjs/utils/query-helpers.util.js +53 -0
- package/classes/api-controller.class.d.ts +5 -5
- package/classes/api-service.class.d.ts +5 -5
- package/classes/index.d.ts +1 -0
- package/classes/request-scoped-api.service.d.ts +3 -2
- package/constants/index.d.ts +1 -0
- package/constants/permissions.d.ts +167 -0
- package/decorators/index.d.ts +1 -0
- package/decorators/sanitize-html.decorator.d.ts +2 -0
- package/dtos/filter-and-pagination.dto.d.ts +0 -2
- package/dtos/response-payload.dto.d.ts +0 -7
- package/fesm/classes/api-controller.class.js +9 -24
- package/fesm/classes/index.js +2 -0
- package/fesm/constants/index.js +2 -0
- package/fesm/constants/permissions.js +121 -0
- package/fesm/decorators/api-response.decorator.js +1 -1
- package/fesm/decorators/index.js +1 -0
- package/fesm/decorators/sanitize-html.decorator.js +45 -0
- package/fesm/dtos/filter-and-pagination.dto.js +26 -47
- package/fesm/dtos/pagination.dto.js +4 -8
- package/fesm/dtos/response-payload.dto.js +0 -38
- package/fesm/entities/identity.js +4 -4
- package/fesm/entities/user-root.js +13 -14
- package/fesm/guards/permission.guard.js +39 -94
- package/fesm/interceptors/index.js +1 -0
- package/fesm/interceptors/set-create-by-on-body.interceptor.js +4 -30
- package/fesm/interceptors/set-delete-by-on-body.interceptor.js +4 -30
- package/fesm/interceptors/set-update-by-on-body.interceptor.js +4 -30
- package/fesm/interceptors/set-user-field-on-body.interceptor.js +36 -0
- package/fesm/interceptors/slug.interceptor.js +31 -10
- package/fesm/interfaces/datasource.interface.js +20 -0
- package/fesm/interfaces/index.js +2 -1
- package/fesm/interfaces/module-config.interface.js +5 -0
- package/fesm/modules/cache/cache.module.js +2 -2
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +30 -110
- package/fesm/modules/utils/utils.service.js +50 -143
- package/fesm/utils/error-handler.util.js +93 -14
- package/fesm/utils/html-sanitizer.util.js +82 -0
- package/fesm/utils/index.js +2 -0
- package/fesm/utils/query-helpers.util.js +78 -0
- package/interceptors/index.d.ts +1 -0
- package/interceptors/set-create-by-on-body.interceptor.d.ts +1 -5
- package/interceptors/set-delete-by-on-body.interceptor.d.ts +1 -5
- package/interceptors/set-update-by-on-body.interceptor.d.ts +1 -5
- package/interceptors/set-user-field-on-body.interceptor.d.ts +2 -0
- package/interceptors/slug.interceptor.d.ts +2 -1
- package/interfaces/api.interface.d.ts +2 -2
- package/interfaces/datasource.interface.d.ts +5 -0
- package/interfaces/identity.interface.d.ts +4 -4
- package/interfaces/index.d.ts +2 -1
- package/interfaces/module-config.interface.d.ts +6 -0
- package/interfaces/permission.interface.d.ts +0 -1
- package/modules/utils/utils.service.d.ts +10 -4
- package/package.json +4 -4
- package/utils/error-handler.util.d.ts +23 -13
- package/utils/html-sanitizer.util.d.ts +3 -0
- package/utils/index.d.ts +2 -0
- package/utils/query-helpers.util.d.ts +16 -0
- package/cjs/interfaces/base-query.interface.js +0 -6
- package/fesm/interfaces/base-query.interface.js +0 -3
- package/interfaces/base-query.interface.d.ts +0 -7
|
@@ -8,28 +8,104 @@ Object.defineProperty(exports, "ErrorHandler", {
|
|
|
8
8
|
return ErrorHandler;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
+
const _common = require("@nestjs/common");
|
|
12
|
+
/** Check if running in production environment */ const IS_PRODUCTION = process.env.NODE_ENV === 'production';
|
|
13
|
+
/** Sensitive keys that should be redacted from logs */ const SENSITIVE_KEYS = [
|
|
14
|
+
'password',
|
|
15
|
+
'secret',
|
|
16
|
+
'token',
|
|
17
|
+
'apiKey',
|
|
18
|
+
'credential',
|
|
19
|
+
'authorization'
|
|
20
|
+
];
|
|
21
|
+
/** Patterns that indicate sensitive data in error messages */ const SENSITIVE_PATTERNS = [
|
|
22
|
+
/password/i,
|
|
23
|
+
/secret/i,
|
|
24
|
+
/token/i,
|
|
25
|
+
/key/i,
|
|
26
|
+
/credential/i,
|
|
27
|
+
/authorization/i,
|
|
28
|
+
/bearer/i
|
|
29
|
+
];
|
|
11
30
|
let ErrorHandler = class ErrorHandler {
|
|
12
31
|
/**
|
|
13
|
-
* Safely extract error message from unknown error
|
|
14
|
-
|
|
32
|
+
* Safely extract error message from unknown error.
|
|
33
|
+
* @param error - The error to extract message from
|
|
34
|
+
* @param sanitizeForClient - If true, redacts potentially sensitive info in production
|
|
35
|
+
*/ static getErrorMessage(error, sanitizeForClient = false) {
|
|
36
|
+
let message = 'Unknown error occurred';
|
|
15
37
|
if (error instanceof Error) {
|
|
16
|
-
|
|
38
|
+
message = error.message;
|
|
39
|
+
} else if (typeof error === 'string') {
|
|
40
|
+
message = error;
|
|
17
41
|
}
|
|
18
|
-
|
|
19
|
-
|
|
42
|
+
// Sanitize for client responses in production
|
|
43
|
+
if (sanitizeForClient && IS_PRODUCTION) {
|
|
44
|
+
if (this.containsSensitiveData(message)) {
|
|
45
|
+
return 'An unexpected error occurred. Please try again later.';
|
|
46
|
+
}
|
|
20
47
|
}
|
|
21
|
-
return
|
|
48
|
+
return message;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check if a string contains potentially sensitive data.
|
|
52
|
+
*/ static containsSensitiveData(text) {
|
|
53
|
+
return SENSITIVE_PATTERNS.some((pattern)=>pattern.test(text));
|
|
22
54
|
}
|
|
23
55
|
/**
|
|
24
|
-
* Safely extract error stack from unknown error
|
|
25
|
-
|
|
56
|
+
* Safely extract error stack from unknown error.
|
|
57
|
+
* Returns undefined in production for client responses.
|
|
58
|
+
* @param error - The error to extract stack from
|
|
59
|
+
* @param forClient - If true, never returns stack in production
|
|
60
|
+
*/ static getErrorStack(error, forClient = false) {
|
|
61
|
+
// Never expose stack traces to clients in production
|
|
62
|
+
if (forClient && IS_PRODUCTION) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
26
65
|
if (error instanceof Error) {
|
|
27
66
|
return error.stack;
|
|
28
67
|
}
|
|
29
68
|
return undefined;
|
|
30
69
|
}
|
|
31
70
|
/**
|
|
32
|
-
* Create error
|
|
71
|
+
* Create a sanitized error response for clients.
|
|
72
|
+
* In production, sensitive data is redacted and stack traces removed.
|
|
73
|
+
*/ static createClientError(error, statusCode = _common.HttpStatus.INTERNAL_SERVER_ERROR, code) {
|
|
74
|
+
const sanitizedError = {
|
|
75
|
+
message: this.getErrorMessage(error, true),
|
|
76
|
+
statusCode
|
|
77
|
+
};
|
|
78
|
+
if (code) {
|
|
79
|
+
sanitizedError.code = code;
|
|
80
|
+
}
|
|
81
|
+
// Include stack only in development
|
|
82
|
+
if (!IS_PRODUCTION) {
|
|
83
|
+
sanitizedError.stack = this.getErrorStack(error);
|
|
84
|
+
}
|
|
85
|
+
return sanitizedError;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Sanitize context data to redact sensitive fields from logs.
|
|
89
|
+
*/ static sanitizeContextForLogging(context) {
|
|
90
|
+
const sanitized = {};
|
|
91
|
+
for (const [key, value] of Object.entries(context)){
|
|
92
|
+
// Check if key contains sensitive words
|
|
93
|
+
const isSensitive = SENSITIVE_KEYS.some((sk)=>key.toLowerCase().includes(sk.toLowerCase()));
|
|
94
|
+
if (isSensitive) {
|
|
95
|
+
sanitized[key] = '[REDACTED]';
|
|
96
|
+
} else if (Array.isArray(value)) {
|
|
97
|
+
sanitized[key] = value.map((item)=>typeof item === 'object' && item !== null ? this.sanitizeContextForLogging(item) : item);
|
|
98
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
99
|
+
sanitized[key] = this.sanitizeContextForLogging(value);
|
|
100
|
+
} else {
|
|
101
|
+
sanitized[key] = value;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return sanitized;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create error context object for internal logging.
|
|
108
|
+
* Context data is sanitized to redact sensitive fields.
|
|
33
109
|
*/ static createErrorContext(error, context) {
|
|
34
110
|
const errorContext = {
|
|
35
111
|
error: {
|
|
@@ -41,20 +117,22 @@ let ErrorHandler = class ErrorHandler {
|
|
|
41
117
|
errorContext.error.name = error.name;
|
|
42
118
|
}
|
|
43
119
|
if (context && Object.keys(context).length > 0) {
|
|
44
|
-
|
|
120
|
+
// Sanitize context to redact sensitive fields
|
|
121
|
+
errorContext.context = this.sanitizeContextForLogging(context);
|
|
45
122
|
}
|
|
46
123
|
return errorContext;
|
|
47
124
|
}
|
|
48
125
|
/**
|
|
49
|
-
* Log error with consistent format
|
|
126
|
+
* Log error with consistent format.
|
|
127
|
+
* Sensitive data in context is automatically redacted.
|
|
50
128
|
*/ static logError(logger, error, operation, context) {
|
|
51
129
|
const errorContext = this.createErrorContext(error, {
|
|
52
130
|
operation,
|
|
53
131
|
...context
|
|
54
132
|
});
|
|
55
133
|
const errorMessage = `Failed to ${operation}: ${errorContext.error.message}`;
|
|
56
|
-
|
|
57
|
-
logger.error(errorMessage, errorContext.error.stack,
|
|
134
|
+
// Log full details internally (stack traces are fine for internal logs)
|
|
135
|
+
logger.error(errorMessage, errorContext.error.stack, errorContext);
|
|
58
136
|
}
|
|
59
137
|
/**
|
|
60
138
|
* Re-throw error with proper type checking
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Sanitizer Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for escaping HTML content to prevent XSS attacks.
|
|
5
|
+
* Use these utilities when interpolating user-provided variables into HTML content.
|
|
6
|
+
*/ /**
|
|
7
|
+
* HTML entity mapping for escaping special characters
|
|
8
|
+
*/ "use strict";
|
|
9
|
+
Object.defineProperty(exports, "__esModule", {
|
|
10
|
+
value: true
|
|
11
|
+
});
|
|
12
|
+
function _export(target, all) {
|
|
13
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
_export(exports, {
|
|
19
|
+
get containsHtmlContent () {
|
|
20
|
+
return containsHtmlContent;
|
|
21
|
+
},
|
|
22
|
+
get escapeHtml () {
|
|
23
|
+
return escapeHtml;
|
|
24
|
+
},
|
|
25
|
+
get escapeHtmlVariables () {
|
|
26
|
+
return escapeHtmlVariables;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const HTML_ESCAPE_MAP = {
|
|
30
|
+
'&': '&',
|
|
31
|
+
'<': '<',
|
|
32
|
+
'>': '>',
|
|
33
|
+
'"': '"',
|
|
34
|
+
"'": ''',
|
|
35
|
+
'/': '/',
|
|
36
|
+
'`': '`',
|
|
37
|
+
'=': '='
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Regex pattern matching characters that need HTML escaping
|
|
41
|
+
*/ const HTML_ESCAPE_REGEX = /[&<>"'`=/]/g;
|
|
42
|
+
function escapeHtml(str) {
|
|
43
|
+
if (!str || typeof str !== 'string') {
|
|
44
|
+
return str ?? '';
|
|
45
|
+
}
|
|
46
|
+
return str.replace(HTML_ESCAPE_REGEX, (char)=>HTML_ESCAPE_MAP[char] || char);
|
|
47
|
+
}
|
|
48
|
+
function escapeHtmlVariables(variables) {
|
|
49
|
+
if (!variables || typeof variables !== 'object') {
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
const escaped = {};
|
|
53
|
+
for (const [key, value] of Object.entries(variables)){
|
|
54
|
+
if (value === null || value === undefined) {
|
|
55
|
+
escaped[key] = '';
|
|
56
|
+
} else if (typeof value === 'string') {
|
|
57
|
+
escaped[key] = escapeHtml(value);
|
|
58
|
+
} else if (typeof value === 'object') {
|
|
59
|
+
// For objects/arrays, stringify and escape
|
|
60
|
+
escaped[key] = escapeHtml(JSON.stringify(value));
|
|
61
|
+
} else {
|
|
62
|
+
// For numbers, booleans, etc., convert to string (no escaping needed)
|
|
63
|
+
escaped[key] = String(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return escaped;
|
|
67
|
+
}
|
|
68
|
+
function containsHtmlContent(str) {
|
|
69
|
+
if (!str || typeof str !== 'string') {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
// Check for common HTML patterns
|
|
73
|
+
return /<[a-z][\s\S]*>/i.test(str) || /javascript:/i.test(str) || /on\w+=/i.test(str);
|
|
74
|
+
}
|
package/cjs/utils/index.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./error-handler.util"), exports);
|
|
6
|
+
_export_star(require("./html-sanitizer.util"), exports);
|
|
7
|
+
_export_star(require("./query-helpers.util"), exports);
|
|
6
8
|
function _export_star(from, to) {
|
|
7
9
|
Object.keys(from).forEach(function(k) {
|
|
8
10
|
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get applyCompanyFilter () {
|
|
13
|
+
return applyCompanyFilter;
|
|
14
|
+
},
|
|
15
|
+
get buildCompanyWhereCondition () {
|
|
16
|
+
return buildCompanyWhereCondition;
|
|
17
|
+
},
|
|
18
|
+
get hasCompanyId () {
|
|
19
|
+
return hasCompanyId;
|
|
20
|
+
},
|
|
21
|
+
get validateCompanyOwnership () {
|
|
22
|
+
return validateCompanyOwnership;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const _common = require("@nestjs/common");
|
|
26
|
+
function applyCompanyFilter(query, config, user) {
|
|
27
|
+
const columnName = config.columnName ?? 'companyId';
|
|
28
|
+
if (config.isCompanyFeatureEnabled && user?.companyId) {
|
|
29
|
+
query.andWhere(`${config.entityAlias}.${columnName} = :companyId`, {
|
|
30
|
+
companyId: user.companyId
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return query;
|
|
34
|
+
}
|
|
35
|
+
function buildCompanyWhereCondition(baseWhere, isCompanyFeatureEnabled, user) {
|
|
36
|
+
if (isCompanyFeatureEnabled && user?.companyId) {
|
|
37
|
+
return {
|
|
38
|
+
...baseWhere,
|
|
39
|
+
companyId: user.companyId
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return baseWhere;
|
|
43
|
+
}
|
|
44
|
+
function hasCompanyId(entity) {
|
|
45
|
+
return entity !== null && typeof entity === 'object' && 'companyId' in entity;
|
|
46
|
+
}
|
|
47
|
+
function validateCompanyOwnership(entity, user, isCompanyFeatureEnabled, entityName) {
|
|
48
|
+
if (isCompanyFeatureEnabled && user?.companyId && hasCompanyId(entity)) {
|
|
49
|
+
if (entity.companyId && entity.companyId !== user.companyId) {
|
|
50
|
+
throw new _common.BadRequestException(`${entityName} belongs to another company`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { BulkResponseDto, DeleteDto, FilterAndPaginationDto, GetByIdBodyDto, ListResponseDto, MessageResponseDto, SingleResponseDto } from '
|
|
2
|
-
import { Identity } from '
|
|
3
|
-
import { ILoggedUserInfo, IService, PermissionCondition, PermissionOperator } from '
|
|
1
|
+
import { BulkResponseDto, DeleteDto, FilterAndPaginationDto, GetByIdBodyDto, ListResponseDto, MessageResponseDto, SingleResponseDto } from '../dtos';
|
|
2
|
+
import { Identity } from '../entities';
|
|
3
|
+
import { ILoggedUserInfo, IService, PermissionCondition, PermissionOperator } from '../interfaces';
|
|
4
4
|
import { Type } from '@nestjs/common';
|
|
5
5
|
export type ApiEndpoint = 'insert' | 'insertMany' | 'getById' | 'getAll' | 'update' | 'updateMany' | 'delete';
|
|
6
6
|
export type SecurityLevel = 'public' | 'jwt' | 'permission';
|
|
@@ -16,9 +16,9 @@ export type ApiSecurityConfig = {
|
|
|
16
16
|
export interface ApiControllerOptions {
|
|
17
17
|
security?: ApiSecurityConfig | EndpointSecurity | SecurityLevel;
|
|
18
18
|
}
|
|
19
|
-
export declare function createApiController<CreateDtoT extends
|
|
19
|
+
export declare function createApiController<CreateDtoT extends object, UpdateDtoT extends {
|
|
20
20
|
id: string;
|
|
21
|
-
}, ResponseDtoT extends
|
|
21
|
+
}, ResponseDtoT extends object, InterfaceT extends Identity, ServiceT extends IService<CreateDtoT, UpdateDtoT, InterfaceT>>(createDtoClass: Type<CreateDtoT>, updateDtoClass: Type<UpdateDtoT>, responseDtoClass: Type<ResponseDtoT>, options?: ApiControllerOptions): abstract new (service: ServiceT) => {
|
|
22
22
|
service: ServiceT;
|
|
23
23
|
insert(addDto: CreateDtoT, user: ILoggedUserInfo | null): Promise<SingleResponseDto<ResponseDtoT>>;
|
|
24
24
|
insertMany(addDto: CreateDtoT[], user: ILoggedUserInfo | null): Promise<BulkResponseDto<ResponseDtoT>>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { DeleteDto, FilterAndPaginationDto } from '
|
|
2
|
-
import { Identity } from '
|
|
3
|
-
import { ILoggedUserInfo, IService } from '
|
|
4
|
-
import { UtilsService } from '
|
|
1
|
+
import { DeleteDto, FilterAndPaginationDto } from '../dtos';
|
|
2
|
+
import { Identity } from '../entities';
|
|
3
|
+
import { ILoggedUserInfo, IService } from '../interfaces';
|
|
4
|
+
import { UtilsService } from '../modules/utils/utils.service';
|
|
5
5
|
import { Logger } from '@nestjs/common';
|
|
6
6
|
import { QueryRunner, Repository, SelectQueryBuilder } from 'typeorm';
|
|
7
7
|
import { HybridCache } from './hybrid-cache.class';
|
|
8
|
-
export declare abstract class ApiService<CreateDtoT extends
|
|
8
|
+
export declare abstract class ApiService<CreateDtoT extends object, UpdateDtoT extends {
|
|
9
9
|
id: string;
|
|
10
10
|
}, InterfaceT extends Identity, EntityT extends Identity, RepositoryT extends Repository<EntityT>> implements IService<CreateDtoT, UpdateDtoT, InterfaceT> {
|
|
11
11
|
protected entityName: string;
|
package/classes/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { DataSource, EntityTarget, Repository } from 'typeorm';
|
|
2
2
|
import { Identity } from '../entities';
|
|
3
|
+
import { IDataSourceProvider } from '../interfaces';
|
|
3
4
|
import { ApiService } from './api-service.class';
|
|
4
|
-
export declare abstract class RequestScopedApiService<CreateDtoT extends
|
|
5
|
+
export declare abstract class RequestScopedApiService<CreateDtoT extends object, UpdateDtoT extends {
|
|
5
6
|
id: string;
|
|
6
7
|
}, InterfaceT extends Identity, EntityT extends Identity, RepositoryT extends Repository<EntityT>> extends ApiService<CreateDtoT, UpdateDtoT, InterfaceT, EntityT, RepositoryT> {
|
|
7
8
|
private repositoryInitialized;
|
|
8
9
|
protected abstract resolveEntity(): EntityTarget<EntityT>;
|
|
9
|
-
protected abstract getDataSourceProvider():
|
|
10
|
+
protected abstract getDataSourceProvider(): IDataSourceProvider;
|
|
10
11
|
protected ensureRepositoryInitialized(): Promise<void>;
|
|
11
12
|
protected initializeAdditionalRepositories(entities: EntityTarget<any>[]): Promise<Repository<any>[]>;
|
|
12
13
|
protected getDataSourceForService(): Promise<DataSource>;
|
package/constants/index.d.ts
CHANGED
|
@@ -8,3 +8,4 @@ export declare const REQUEST_ID_HEADER = "x-request-id";
|
|
|
8
8
|
export declare const CLIENT_TYPE_HEADER = "x-client-type";
|
|
9
9
|
export declare const PERMISSIONS_CACHE_PREFIX = "permissions";
|
|
10
10
|
export declare const IDEMPOTENCY_CACHE_PREFIX = "idempotency";
|
|
11
|
+
export * from './permissions';
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
export declare const USER_PERMISSIONS: {
|
|
2
|
+
readonly CREATE: "user.create";
|
|
3
|
+
readonly READ: "user.read";
|
|
4
|
+
readonly UPDATE: "user.update";
|
|
5
|
+
readonly DELETE: "user.delete";
|
|
6
|
+
};
|
|
7
|
+
export declare const COMPANY_PERMISSIONS: {
|
|
8
|
+
readonly CREATE: "company.create";
|
|
9
|
+
readonly READ: "company.read";
|
|
10
|
+
readonly UPDATE: "company.update";
|
|
11
|
+
readonly DELETE: "company.delete";
|
|
12
|
+
};
|
|
13
|
+
export declare const BRANCH_PERMISSIONS: {
|
|
14
|
+
readonly CREATE: "branch.create";
|
|
15
|
+
readonly READ: "branch.read";
|
|
16
|
+
readonly UPDATE: "branch.update";
|
|
17
|
+
readonly DELETE: "branch.delete";
|
|
18
|
+
};
|
|
19
|
+
export declare const ACTION_PERMISSIONS: {
|
|
20
|
+
readonly CREATE: "action.create";
|
|
21
|
+
readonly READ: "action.read";
|
|
22
|
+
readonly UPDATE: "action.update";
|
|
23
|
+
readonly DELETE: "action.delete";
|
|
24
|
+
};
|
|
25
|
+
export declare const ROLE_PERMISSIONS: {
|
|
26
|
+
readonly CREATE: "role.create";
|
|
27
|
+
readonly READ: "role.read";
|
|
28
|
+
readonly UPDATE: "role.update";
|
|
29
|
+
readonly DELETE: "role.delete";
|
|
30
|
+
};
|
|
31
|
+
export declare const ROLE_ACTION_PERMISSIONS: {
|
|
32
|
+
readonly READ: "role-action.read";
|
|
33
|
+
readonly ASSIGN: "role-action.assign";
|
|
34
|
+
};
|
|
35
|
+
export declare const USER_ROLE_PERMISSIONS: {
|
|
36
|
+
readonly READ: "user-role.read";
|
|
37
|
+
readonly ASSIGN: "user-role.assign";
|
|
38
|
+
};
|
|
39
|
+
export declare const USER_ACTION_PERMISSIONS: {
|
|
40
|
+
readonly READ: "user-action.read";
|
|
41
|
+
readonly ASSIGN: "user-action.assign";
|
|
42
|
+
};
|
|
43
|
+
export declare const COMPANY_ACTION_PERMISSIONS: {
|
|
44
|
+
readonly READ: "company-action.read";
|
|
45
|
+
readonly ASSIGN: "company-action.assign";
|
|
46
|
+
};
|
|
47
|
+
export declare const FILE_PERMISSIONS: {
|
|
48
|
+
readonly CREATE: "file.create";
|
|
49
|
+
readonly READ: "file.read";
|
|
50
|
+
readonly UPDATE: "file.update";
|
|
51
|
+
readonly DELETE: "file.delete";
|
|
52
|
+
};
|
|
53
|
+
export declare const FOLDER_PERMISSIONS: {
|
|
54
|
+
readonly CREATE: "folder.create";
|
|
55
|
+
readonly READ: "folder.read";
|
|
56
|
+
readonly UPDATE: "folder.update";
|
|
57
|
+
readonly DELETE: "folder.delete";
|
|
58
|
+
};
|
|
59
|
+
export declare const STORAGE_CONFIG_PERMISSIONS: {
|
|
60
|
+
readonly CREATE: "storage-config.create";
|
|
61
|
+
readonly READ: "storage-config.read";
|
|
62
|
+
readonly UPDATE: "storage-config.update";
|
|
63
|
+
readonly DELETE: "storage-config.delete";
|
|
64
|
+
};
|
|
65
|
+
export declare const EMAIL_CONFIG_PERMISSIONS: {
|
|
66
|
+
readonly CREATE: "email-config.create";
|
|
67
|
+
readonly READ: "email-config.read";
|
|
68
|
+
readonly UPDATE: "email-config.update";
|
|
69
|
+
readonly DELETE: "email-config.delete";
|
|
70
|
+
};
|
|
71
|
+
export declare const EMAIL_TEMPLATE_PERMISSIONS: {
|
|
72
|
+
readonly CREATE: "email-template.create";
|
|
73
|
+
readonly READ: "email-template.read";
|
|
74
|
+
readonly UPDATE: "email-template.update";
|
|
75
|
+
readonly DELETE: "email-template.delete";
|
|
76
|
+
};
|
|
77
|
+
export declare const FORM_PERMISSIONS: {
|
|
78
|
+
readonly CREATE: "form.create";
|
|
79
|
+
readonly READ: "form.read";
|
|
80
|
+
readonly UPDATE: "form.update";
|
|
81
|
+
readonly DELETE: "form.delete";
|
|
82
|
+
};
|
|
83
|
+
export declare const PERMISSIONS: {
|
|
84
|
+
readonly USER: {
|
|
85
|
+
readonly CREATE: "user.create";
|
|
86
|
+
readonly READ: "user.read";
|
|
87
|
+
readonly UPDATE: "user.update";
|
|
88
|
+
readonly DELETE: "user.delete";
|
|
89
|
+
};
|
|
90
|
+
readonly COMPANY: {
|
|
91
|
+
readonly CREATE: "company.create";
|
|
92
|
+
readonly READ: "company.read";
|
|
93
|
+
readonly UPDATE: "company.update";
|
|
94
|
+
readonly DELETE: "company.delete";
|
|
95
|
+
};
|
|
96
|
+
readonly BRANCH: {
|
|
97
|
+
readonly CREATE: "branch.create";
|
|
98
|
+
readonly READ: "branch.read";
|
|
99
|
+
readonly UPDATE: "branch.update";
|
|
100
|
+
readonly DELETE: "branch.delete";
|
|
101
|
+
};
|
|
102
|
+
readonly ACTION: {
|
|
103
|
+
readonly CREATE: "action.create";
|
|
104
|
+
readonly READ: "action.read";
|
|
105
|
+
readonly UPDATE: "action.update";
|
|
106
|
+
readonly DELETE: "action.delete";
|
|
107
|
+
};
|
|
108
|
+
readonly ROLE: {
|
|
109
|
+
readonly CREATE: "role.create";
|
|
110
|
+
readonly READ: "role.read";
|
|
111
|
+
readonly UPDATE: "role.update";
|
|
112
|
+
readonly DELETE: "role.delete";
|
|
113
|
+
};
|
|
114
|
+
readonly ROLE_ACTION: {
|
|
115
|
+
readonly READ: "role-action.read";
|
|
116
|
+
readonly ASSIGN: "role-action.assign";
|
|
117
|
+
};
|
|
118
|
+
readonly USER_ROLE: {
|
|
119
|
+
readonly READ: "user-role.read";
|
|
120
|
+
readonly ASSIGN: "user-role.assign";
|
|
121
|
+
};
|
|
122
|
+
readonly USER_ACTION: {
|
|
123
|
+
readonly READ: "user-action.read";
|
|
124
|
+
readonly ASSIGN: "user-action.assign";
|
|
125
|
+
};
|
|
126
|
+
readonly COMPANY_ACTION: {
|
|
127
|
+
readonly READ: "company-action.read";
|
|
128
|
+
readonly ASSIGN: "company-action.assign";
|
|
129
|
+
};
|
|
130
|
+
readonly FILE: {
|
|
131
|
+
readonly CREATE: "file.create";
|
|
132
|
+
readonly READ: "file.read";
|
|
133
|
+
readonly UPDATE: "file.update";
|
|
134
|
+
readonly DELETE: "file.delete";
|
|
135
|
+
};
|
|
136
|
+
readonly FOLDER: {
|
|
137
|
+
readonly CREATE: "folder.create";
|
|
138
|
+
readonly READ: "folder.read";
|
|
139
|
+
readonly UPDATE: "folder.update";
|
|
140
|
+
readonly DELETE: "folder.delete";
|
|
141
|
+
};
|
|
142
|
+
readonly STORAGE_CONFIG: {
|
|
143
|
+
readonly CREATE: "storage-config.create";
|
|
144
|
+
readonly READ: "storage-config.read";
|
|
145
|
+
readonly UPDATE: "storage-config.update";
|
|
146
|
+
readonly DELETE: "storage-config.delete";
|
|
147
|
+
};
|
|
148
|
+
readonly EMAIL_CONFIG: {
|
|
149
|
+
readonly CREATE: "email-config.create";
|
|
150
|
+
readonly READ: "email-config.read";
|
|
151
|
+
readonly UPDATE: "email-config.update";
|
|
152
|
+
readonly DELETE: "email-config.delete";
|
|
153
|
+
};
|
|
154
|
+
readonly EMAIL_TEMPLATE: {
|
|
155
|
+
readonly CREATE: "email-template.create";
|
|
156
|
+
readonly READ: "email-template.read";
|
|
157
|
+
readonly UPDATE: "email-template.update";
|
|
158
|
+
readonly DELETE: "email-template.delete";
|
|
159
|
+
};
|
|
160
|
+
readonly FORM: {
|
|
161
|
+
readonly CREATE: "form.create";
|
|
162
|
+
readonly READ: "form.read";
|
|
163
|
+
readonly UPDATE: "form.update";
|
|
164
|
+
readonly DELETE: "form.delete";
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
export type PermissionCode = (typeof PERMISSIONS)[keyof typeof PERMISSIONS][keyof (typeof PERMISSIONS)[keyof typeof PERMISSIONS]];
|
package/decorators/index.d.ts
CHANGED
|
@@ -53,11 +53,4 @@ export declare class ErrorResponseDto {
|
|
|
53
53
|
errors?: ValidationErrorDto[];
|
|
54
54
|
_meta?: RequestMetaDto;
|
|
55
55
|
}
|
|
56
|
-
export declare class ResponsePayloadDto<T> {
|
|
57
|
-
success: boolean;
|
|
58
|
-
message: string;
|
|
59
|
-
data?: T;
|
|
60
|
-
meta?: PaginationMetaDto | BulkMetaDto;
|
|
61
|
-
_meta?: RequestMetaDto;
|
|
62
|
-
}
|
|
63
56
|
export type ApiResponse<T> = SingleResponseDto<T> | ListResponseDto<T> | BulkResponseDto<T> | MessageResponseDto | ErrorResponseDto;
|
|
@@ -25,10 +25,10 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import { CurrentUser, Public, RequirePermission, RequireAnyPermission, RequirePermissionCondition } from '
|
|
29
|
-
import { DeleteDto, FilterAndPaginationDto, GetByIdBodyDto } from '
|
|
30
|
-
import { JwtAuthGuard, PermissionGuard } from '
|
|
31
|
-
import { IdempotencyInterceptor, SetCreatedByOnBody, SetDeletedByOnBody, SetUpdateByOnBody, Slug } from '
|
|
28
|
+
import { CurrentUser, Public, RequirePermission, RequireAnyPermission, RequirePermissionCondition } from '../decorators';
|
|
29
|
+
import { DeleteDto, FilterAndPaginationDto, GetByIdBodyDto } from '../dtos';
|
|
30
|
+
import { JwtAuthGuard, PermissionGuard } from '../guards';
|
|
31
|
+
import { IdempotencyInterceptor, SetCreatedByOnBody, SetDeletedByOnBody, SetUpdateByOnBody, Slug } from '../interceptors';
|
|
32
32
|
import { applyDecorators, Body, HttpCode, HttpStatus, Param, Post, Query, UseGuards, UseInterceptors, Version, VERSION_NEUTRAL } from '@nestjs/common';
|
|
33
33
|
import { ApiBearerAuth, ApiBody, ApiHeader, ApiOperation, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger';
|
|
34
34
|
import { plainToInstance } from 'class-transformer';
|
|
@@ -94,8 +94,10 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
|
|
|
94
94
|
// 2. It's an object with 'level' property but no endpoint keys
|
|
95
95
|
const isGlobalSecurity = typeof securityConfig === 'string' || securityConfig && typeof securityConfig === 'object' && 'level' in securityConfig && !endpointKeys.some((key)=>key in securityConfig);
|
|
96
96
|
// Normalize security config for each endpoint
|
|
97
|
+
// IMPORTANT: When per-endpoint security is specified, default to 'jwt' for unconfigured endpoints
|
|
98
|
+
// to prevent accidentally exposing endpoints without authentication
|
|
97
99
|
const defaultSecurity = isGlobalSecurity ? normalizeSecurity(securityConfig) : {
|
|
98
|
-
level: '
|
|
100
|
+
level: 'jwt'
|
|
99
101
|
};
|
|
100
102
|
const security = {
|
|
101
103
|
insert: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.insert),
|
|
@@ -337,16 +339,7 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
|
|
|
337
339
|
HttpCode(HttpStatus.OK),
|
|
338
340
|
ApiOperation({
|
|
339
341
|
summary: 'Get all items with filters and pagination',
|
|
340
|
-
description:
|
|
341
|
-
Retrieves items with support for:
|
|
342
|
-
- **filter**: Apply field-based filters (e.g., \`{ "isActive": true }\`)
|
|
343
|
-
- **pagination**: Control page and page size
|
|
344
|
-
- **sort**: Order by any field (e.g., \`{ "createdAt": "DESC" }\`)
|
|
345
|
-
- **select**: Choose specific fields to return
|
|
346
|
-
- **withDeleted**: Include soft-deleted items
|
|
347
|
-
- **extraKey**: Include additional relations
|
|
348
|
-
- **q** (query param): Global text search
|
|
349
|
-
`
|
|
342
|
+
description: 'Supports filter, pagination, sort, select, withDeleted, and q (search) params'
|
|
350
343
|
}),
|
|
351
344
|
ApiQuery({
|
|
352
345
|
name: 'q',
|
|
@@ -375,15 +368,7 @@ Retrieves items with support for:
|
|
|
375
368
|
HttpCode(HttpStatus.OK),
|
|
376
369
|
ApiOperation({
|
|
377
370
|
summary: 'Delete, restore, or permanently remove items',
|
|
378
|
-
description:
|
|
379
|
-
Performs one of three actions:
|
|
380
|
-
|
|
381
|
-
- **"delete"** (soft delete): Marks items as deleted but keeps in database
|
|
382
|
-
- **"restore"**: Reverts soft-deleted items to active
|
|
383
|
-
- **"permanent"**: Completely removes items from database
|
|
384
|
-
|
|
385
|
-
Supports single ID or array of IDs for batch operations.
|
|
386
|
-
`
|
|
371
|
+
description: 'Types: delete (soft), restore, permanent. Supports batch IDs.'
|
|
387
372
|
}),
|
|
388
373
|
ApiResponse({
|
|
389
374
|
status: 200,
|
package/fesm/classes/index.js
CHANGED
package/fesm/constants/index.js
CHANGED