@flusys/nestjs-shared 0.1.0-beta.3 → 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.
Files changed (102) hide show
  1. package/README.md +106 -138
  2. package/cjs/classes/api-controller.class.js +9 -45
  3. package/cjs/classes/api-service.class.js +4 -41
  4. package/cjs/classes/index.js +1 -0
  5. package/cjs/classes/request-scoped-api.service.js +4 -53
  6. package/cjs/classes/winston.logger.class.js +5 -15
  7. package/cjs/constants/index.js +16 -11
  8. package/cjs/constants/permissions.js +174 -0
  9. package/cjs/decorators/api-response.decorator.js +1 -1
  10. package/cjs/decorators/index.js +1 -0
  11. package/cjs/decorators/sanitize-html.decorator.js +36 -0
  12. package/cjs/dtos/filter-and-pagination.dto.js +24 -34
  13. package/cjs/dtos/pagination.dto.js +4 -8
  14. package/cjs/dtos/response-payload.dto.js +35 -121
  15. package/cjs/entities/identity.js +4 -4
  16. package/cjs/entities/user-root.js +13 -14
  17. package/cjs/guards/permission.guard.js +39 -94
  18. package/cjs/interceptors/index.js +1 -0
  19. package/cjs/interceptors/set-create-by-on-body.interceptor.js +2 -30
  20. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +2 -30
  21. package/cjs/interceptors/set-update-by-on-body.interceptor.js +2 -30
  22. package/cjs/interceptors/set-user-field-on-body.interceptor.js +43 -0
  23. package/cjs/interceptors/slug.interceptor.js +30 -9
  24. package/cjs/interfaces/datasource.interface.js +4 -0
  25. package/cjs/interfaces/index.js +2 -1
  26. package/cjs/interfaces/logged-user-info.interface.js +1 -2
  27. package/cjs/interfaces/module-config.interface.js +4 -0
  28. package/cjs/interfaces/permission.interface.js +1 -10
  29. package/cjs/middlewares/logger.middleware.js +2 -6
  30. package/cjs/modules/cache/cache.module.js +3 -3
  31. package/cjs/modules/datasource/multi-tenant-datasource.service.js +31 -111
  32. package/cjs/modules/utils/utils.service.js +63 -145
  33. package/cjs/utils/error-handler.util.js +91 -13
  34. package/cjs/utils/html-sanitizer.util.js +74 -0
  35. package/cjs/utils/index.js +2 -0
  36. package/cjs/utils/query-helpers.util.js +53 -0
  37. package/classes/api-controller.class.d.ts +5 -5
  38. package/classes/api-service.class.d.ts +5 -5
  39. package/classes/index.d.ts +1 -0
  40. package/classes/request-scoped-api.service.d.ts +3 -2
  41. package/constants/index.d.ts +1 -0
  42. package/constants/permissions.d.ts +167 -0
  43. package/decorators/index.d.ts +1 -0
  44. package/decorators/sanitize-html.decorator.d.ts +2 -0
  45. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  46. package/dtos/response-payload.dto.d.ts +0 -7
  47. package/fesm/classes/api-controller.class.js +10 -93
  48. package/fesm/classes/api-service.class.js +5 -46
  49. package/fesm/classes/index.js +2 -0
  50. package/fesm/classes/request-scoped-api.service.js +4 -53
  51. package/fesm/classes/winston.logger.class.js +6 -18
  52. package/fesm/constants/index.js +16 -29
  53. package/fesm/constants/permissions.js +121 -0
  54. package/fesm/decorators/api-response.decorator.js +1 -1
  55. package/fesm/decorators/index.js +1 -0
  56. package/fesm/decorators/sanitize-html.decorator.js +45 -0
  57. package/fesm/dtos/filter-and-pagination.dto.js +26 -47
  58. package/fesm/dtos/pagination.dto.js +4 -8
  59. package/fesm/dtos/response-payload.dto.js +39 -142
  60. package/fesm/entities/identity.js +4 -4
  61. package/fesm/entities/user-root.js +13 -14
  62. package/fesm/guards/permission.guard.js +39 -94
  63. package/fesm/interceptors/index.js +1 -0
  64. package/fesm/interceptors/set-create-by-on-body.interceptor.js +4 -30
  65. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +4 -30
  66. package/fesm/interceptors/set-update-by-on-body.interceptor.js +4 -30
  67. package/fesm/interceptors/set-user-field-on-body.interceptor.js +36 -0
  68. package/fesm/interceptors/slug.interceptor.js +31 -10
  69. package/fesm/interfaces/datasource.interface.js +20 -0
  70. package/fesm/interfaces/index.js +2 -1
  71. package/fesm/interfaces/logged-user-info.interface.js +1 -2
  72. package/fesm/interfaces/module-config.interface.js +5 -0
  73. package/fesm/interfaces/permission.interface.js +0 -12
  74. package/fesm/middlewares/logger.middleware.js +2 -6
  75. package/fesm/modules/cache/cache.module.js +2 -2
  76. package/fesm/modules/datasource/multi-tenant-datasource.service.js +31 -111
  77. package/fesm/modules/utils/utils.service.js +50 -143
  78. package/fesm/utils/error-handler.util.js +93 -14
  79. package/fesm/utils/html-sanitizer.util.js +82 -0
  80. package/fesm/utils/index.js +2 -0
  81. package/fesm/utils/query-helpers.util.js +78 -0
  82. package/interceptors/index.d.ts +1 -0
  83. package/interceptors/set-create-by-on-body.interceptor.d.ts +1 -5
  84. package/interceptors/set-delete-by-on-body.interceptor.d.ts +1 -5
  85. package/interceptors/set-update-by-on-body.interceptor.d.ts +1 -5
  86. package/interceptors/set-user-field-on-body.interceptor.d.ts +2 -0
  87. package/interceptors/slug.interceptor.d.ts +2 -1
  88. package/interfaces/api.interface.d.ts +2 -2
  89. package/interfaces/datasource.interface.d.ts +5 -0
  90. package/interfaces/identity.interface.d.ts +4 -4
  91. package/interfaces/index.d.ts +2 -1
  92. package/interfaces/module-config.interface.d.ts +6 -0
  93. package/interfaces/permission.interface.d.ts +0 -1
  94. package/modules/utils/utils.service.d.ts +10 -4
  95. package/package.json +4 -4
  96. package/utils/error-handler.util.d.ts +23 -13
  97. package/utils/html-sanitizer.util.d.ts +3 -0
  98. package/utils/index.d.ts +2 -0
  99. package/utils/query-helpers.util.d.ts +16 -0
  100. package/cjs/interfaces/base-query.interface.js +0 -6
  101. package/fesm/interfaces/base-query.interface.js +0 -3
  102. package/interfaces/base-query.interface.d.ts +0 -7
@@ -1,27 +1,104 @@
1
+ import { HttpStatus } from '@nestjs/common';
2
+ /** Check if running in production environment */ const IS_PRODUCTION = process.env.NODE_ENV === 'production';
3
+ /** Sensitive keys that should be redacted from logs */ const SENSITIVE_KEYS = [
4
+ 'password',
5
+ 'secret',
6
+ 'token',
7
+ 'apiKey',
8
+ 'credential',
9
+ 'authorization'
10
+ ];
11
+ /** Patterns that indicate sensitive data in error messages */ const SENSITIVE_PATTERNS = [
12
+ /password/i,
13
+ /secret/i,
14
+ /token/i,
15
+ /key/i,
16
+ /credential/i,
17
+ /authorization/i,
18
+ /bearer/i
19
+ ];
1
20
  /**
2
- * Error handling utility for consistent error logging and handling
21
+ * Error handling utility for consistent error logging and handling.
22
+ * Provides production-aware error sanitization to prevent sensitive data leakage.
3
23
  */ export class ErrorHandler {
4
24
  /**
5
- * Safely extract error message from unknown error
6
- */ static getErrorMessage(error) {
25
+ * Safely extract error message from unknown error.
26
+ * @param error - The error to extract message from
27
+ * @param sanitizeForClient - If true, redacts potentially sensitive info in production
28
+ */ static getErrorMessage(error, sanitizeForClient = false) {
29
+ let message = 'Unknown error occurred';
7
30
  if (error instanceof Error) {
8
- return error.message;
31
+ message = error.message;
32
+ } else if (typeof error === 'string') {
33
+ message = error;
9
34
  }
10
- if (typeof error === 'string') {
11
- return error;
35
+ // Sanitize for client responses in production
36
+ if (sanitizeForClient && IS_PRODUCTION) {
37
+ if (this.containsSensitiveData(message)) {
38
+ return 'An unexpected error occurred. Please try again later.';
39
+ }
12
40
  }
13
- return 'Unknown error occurred';
41
+ return message;
42
+ }
43
+ /**
44
+ * Check if a string contains potentially sensitive data.
45
+ */ static containsSensitiveData(text) {
46
+ return SENSITIVE_PATTERNS.some((pattern)=>pattern.test(text));
14
47
  }
15
48
  /**
16
- * Safely extract error stack from unknown error
17
- */ static getErrorStack(error) {
49
+ * Safely extract error stack from unknown error.
50
+ * Returns undefined in production for client responses.
51
+ * @param error - The error to extract stack from
52
+ * @param forClient - If true, never returns stack in production
53
+ */ static getErrorStack(error, forClient = false) {
54
+ // Never expose stack traces to clients in production
55
+ if (forClient && IS_PRODUCTION) {
56
+ return undefined;
57
+ }
18
58
  if (error instanceof Error) {
19
59
  return error.stack;
20
60
  }
21
61
  return undefined;
22
62
  }
23
63
  /**
24
- * Create error context object for logging
64
+ * Create a sanitized error response for clients.
65
+ * In production, sensitive data is redacted and stack traces removed.
66
+ */ static createClientError(error, statusCode = HttpStatus.INTERNAL_SERVER_ERROR, code) {
67
+ const sanitizedError = {
68
+ message: this.getErrorMessage(error, true),
69
+ statusCode
70
+ };
71
+ if (code) {
72
+ sanitizedError.code = code;
73
+ }
74
+ // Include stack only in development
75
+ if (!IS_PRODUCTION) {
76
+ sanitizedError.stack = this.getErrorStack(error);
77
+ }
78
+ return sanitizedError;
79
+ }
80
+ /**
81
+ * Sanitize context data to redact sensitive fields from logs.
82
+ */ static sanitizeContextForLogging(context) {
83
+ const sanitized = {};
84
+ for (const [key, value] of Object.entries(context)){
85
+ // Check if key contains sensitive words
86
+ const isSensitive = SENSITIVE_KEYS.some((sk)=>key.toLowerCase().includes(sk.toLowerCase()));
87
+ if (isSensitive) {
88
+ sanitized[key] = '[REDACTED]';
89
+ } else if (Array.isArray(value)) {
90
+ sanitized[key] = value.map((item)=>typeof item === 'object' && item !== null ? this.sanitizeContextForLogging(item) : item);
91
+ } else if (typeof value === 'object' && value !== null) {
92
+ sanitized[key] = this.sanitizeContextForLogging(value);
93
+ } else {
94
+ sanitized[key] = value;
95
+ }
96
+ }
97
+ return sanitized;
98
+ }
99
+ /**
100
+ * Create error context object for internal logging.
101
+ * Context data is sanitized to redact sensitive fields.
25
102
  */ static createErrorContext(error, context) {
26
103
  const errorContext = {
27
104
  error: {
@@ -33,20 +110,22 @@
33
110
  errorContext.error.name = error.name;
34
111
  }
35
112
  if (context && Object.keys(context).length > 0) {
36
- errorContext.context = context;
113
+ // Sanitize context to redact sensitive fields
114
+ errorContext.context = this.sanitizeContextForLogging(context);
37
115
  }
38
116
  return errorContext;
39
117
  }
40
118
  /**
41
- * Log error with consistent format
119
+ * Log error with consistent format.
120
+ * Sensitive data in context is automatically redacted.
42
121
  */ static logError(logger, error, operation, context) {
43
122
  const errorContext = this.createErrorContext(error, {
44
123
  operation,
45
124
  ...context
46
125
  });
47
126
  const errorMessage = `Failed to ${operation}: ${errorContext.error.message}`;
48
- const loggerContext = logger.context || 'ErrorHandler';
49
- logger.error(errorMessage, errorContext.error.stack, loggerContext, errorContext);
127
+ // Log full details internally (stack traces are fine for internal logs)
128
+ logger.error(errorMessage, errorContext.error.stack, errorContext);
50
129
  }
51
130
  /**
52
131
  * Re-throw error with proper type checking
@@ -0,0 +1,82 @@
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
+ */ const HTML_ESCAPE_MAP = {
9
+ '&': '&',
10
+ '<': '&lt;',
11
+ '>': '&gt;',
12
+ '"': '&quot;',
13
+ "'": '&#x27;',
14
+ '/': '&#x2F;',
15
+ '`': '&#x60;',
16
+ '=': '&#x3D;'
17
+ };
18
+ /**
19
+ * Regex pattern matching characters that need HTML escaping
20
+ */ const HTML_ESCAPE_REGEX = /[&<>"'`=/]/g;
21
+ /**
22
+ * Escapes HTML special characters to prevent XSS attacks.
23
+ *
24
+ * @param str - The string to escape
25
+ * @returns The escaped string safe for HTML insertion
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * escapeHtml('<script>alert("xss")</script>')
30
+ * // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
31
+ * ```
32
+ */ export function escapeHtml(str) {
33
+ if (!str || typeof str !== 'string') {
34
+ return str ?? '';
35
+ }
36
+ return str.replace(HTML_ESCAPE_REGEX, (char)=>HTML_ESCAPE_MAP[char] || char);
37
+ }
38
+ /**
39
+ * Escapes all string values in a variables object for safe HTML interpolation.
40
+ * Non-string values are converted to strings and escaped.
41
+ *
42
+ * @param variables - Object containing template variables
43
+ * @returns New object with all string values HTML-escaped
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * escapeHtmlVariables({ name: '<script>xss</script>', count: 5 })
48
+ * // Returns: { name: '&lt;script&gt;xss&lt;/script&gt;', count: '5' }
49
+ * ```
50
+ */ export function escapeHtmlVariables(variables) {
51
+ if (!variables || typeof variables !== 'object') {
52
+ return {};
53
+ }
54
+ const escaped = {};
55
+ for (const [key, value] of Object.entries(variables)){
56
+ if (value === null || value === undefined) {
57
+ escaped[key] = '';
58
+ } else if (typeof value === 'string') {
59
+ escaped[key] = escapeHtml(value);
60
+ } else if (typeof value === 'object') {
61
+ // For objects/arrays, stringify and escape
62
+ escaped[key] = escapeHtml(JSON.stringify(value));
63
+ } else {
64
+ // For numbers, booleans, etc., convert to string (no escaping needed)
65
+ escaped[key] = String(value);
66
+ }
67
+ }
68
+ return escaped;
69
+ }
70
+ /**
71
+ * Checks if a string contains potential HTML/script injection.
72
+ * Useful for logging or validation purposes.
73
+ *
74
+ * @param str - The string to check
75
+ * @returns True if the string contains HTML-like content
76
+ */ export function containsHtmlContent(str) {
77
+ if (!str || typeof str !== 'string') {
78
+ return false;
79
+ }
80
+ // Check for common HTML patterns
81
+ return /<[a-z][\s\S]*>/i.test(str) || /javascript:/i.test(str) || /on\w+=/i.test(str);
82
+ }
@@ -1 +1,3 @@
1
1
  export * from './error-handler.util';
2
+ export * from './html-sanitizer.util';
3
+ export * from './query-helpers.util';
@@ -0,0 +1,78 @@
1
+ import { BadRequestException } from '@nestjs/common';
2
+ /**
3
+ * Apply company filter to a query builder if company feature is enabled.
4
+ * Centralizes the common pattern of filtering by user's company.
5
+ *
6
+ * @param query - TypeORM SelectQueryBuilder
7
+ * @param config - Company filter configuration
8
+ * @param user - Current logged user (may be null)
9
+ * @returns The modified query builder
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * applyCompanyFilter(query, {
14
+ * isCompanyFeatureEnabled: this.config.isCompanyFeatureEnabled(),
15
+ * entityAlias: 'file_manager',
16
+ * }, user);
17
+ * ```
18
+ */ export function applyCompanyFilter(query, config, user) {
19
+ const columnName = config.columnName ?? 'companyId';
20
+ if (config.isCompanyFeatureEnabled && user?.companyId) {
21
+ query.andWhere(`${config.entityAlias}.${columnName} = :companyId`, {
22
+ companyId: user.companyId
23
+ });
24
+ }
25
+ return query;
26
+ }
27
+ /**
28
+ * Build a where condition object with optional company filter.
29
+ *
30
+ * @param baseWhere - Base where condition object
31
+ * @param isCompanyFeatureEnabled - Whether company feature is enabled
32
+ * @param user - Current logged user
33
+ * @returns Where condition with companyId added if applicable
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const where = buildCompanyWhereCondition(
38
+ * { id: dto.id },
39
+ * this.config.isCompanyFeatureEnabled(),
40
+ * user
41
+ * );
42
+ * // Returns { id: dto.id, companyId: user.companyId } if company feature enabled
43
+ * ```
44
+ */ export function buildCompanyWhereCondition(baseWhere, isCompanyFeatureEnabled, user) {
45
+ if (isCompanyFeatureEnabled && user?.companyId) {
46
+ return {
47
+ ...baseWhere,
48
+ companyId: user.companyId
49
+ };
50
+ }
51
+ return baseWhere;
52
+ }
53
+ /**
54
+ * Type guard to check if entity has companyId property
55
+ */ export function hasCompanyId(entity) {
56
+ return entity !== null && typeof entity === 'object' && 'companyId' in entity;
57
+ }
58
+ /**
59
+ * Validates that an entity with companyId belongs to the user's company.
60
+ * Throws BadRequestException if the entity belongs to another company.
61
+ *
62
+ * @param entity - Entity to validate
63
+ * @param user - Current logged user
64
+ * @param isCompanyFeatureEnabled - Whether company feature is enabled
65
+ * @param entityName - Name of entity for error message (e.g., 'Email configuration')
66
+ * @throws BadRequestException if entity belongs to another company
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * validateCompanyOwnership(config, user, this.config.isCompanyFeatureEnabled(), 'Email configuration');
71
+ * ```
72
+ */ export function validateCompanyOwnership(entity, user, isCompanyFeatureEnabled, entityName) {
73
+ if (isCompanyFeatureEnabled && user?.companyId && hasCompanyId(entity)) {
74
+ if (entity.companyId && entity.companyId !== user.companyId) {
75
+ throw new BadRequestException(`${entityName} belongs to another company`);
76
+ }
77
+ }
78
+ }
@@ -2,6 +2,7 @@ export * from './delete-empty-id-from-body.interceptor';
2
2
  export * from './idempotency.interceptor';
3
3
  export * from './query-performance.interceptor';
4
4
  export * from './response-meta.interceptor';
5
+ export * from './set-user-field-on-body.interceptor';
5
6
  export * from './set-create-by-on-body.interceptor';
6
7
  export * from './set-delete-by-on-body.interceptor';
7
8
  export * from './set-update-by-on-body.interceptor';
@@ -1,5 +1 @@
1
- import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
2
- import { Observable } from 'rxjs';
3
- export declare class SetCreatedByOnBody implements NestInterceptor {
4
- intercept<T = any>(context: ExecutionContext, next: CallHandler<T>): Observable<T>;
5
- }
1
+ export declare const SetCreatedByOnBody: import("@nestjs/common").Type<import("@nestjs/common").NestInterceptor<any, any>>;
@@ -1,5 +1 @@
1
- import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
2
- import { Observable } from 'rxjs';
3
- export declare class SetDeletedByOnBody implements NestInterceptor {
4
- intercept<T = any>(context: ExecutionContext, next: CallHandler<T>): Observable<T>;
5
- }
1
+ export declare const SetDeletedByOnBody: import("@nestjs/common").Type<import("@nestjs/common").NestInterceptor<any, any>>;
@@ -1,5 +1 @@
1
- import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
2
- import { Observable } from 'rxjs';
3
- export declare class SetUpdateByOnBody implements NestInterceptor {
4
- intercept<T = any>(context: ExecutionContext, next: CallHandler<T>): Observable<T>;
5
- }
1
+ export declare const SetUpdateByOnBody: import("@nestjs/common").Type<import("@nestjs/common").NestInterceptor<any, any>>;
@@ -0,0 +1,2 @@
1
+ import { NestInterceptor, Type } from '@nestjs/common';
2
+ export declare function createSetUserFieldInterceptor(fieldName: string): Type<NestInterceptor>;
@@ -2,6 +2,7 @@ import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
2
2
  import { Observable } from 'rxjs';
3
3
  import { UtilsService } from '../modules/utils/utils.service';
4
4
  export declare class Slug implements NestInterceptor {
5
- utilsService: UtilsService;
5
+ private readonly utilsService;
6
+ constructor(utilsService: UtilsService);
6
7
  intercept<T = any>(context: ExecutionContext, next: CallHandler<T>): Observable<T>;
7
8
  }
@@ -1,5 +1,5 @@
1
- import { DeleteDto, FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
2
- import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
1
+ import { DeleteDto, FilterAndPaginationDto } from '../dtos';
2
+ import { ILoggedUserInfo } from './logged-user-info.interface';
3
3
  export interface IService<CreateDtoT, UpdateDtoT, InterfaceT> {
4
4
  insert(addDto: CreateDtoT, user: ILoggedUserInfo | null): Promise<InterfaceT>;
5
5
  insertMany(addDto: Array<CreateDtoT>, user: ILoggedUserInfo | null): Promise<Array<InterfaceT>>;
@@ -0,0 +1,5 @@
1
+ import { DataSource, EntityTarget, ObjectLiteral, Repository } from 'typeorm';
2
+ export interface IDataSourceProvider {
3
+ getRepository<T extends ObjectLiteral>(entity: EntityTarget<T>): Promise<Repository<T>>;
4
+ getDataSource(): Promise<DataSource>;
5
+ }
@@ -2,8 +2,8 @@ export interface IIdentity {
2
2
  id: string;
3
3
  createdAt: Date;
4
4
  updatedAt: Date;
5
- deletedAt: Date;
6
- createdById: string;
7
- updatedById: string;
8
- deletedById: string;
5
+ deletedAt: Date | null;
6
+ createdById: string | null;
7
+ updatedById: string | null;
8
+ deletedById: string | null;
9
9
  }
@@ -1,6 +1,7 @@
1
1
  export * from './api.interface';
2
- export * from './base-query.interface';
2
+ export * from './datasource.interface';
3
3
  export * from './identity.interface';
4
4
  export * from './logged-user-info.interface';
5
5
  export * from './logger.interface';
6
+ export * from './module-config.interface';
6
7
  export * from './permission.interface';
@@ -0,0 +1,6 @@
1
+ import { DatabaseMode } from '@flusys/nestjs-core';
2
+ export interface IModuleConfigService {
3
+ getDatabaseMode(): DatabaseMode;
4
+ isMultiTenant(): boolean;
5
+ isCompanyFeatureEnabled(): boolean;
6
+ }
@@ -11,7 +11,6 @@ export interface LegacyPermissionConfig {
11
11
  export type PermissionConfig = PermissionCondition | LegacyPermissionConfig | string[];
12
12
  export interface PermissionGuardConfig {
13
13
  enableCompanyFeature?: boolean;
14
- cacheKeyPrefix?: string;
15
14
  userPermissionKeyFormat?: string;
16
15
  companyPermissionKeyFormat?: string;
17
16
  }
@@ -1,6 +1,14 @@
1
- import { HybridCache } from '@flusys/nestjs-shared/classes';
1
+ import { HybridCache } from '../../classes/hybrid-cache.class';
2
+ export declare const DEFAULT_PHONE_REGEX: RegExp;
3
+ export declare const DEFAULT_PHONE_COUNTRY_CODE = "+88";
4
+ export interface PhoneValidationConfig {
5
+ regex: RegExp;
6
+ countryCode: string;
7
+ }
2
8
  export declare class UtilsService {
3
- constructor();
9
+ private readonly logger;
10
+ private phoneConfig;
11
+ setPhoneValidationConfig(config: PhoneValidationConfig): void;
4
12
  getCacheKey(entityName: string, params: any, entityId?: string, tenantId?: string): string;
5
13
  trackCacheKey(cacheKey: string, entityName: string, cacheManager: HybridCache, entityId?: string, tenantId?: string): Promise<void>;
6
14
  clearCache(entityName: string, cacheManager: HybridCache, entityId?: string, tenantId?: string): Promise<void>;
@@ -16,6 +24,4 @@ export declare class UtilsService {
16
24
  columnName: string;
17
25
  value: string;
18
26
  };
19
- getOtpEmailFormat(otp: number, userName?: string | null | undefined): string;
20
- getResetPasswordEmailFormat(resetLink: string, userName?: string | null | undefined): string;
21
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-shared",
3
- "version": "0.1.0-beta.3",
3
+ "version": "1.0.0-rc",
4
4
  "description": "Common shared utilities for Flusys NestJS applications",
5
5
  "main": "cjs/index.js",
6
6
  "module": "fesm/index.js",
@@ -88,7 +88,7 @@
88
88
  }
89
89
  },
90
90
  "peerDependencies": {
91
- "@keyv/redis": "^3.0.0",
91
+ "@keyv/redis": "^5.0.0",
92
92
  "@nestjs/common": "^10.0.0 || ^11.0.0",
93
93
  "@nestjs/config": "^3.0.0 || ^4.0.0",
94
94
  "@nestjs/core": "^10.0.0 || ^11.0.0",
@@ -96,7 +96,7 @@
96
96
  "@nestjs/passport": "^10.0.0 || ^11.0.0",
97
97
  "@nestjs/swagger": "^7.0.0 || ^11.0.0",
98
98
  "@nestjs/typeorm": "^10.0.0 || ^11.0.0",
99
- "cacheable": "^1.0.0",
99
+ "cacheable": "^2.0.0",
100
100
  "class-transformer": "^0.5.0",
101
101
  "class-validator": "^0.14.0",
102
102
  "keyv": "^5.0.0",
@@ -105,6 +105,6 @@
105
105
  "winston-daily-rotate-file": "^5.0.0"
106
106
  },
107
107
  "dependencies": {
108
- "@flusys/nestjs-core": "0.1.0-beta.3"
108
+ "@flusys/nestjs-core": "1.0.0-rc"
109
109
  }
110
110
  }
@@ -1,13 +1,27 @@
1
1
  import { Logger } from '@nestjs/common';
2
+ export interface IErrorContext {
3
+ operation?: string;
4
+ entity?: string;
5
+ userId?: string;
6
+ id?: string;
7
+ companyId?: string;
8
+ branchId?: string;
9
+ sectionId?: string;
10
+ data?: Record<string, unknown>;
11
+ }
12
+ export interface ISanitizedError {
13
+ message: string;
14
+ code?: string;
15
+ statusCode?: number;
16
+ stack?: string;
17
+ }
2
18
  export declare class ErrorHandler {
3
- static getErrorMessage(error: unknown): string;
4
- static getErrorStack(error: unknown): string | undefined;
5
- static createErrorContext(error: unknown, context?: {
6
- operation?: string;
7
- entity?: string;
8
- userId?: string;
9
- [key: string]: unknown;
10
- }): {
19
+ static getErrorMessage(error: unknown, sanitizeForClient?: boolean): string;
20
+ private static containsSensitiveData;
21
+ static getErrorStack(error: unknown, forClient?: boolean): string | undefined;
22
+ static createClientError(error: unknown, statusCode?: number, code?: string): ISanitizedError;
23
+ private static sanitizeContextForLogging;
24
+ static createErrorContext(error: unknown, context?: IErrorContext): {
11
25
  error: {
12
26
  message: string;
13
27
  stack?: string;
@@ -15,10 +29,6 @@ export declare class ErrorHandler {
15
29
  };
16
30
  context?: Record<string, unknown>;
17
31
  };
18
- static logError(logger: Logger, error: unknown, operation: string, context?: {
19
- entity?: string;
20
- userId?: string;
21
- [key: string]: unknown;
22
- }): void;
32
+ static logError(logger: Logger, error: unknown, operation: string, context?: Omit<IErrorContext, 'operation'>): void;
23
33
  static rethrowError(error: unknown): never;
24
34
  }
@@ -0,0 +1,3 @@
1
+ export declare function escapeHtml(str: string): string;
2
+ export declare function escapeHtmlVariables(variables: Record<string, unknown>): Record<string, string>;
3
+ export declare function containsHtmlContent(str: string): boolean;
package/utils/index.d.ts CHANGED
@@ -1 +1,3 @@
1
1
  export * from './error-handler.util';
2
+ export * from './html-sanitizer.util';
3
+ export * from './query-helpers.util';
@@ -0,0 +1,16 @@
1
+ import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
2
+ import { ILoggedUserInfo } from '../interfaces';
3
+ export interface ICompanyFilterConfig {
4
+ isCompanyFeatureEnabled: boolean;
5
+ entityAlias: string;
6
+ columnName?: string;
7
+ }
8
+ export declare function applyCompanyFilter<T extends ObjectLiteral>(query: SelectQueryBuilder<T>, config: ICompanyFilterConfig, user: ILoggedUserInfo | null | undefined): SelectQueryBuilder<T>;
9
+ export declare function buildCompanyWhereCondition<T extends Record<string, unknown>>(baseWhere: T, isCompanyFeatureEnabled: boolean, user: ILoggedUserInfo | null | undefined): T & {
10
+ companyId?: string;
11
+ };
12
+ export interface ICompanyEnabled {
13
+ companyId?: string | null;
14
+ }
15
+ export declare function hasCompanyId<T>(entity: T): entity is T & ICompanyEnabled;
16
+ export declare function validateCompanyOwnership<T>(entity: T, user: ILoggedUserInfo | null | undefined, isCompanyFeatureEnabled: boolean, entityName: string): void;
@@ -1,6 +0,0 @@
1
- /**
2
- * Base query DTO interface for list/search endpoints
3
- */ "use strict";
4
- Object.defineProperty(exports, "__esModule", {
5
- value: true
6
- });
@@ -1,3 +0,0 @@
1
- /**
2
- * Base query DTO interface for list/search endpoints
3
- */ export { };
@@ -1,7 +0,0 @@
1
- export interface IBaseQueryDto {
2
- search?: string;
3
- sortBy?: string;
4
- sortOrder?: 'ASC' | 'DESC';
5
- page?: number;
6
- limit?: number;
7
- }