@flusys/nestjs-shared 1.1.0-beta → 1.2.0-rc.1

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 (198) hide show
  1. package/README.md +146 -1330
  2. package/cjs/classes/api-controller.class.js +325 -49
  3. package/cjs/classes/api-service.class.js +271 -149
  4. package/cjs/classes/handle-custome-db.js +37 -0
  5. package/cjs/classes/index.js +2 -2
  6. package/cjs/classes/winston.logger.class.js +103 -70
  7. package/cjs/constants/index.js +15 -4
  8. package/cjs/constants/message-keys.js +68 -0
  9. package/cjs/constants/permissions.js +287 -0
  10. package/cjs/decorators/api-response.decorator.js +2 -3
  11. package/cjs/decorators/index.js +1 -0
  12. package/cjs/decorators/log-action.decorator.js +149 -0
  13. package/cjs/decorators/require-permission.decorator.js +5 -5
  14. package/cjs/dtos/delete.dto.js +10 -0
  15. package/cjs/dtos/filter-and-pagination.dto.js +24 -34
  16. package/cjs/dtos/get-by-ids.dto.js +65 -0
  17. package/cjs/dtos/identity-response.dto.js +4 -8
  18. package/cjs/dtos/index.js +1 -0
  19. package/cjs/dtos/pagination.dto.js +4 -8
  20. package/cjs/dtos/response-payload.dto.js +68 -112
  21. package/cjs/entities/identity.js +12 -6
  22. package/cjs/entities/index.js +1 -0
  23. package/cjs/entities/raw-type.js +4 -0
  24. package/cjs/entities/user-root.js +15 -15
  25. package/cjs/enums/index.js +20 -0
  26. package/cjs/enums/notification-type.enum.js +17 -0
  27. package/cjs/enums/participant-status.enum.js +17 -0
  28. package/cjs/enums/recurrence-type.enum.js +18 -0
  29. package/cjs/exceptions/base-app.exception.js +151 -0
  30. package/cjs/exceptions/index.js +1 -0
  31. package/cjs/exceptions/permission.exception.js +13 -9
  32. package/cjs/filters/global-exception.filter.js +279 -0
  33. package/cjs/filters/index.js +18 -0
  34. package/cjs/guards/jwt-auth.guard.js +4 -1
  35. package/cjs/guards/permission.guard.js +88 -148
  36. package/cjs/index.js +2 -0
  37. package/cjs/interceptors/idempotency.interceptor.js +1 -1
  38. package/cjs/interceptors/index.js +1 -4
  39. package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
  40. package/cjs/interceptors/slug.interceptor.js +30 -9
  41. package/cjs/interfaces/datasource.interface.js +4 -0
  42. package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
  43. package/cjs/interfaces/index.js +4 -2
  44. package/cjs/interfaces/module-config.interface.js +4 -0
  45. package/cjs/interfaces/notification-adapter.interface.js +11 -0
  46. package/cjs/interfaces/permission.interface.js +1 -1
  47. package/cjs/middlewares/logger.middleware.js +133 -115
  48. package/cjs/modules/cache/cache.module.js +3 -3
  49. package/cjs/modules/datasource/index.js +0 -1
  50. package/cjs/modules/datasource/multi-tenant-datasource.service.js +60 -183
  51. package/cjs/modules/utils/utils.service.js +27 -206
  52. package/cjs/utils/date-time.util.js +94 -0
  53. package/cjs/utils/html-sanitizer.util.js +64 -0
  54. package/cjs/utils/index.js +5 -1
  55. package/cjs/utils/query-helpers.util.js +60 -0
  56. package/cjs/utils/request.util.js +96 -0
  57. package/cjs/utils/string.util.js +63 -0
  58. package/classes/api-controller.class.d.ts +16 -8
  59. package/classes/api-service.class.d.ts +30 -25
  60. package/classes/handle-custome-db.d.ts +3 -0
  61. package/classes/index.d.ts +2 -2
  62. package/classes/winston.logger.class.d.ts +1 -0
  63. package/constants/index.d.ts +2 -1
  64. package/constants/message-keys.d.ts +45 -0
  65. package/constants/permissions.d.ts +306 -0
  66. package/decorators/api-response.decorator.d.ts +1 -1
  67. package/decorators/current-user.decorator.d.ts +1 -1
  68. package/decorators/index.d.ts +1 -0
  69. package/decorators/log-action.decorator.d.ts +8 -0
  70. package/decorators/require-permission.decorator.d.ts +2 -2
  71. package/dtos/delete.dto.d.ts +1 -0
  72. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  73. package/dtos/get-by-ids.dto.d.ts +4 -0
  74. package/dtos/index.d.ts +1 -0
  75. package/dtos/response-payload.dto.d.ts +8 -20
  76. package/entities/index.d.ts +1 -0
  77. package/entities/raw-type.d.ts +1 -0
  78. package/enums/index.d.ts +3 -0
  79. package/enums/notification-type.enum.d.ts +6 -0
  80. package/enums/participant-status.enum.d.ts +6 -0
  81. package/enums/recurrence-type.enum.d.ts +7 -0
  82. package/exceptions/base-app.exception.d.ts +41 -0
  83. package/exceptions/index.d.ts +1 -0
  84. package/exceptions/permission.exception.d.ts +2 -2
  85. package/fesm/classes/api-controller.class.js +326 -50
  86. package/fesm/classes/api-service.class.js +274 -152
  87. package/fesm/classes/handle-custome-db.js +19 -0
  88. package/fesm/classes/index.js +3 -2
  89. package/fesm/classes/winston.logger.class.js +100 -68
  90. package/fesm/constants/index.js +4 -1
  91. package/fesm/constants/message-keys.js +49 -0
  92. package/fesm/constants/permissions.js +200 -0
  93. package/fesm/decorators/api-response.decorator.js +3 -21
  94. package/fesm/decorators/index.js +1 -0
  95. package/fesm/decorators/log-action.decorator.js +139 -0
  96. package/fesm/decorators/require-permission.decorator.js +17 -65
  97. package/fesm/dtos/delete.dto.js +12 -2
  98. package/fesm/dtos/filter-and-pagination.dto.js +26 -47
  99. package/fesm/dtos/get-by-ids.dto.js +55 -0
  100. package/fesm/dtos/identity-response.dto.js +4 -8
  101. package/fesm/dtos/index.js +1 -0
  102. package/fesm/dtos/pagination.dto.js +4 -8
  103. package/fesm/dtos/response-payload.dto.js +68 -103
  104. package/fesm/entities/identity.js +12 -6
  105. package/fesm/entities/index.js +1 -0
  106. package/fesm/entities/raw-type.js +1 -0
  107. package/fesm/entities/user-root.js +15 -15
  108. package/fesm/enums/index.js +3 -0
  109. package/fesm/enums/notification-type.enum.js +7 -0
  110. package/fesm/enums/participant-status.enum.js +7 -0
  111. package/fesm/enums/recurrence-type.enum.js +8 -0
  112. package/fesm/exceptions/base-app.exception.js +115 -0
  113. package/fesm/exceptions/index.js +1 -0
  114. package/fesm/exceptions/permission.exception.js +16 -18
  115. package/fesm/filters/global-exception.filter.js +269 -0
  116. package/fesm/filters/index.js +1 -0
  117. package/fesm/guards/jwt-auth.guard.js +5 -2
  118. package/fesm/guards/permission.guard.js +90 -150
  119. package/fesm/index.js +2 -0
  120. package/fesm/interceptors/idempotency.interceptor.js +2 -2
  121. package/fesm/interceptors/index.js +1 -4
  122. package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
  123. package/fesm/interceptors/slug.interceptor.js +31 -10
  124. package/fesm/interfaces/datasource.interface.js +1 -0
  125. package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
  126. package/fesm/interfaces/index.js +4 -2
  127. package/fesm/interfaces/module-config.interface.js +5 -0
  128. package/fesm/interfaces/notification-adapter.interface.js +1 -0
  129. package/fesm/interfaces/permission.interface.js +1 -3
  130. package/fesm/middlewares/logger.middleware.js +133 -109
  131. package/fesm/modules/cache/cache.module.js +2 -2
  132. package/fesm/modules/datasource/index.js +0 -1
  133. package/fesm/modules/datasource/multi-tenant-datasource.service.js +61 -184
  134. package/fesm/modules/utils/utils.service.js +27 -206
  135. package/fesm/utils/date-time.util.js +70 -0
  136. package/fesm/utils/html-sanitizer.util.js +69 -0
  137. package/fesm/utils/index.js +5 -1
  138. package/fesm/utils/query-helpers.util.js +85 -0
  139. package/fesm/utils/request.util.js +81 -0
  140. package/fesm/utils/string.util.js +71 -0
  141. package/filters/global-exception.filter.d.ts +9 -0
  142. package/filters/index.d.ts +1 -0
  143. package/guards/permission.guard.d.ts +4 -6
  144. package/index.d.ts +2 -0
  145. package/interceptors/idempotency.interceptor.d.ts +1 -1
  146. package/interceptors/index.d.ts +1 -4
  147. package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
  148. package/interceptors/slug.interceptor.d.ts +2 -1
  149. package/interfaces/api.interface.d.ts +4 -2
  150. package/interfaces/datasource.interface.d.ts +5 -0
  151. package/interfaces/event-manager-adapter.interface.d.ts +43 -0
  152. package/interfaces/identity.interface.d.ts +4 -4
  153. package/interfaces/index.d.ts +4 -2
  154. package/interfaces/logged-user-info.interface.d.ts +0 -2
  155. package/interfaces/module-config.interface.d.ts +6 -0
  156. package/interfaces/notification-adapter.interface.d.ts +21 -0
  157. package/interfaces/permission.interface.d.ts +13 -9
  158. package/middlewares/logger.middleware.d.ts +2 -2
  159. package/modules/datasource/index.d.ts +0 -1
  160. package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -16
  161. package/modules/utils/utils.service.d.ts +3 -14
  162. package/package.json +21 -14
  163. package/utils/date-time.util.d.ts +8 -0
  164. package/utils/html-sanitizer.util.d.ts +2 -0
  165. package/utils/index.d.ts +5 -1
  166. package/utils/query-helpers.util.d.ts +16 -0
  167. package/utils/request.util.d.ts +6 -0
  168. package/utils/string.util.d.ts +2 -0
  169. package/cjs/classes/request-scoped-api.service.js +0 -61
  170. package/cjs/classes/winston-logger-adapter.class.js +0 -121
  171. package/cjs/interceptors/query-performance.interceptor.js +0 -66
  172. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
  173. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
  174. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
  175. package/cjs/interfaces/base-query.interface.js +0 -6
  176. package/cjs/interfaces/logger.interface.js +0 -7
  177. package/cjs/modules/datasource/datasource.module.js +0 -120
  178. package/cjs/utils/error-handler.util.js +0 -67
  179. package/classes/request-scoped-api.service.d.ts +0 -13
  180. package/classes/winston-logger-adapter.class.d.ts +0 -20
  181. package/fesm/classes/request-scoped-api.service.js +0 -51
  182. package/fesm/classes/winston-logger-adapter.class.js +0 -124
  183. package/fesm/interceptors/query-performance.interceptor.js +0 -56
  184. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
  185. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
  186. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
  187. package/fesm/interfaces/base-query.interface.js +0 -3
  188. package/fesm/interfaces/logger.interface.js +0 -4
  189. package/fesm/modules/datasource/datasource.module.js +0 -110
  190. package/fesm/utils/error-handler.util.js +0 -59
  191. package/interceptors/query-performance.interceptor.d.ts +0 -8
  192. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
  193. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
  194. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
  195. package/interfaces/base-query.interface.d.ts +0 -7
  196. package/interfaces/logger.interface.d.ts +0 -7
  197. package/modules/datasource/datasource.module.d.ts +0 -18
  198. package/utils/error-handler.util.d.ts +0 -24
@@ -0,0 +1,94 @@
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 formatInTimezone () {
13
+ return formatInTimezone;
14
+ },
15
+ get getTodayInTimezone () {
16
+ return getTodayInTimezone;
17
+ },
18
+ get localToUtc () {
19
+ return localToUtc;
20
+ },
21
+ get utcToLocal () {
22
+ return utcToLocal;
23
+ }
24
+ });
25
+ function getOffsetMinutes(utcDate, timezone) {
26
+ const extract = (tz)=>{
27
+ const parts = new Intl.DateTimeFormat('en-US', {
28
+ timeZone: tz,
29
+ year: 'numeric',
30
+ month: '2-digit',
31
+ day: '2-digit',
32
+ hour: '2-digit',
33
+ minute: '2-digit',
34
+ second: '2-digit',
35
+ hour12: false
36
+ }).formatToParts(utcDate);
37
+ const p = Object.fromEntries(parts.filter((x)=>x.type !== 'literal').map((x)=>[
38
+ x.type,
39
+ parseInt(x.value)
40
+ ]));
41
+ return Date.UTC(p['year'], p['month'] - 1, p['day'], p['hour'], p['minute'], p['second']);
42
+ };
43
+ return (extract(timezone) - extract('UTC')) / 60_000;
44
+ }
45
+ function localToUtc(dateStr, timeStr, timezone) {
46
+ const [year, month, day] = dateStr.split('-').map(Number);
47
+ const [hours, minutes] = timeStr.split(':').map(Number);
48
+ const approx = new Date(Date.UTC(year, month - 1, day, hours, minutes, 0));
49
+ const offset = getOffsetMinutes(approx, timezone);
50
+ const utcDate = new Date(approx.getTime() - offset * 60_000);
51
+ // DST boundary guard: re-check offset after adjustment
52
+ const finalOffset = getOffsetMinutes(utcDate, timezone);
53
+ return finalOffset !== offset ? new Date(approx.getTime() - finalOffset * 60_000) : utcDate;
54
+ }
55
+ function utcToLocal(utcDate, timezone) {
56
+ const d = typeof utcDate === 'string' ? new Date(utcDate) : utcDate;
57
+ const parts = new Intl.DateTimeFormat('en-CA', {
58
+ timeZone: timezone,
59
+ year: 'numeric',
60
+ month: '2-digit',
61
+ day: '2-digit',
62
+ hour: '2-digit',
63
+ minute: '2-digit',
64
+ hour12: false
65
+ }).formatToParts(d);
66
+ const p = Object.fromEntries(parts.filter((x)=>x.type !== 'literal').map((x)=>[
67
+ x.type,
68
+ x.value
69
+ ]));
70
+ // en-CA formats midnight hour as '24' — normalize to '00'
71
+ const hour = p['hour'] === '24' ? '00' : p['hour'];
72
+ return {
73
+ date: `${p['year']}-${p['month']}-${p['day']}`,
74
+ time: `${hour}:${p['minute']}`,
75
+ display: new Intl.DateTimeFormat('en-US', {
76
+ timeZone: timezone,
77
+ dateStyle: 'medium',
78
+ timeStyle: 'short'
79
+ }).format(d)
80
+ };
81
+ }
82
+ function formatInTimezone(utcDate, timezone, options = {
83
+ dateStyle: 'medium',
84
+ timeStyle: 'short'
85
+ }) {
86
+ const d = typeof utcDate === 'string' ? new Date(utcDate) : utcDate;
87
+ return new Intl.DateTimeFormat('en-US', {
88
+ ...options,
89
+ timeZone: timezone
90
+ }).format(d);
91
+ }
92
+ function getTodayInTimezone(timezone) {
93
+ return utcToLocal(new Date(), timezone).date;
94
+ }
@@ -0,0 +1,64 @@
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 escapeHtml () {
13
+ return escapeHtml;
14
+ },
15
+ get escapeHtmlVariables () {
16
+ return escapeHtmlVariables;
17
+ }
18
+ });
19
+ /**
20
+ * HTML Sanitizer Utilities
21
+ *
22
+ * Provides functions for escaping HTML content to prevent XSS attacks.
23
+ * Use these utilities when interpolating user-provided variables into HTML content.
24
+ */ /**
25
+ * HTML entity mapping for escaping special characters
26
+ */ const HTML_ESCAPE_MAP = {
27
+ '&': '&',
28
+ '<': '&lt;',
29
+ '>': '&gt;',
30
+ '"': '&quot;',
31
+ "'": '&#x27;',
32
+ '/': '&#x2F;',
33
+ '`': '&#x60;',
34
+ '=': '&#x3D;'
35
+ };
36
+ /**
37
+ * Regex pattern matching characters that need HTML escaping
38
+ */ const HTML_ESCAPE_REGEX = /[&<>"'`=/]/g;
39
+ function escapeHtml(str) {
40
+ if (!str || typeof str !== 'string') {
41
+ return str ?? '';
42
+ }
43
+ return str.replace(HTML_ESCAPE_REGEX, (char)=>HTML_ESCAPE_MAP[char]);
44
+ }
45
+ function escapeHtmlVariables(variables) {
46
+ if (!variables || typeof variables !== 'object') {
47
+ return {};
48
+ }
49
+ const escaped = {};
50
+ for (const [key, value] of Object.entries(variables)){
51
+ if (value === null || value === undefined) {
52
+ escaped[key] = '';
53
+ } else if (typeof value === 'string') {
54
+ escaped[key] = escapeHtml(value);
55
+ } else if (typeof value === 'object') {
56
+ // For objects/arrays, stringify and escape
57
+ escaped[key] = escapeHtml(JSON.stringify(value));
58
+ } else {
59
+ // For numbers, booleans, etc., convert to string (no escaping needed)
60
+ escaped[key] = String(value);
61
+ }
62
+ }
63
+ return escaped;
64
+ }
@@ -2,7 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- _export_star(require("./error-handler.util"), exports);
5
+ _export_star(require("./date-time.util"), exports);
6
+ _export_star(require("./html-sanitizer.util"), exports);
7
+ _export_star(require("./query-helpers.util"), exports);
8
+ _export_star(require("./request.util"), exports);
9
+ _export_star(require("./string.util"), exports);
6
10
  function _export_star(from, to) {
7
11
  Object.keys(from).forEach(function(k) {
8
12
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
@@ -0,0 +1,60 @@
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
+ const _constants = require("../constants");
27
+ function applyCompanyFilter(query, config, user) {
28
+ const columnName = config.columnName ?? 'companyId';
29
+ if (config.isCompanyFeatureEnabled && user?.companyId) {
30
+ query.andWhere(`${config.entityAlias}.${columnName} = :companyId`, {
31
+ companyId: user.companyId
32
+ });
33
+ }
34
+ return query;
35
+ }
36
+ function buildCompanyWhereCondition(baseWhere, isCompanyFeatureEnabled, user) {
37
+ if (isCompanyFeatureEnabled && user?.companyId) {
38
+ return {
39
+ ...baseWhere,
40
+ companyId: user.companyId
41
+ };
42
+ }
43
+ return baseWhere;
44
+ }
45
+ function hasCompanyId(entity) {
46
+ return entity !== null && typeof entity === 'object' && 'companyId' in entity;
47
+ }
48
+ function validateCompanyOwnership(entity, user, isCompanyFeatureEnabled, entityName) {
49
+ if (isCompanyFeatureEnabled && user?.companyId && hasCompanyId(entity)) {
50
+ if (entity.companyId && entity.companyId !== user.companyId) {
51
+ throw new _common.BadRequestException({
52
+ message: `${entityName} belongs to another company`,
53
+ messageKey: _constants.AUTH_MESSAGES.ENTITY_BELOG_ANOTHER_COMPANY,
54
+ messageVariables: {
55
+ entity: entityName
56
+ }
57
+ });
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,96 @@
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 buildCookieOptions () {
13
+ return buildCookieOptions;
14
+ },
15
+ get buildFrontendUrl () {
16
+ return buildFrontendUrl;
17
+ },
18
+ get extractFrontendUrl () {
19
+ return extractFrontendUrl;
20
+ },
21
+ get isBrowserRequest () {
22
+ return isBrowserRequest;
23
+ },
24
+ get parseDurationToMs () {
25
+ return parseDurationToMs;
26
+ }
27
+ });
28
+ const _config = require("@flusys/nestjs-core/config");
29
+ const _constants = require("../constants");
30
+ /** Time unit multipliers in milliseconds */ const TIME_UNIT_MS = {
31
+ s: 1000,
32
+ m: 60 * 1000,
33
+ h: 60 * 60 * 1000,
34
+ d: 24 * 60 * 60 * 1000,
35
+ w: 7 * 24 * 60 * 60 * 1000
36
+ };
37
+ /** Get normalized client type from request header */ function getClientType(req) {
38
+ const clientType = req.headers[_constants.CLIENT_TYPE_HEADER];
39
+ return clientType ? clientType.toLowerCase() : null;
40
+ }
41
+ function isBrowserRequest(req) {
42
+ const clientType = getClientType(req);
43
+ if (clientType) {
44
+ return clientType === 'browser' || clientType === 'web';
45
+ }
46
+ const accept = req.headers['accept'] || '';
47
+ if (accept.includes('text/html')) return true;
48
+ const userAgent = req.headers['user-agent'] || '';
49
+ const browserPatterns = /mozilla|chrome|safari|firefox|edge|opera|msie/i;
50
+ return browserPatterns.test(userAgent) && !userAgent.includes('Postman');
51
+ }
52
+ function buildCookieOptions(req) {
53
+ const hostname = req.hostname || '';
54
+ const origin = req.headers.origin || '';
55
+ const isProduction = _config.envConfig.isProduction();
56
+ const forwardedProto = req.headers['x-forwarded-proto'];
57
+ const isHttps = isProduction || forwardedProto === 'https' || origin.startsWith('https://') || req.secure;
58
+ let domain;
59
+ const domainParts = hostname.split('.');
60
+ if (domainParts.length > 2 && !hostname.includes('localhost')) {
61
+ domain = '.' + domainParts.slice(-2).join('.');
62
+ }
63
+ return {
64
+ secure: isHttps,
65
+ sameSite: isHttps ? 'strict' : 'lax',
66
+ ...domain && {
67
+ domain
68
+ }
69
+ };
70
+ }
71
+ function parseDurationToMs(duration, defaultMs = TIME_UNIT_MS.w) {
72
+ const match = duration.match(/^(\d+)(s|m|h|d|w)$/);
73
+ if (!match) return defaultMs;
74
+ const value = parseInt(match[1], 10);
75
+ const unit = match[2];
76
+ return value * (TIME_UNIT_MS[unit] ?? TIME_UNIT_MS.d);
77
+ }
78
+ function extractFrontendUrl(req) {
79
+ const allowedOrigins = _config.envConfig.getOrigins();
80
+ const origin = (req.get('origin') ?? req.get('referer') ?? '').trim();
81
+ if (origin && allowedOrigins.length) {
82
+ const matched = allowedOrigins.find((u)=>origin.startsWith(u));
83
+ if (matched) {
84
+ return matched;
85
+ }
86
+ }
87
+ const host = req.get('host');
88
+ if (!host) {
89
+ throw new Error('Unable to determine frontend URL from request');
90
+ }
91
+ return `${req.protocol}://${host}`;
92
+ }
93
+ function buildFrontendUrl(req, path) {
94
+ const baseUrl = extractFrontendUrl(req);
95
+ return `${baseUrl}${path}`;
96
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * String utility functions
3
+ */ /**
4
+ * Generate URL-friendly slug from a string
5
+ * @param text - The text to convert to slug
6
+ * @param maxLength - Maximum length of the slug (default: 100)
7
+ * @returns URL-friendly slug
8
+ *
9
+ * @example
10
+ * generateSlug('My Company Name') // 'my-company-name'
11
+ * generateSlug('Hello World!') // 'hello-world'
12
+ */ "use strict";
13
+ Object.defineProperty(exports, "__esModule", {
14
+ value: true
15
+ });
16
+ function _export(target, all) {
17
+ for(var name in all)Object.defineProperty(target, name, {
18
+ enumerable: true,
19
+ get: Object.getOwnPropertyDescriptor(all, name).get
20
+ });
21
+ }
22
+ _export(exports, {
23
+ get generateSlug () {
24
+ return generateSlug;
25
+ },
26
+ get generateUniqueSlug () {
27
+ return generateUniqueSlug;
28
+ }
29
+ });
30
+ function generateSlug(text, maxLength = 100) {
31
+ return text.toLowerCase().trim().replace(/[^a-z0-9\s-]/g, '') // Remove special characters
32
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
33
+ .replace(/-+/g, '-') // Replace multiple hyphens with single
34
+ .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
35
+ .substring(0, maxLength);
36
+ }
37
+ async function generateUniqueSlug(text, findMatchingSlugs, maxLength = 100) {
38
+ const baseSlug = generateSlug(text, maxLength);
39
+ // Single query to get all matching slugs
40
+ const existingSlugs = await findMatchingSlugs(baseSlug);
41
+ // No collisions - base slug is available
42
+ if (existingSlugs.length === 0) {
43
+ return baseSlug;
44
+ }
45
+ // Build set for O(1) lookup
46
+ const existingSet = new Set(existingSlugs);
47
+ // Base slug not taken (only suffixed versions exist)
48
+ if (!existingSet.has(baseSlug)) {
49
+ return baseSlug;
50
+ }
51
+ // Find next available suffix in memory (fast)
52
+ let counter = 1;
53
+ while(counter < 10000){
54
+ const slugWithSuffix = `${baseSlug}-${counter}`;
55
+ if (!existingSet.has(slugWithSuffix)) {
56
+ return slugWithSuffix;
57
+ }
58
+ counter++;
59
+ }
60
+ // Fallback: append random string (extremely rare edge case)
61
+ const randomSuffix = Math.random().toString(36).substring(2, 8);
62
+ return `${baseSlug}-${randomSuffix}`;
63
+ }
@@ -1,30 +1,38 @@
1
- import { BulkResponseDto, DeleteDto, FilterAndPaginationDto, GetByIdBodyDto, ListResponseDto, MessageResponseDto, SingleResponseDto } from '@flusys/nestjs-shared/dtos';
2
- import { Identity } from '@flusys/nestjs-shared/entities';
3
- import { ILoggedUserInfo, IService, PermissionCondition, PermissionOperator } from '@flusys/nestjs-shared/interfaces';
4
1
  import { Type } from '@nestjs/common';
5
- export type ApiEndpoint = 'insert' | 'insertMany' | 'getById' | 'getAll' | 'update' | 'updateMany' | 'delete';
2
+ import { BulkResponseDto, DeleteDto, FilterAndPaginationDto, GetByIdBodyDto, GetByIdsDto, ListResponseDto, MessageResponseDto, SingleResponseDto } from '../dtos';
3
+ import { Identity } from '../entities';
4
+ import { ILoggedUserInfo, IPermissionLogic, IService } from '../interfaces';
5
+ export type ApiEndpoint = 'insert' | 'insertMany' | 'getById' | 'getByIds' | 'getAll' | 'bulkUpsert' | 'getByFilter' | 'update' | 'updateMany' | 'delete';
6
6
  export type SecurityLevel = 'public' | 'jwt' | 'permission';
7
7
  export interface EndpointSecurity {
8
8
  level: SecurityLevel;
9
9
  permissions?: string[];
10
- operator?: PermissionOperator;
11
- condition?: PermissionCondition;
10
+ operator?: 'AND' | 'OR';
11
+ logic?: IPermissionLogic;
12
12
  }
13
13
  export type ApiSecurityConfig = {
14
14
  [K in ApiEndpoint]?: EndpointSecurity | SecurityLevel;
15
15
  };
16
16
  export interface ApiControllerOptions {
17
17
  security?: ApiSecurityConfig | EndpointSecurity | SecurityLevel;
18
+ entityName?: string;
19
+ enabledEndpoints?: ApiEndpoint[] | 'all';
20
+ excludeExtraneousValues?: boolean;
18
21
  }
19
- export declare function createApiController<CreateDtoT extends Record<string, unknown>, UpdateDtoT extends {
22
+ export declare function createApiController<CreateDtoT extends object, UpdateDtoT extends {
20
23
  id: string;
21
- }, ResponseDtoT extends Record<string, unknown>, InterfaceT extends Identity, ServiceT extends IService<CreateDtoT, UpdateDtoT, InterfaceT>>(createDtoClass: Type<CreateDtoT>, updateDtoClass: Type<UpdateDtoT>, responseDtoClass: Type<ResponseDtoT>, options?: ApiControllerOptions): abstract new (service: ServiceT) => {
24
+ }, 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) => {
25
+ readonly enabledEndpoints: ApiEndpoint[] | "all";
22
26
  service: ServiceT;
27
+ isEnabled(endpoint: ApiEndpoint): boolean;
23
28
  insert(addDto: CreateDtoT, user: ILoggedUserInfo | null): Promise<SingleResponseDto<ResponseDtoT>>;
24
29
  insertMany(addDto: CreateDtoT[], user: ILoggedUserInfo | null): Promise<BulkResponseDto<ResponseDtoT>>;
25
30
  getById(id: string, body: GetByIdBodyDto, user: ILoggedUserInfo | null): Promise<SingleResponseDto<ResponseDtoT>>;
31
+ getByIds(body: GetByIdsDto, user: ILoggedUserInfo | null): Promise<ListResponseDto<ResponseDtoT>>;
26
32
  update(updateDto: UpdateDtoT, user: ILoggedUserInfo | null): Promise<SingleResponseDto<ResponseDtoT>>;
27
33
  updateMany(updateDtos: UpdateDtoT[], user: ILoggedUserInfo | null): Promise<BulkResponseDto<ResponseDtoT>>;
34
+ bulkUpsert(dtos: (CreateDtoT | UpdateDtoT)[], user: ILoggedUserInfo | null): Promise<BulkResponseDto<ResponseDtoT>>;
35
+ getByFilter(filter: Record<string, any>, user: ILoggedUserInfo | null): Promise<SingleResponseDto<ResponseDtoT>>;
28
36
  getAll(filterAndPaginationDto: FilterAndPaginationDto, user: ILoggedUserInfo | null, search?: string): Promise<ListResponseDto<ResponseDtoT>>;
29
37
  delete(deleteDto: DeleteDto, user: ILoggedUserInfo | null): Promise<MessageResponseDto>;
30
38
  };
@@ -1,26 +1,34 @@
1
- import { DeleteDto, FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
2
- import { Identity } from '@flusys/nestjs-shared/entities';
3
- import { ILoggedUserInfo, IService } from '@flusys/nestjs-shared/interfaces';
4
- import { UtilsService } from '@flusys/nestjs-shared/modules';
5
- import { Logger } from '@nestjs/common';
6
- import { QueryRunner, Repository, SelectQueryBuilder } from 'typeorm';
1
+ import { DataSource, EntityTarget, QueryRunner, Repository, SelectQueryBuilder } from 'typeorm';
2
+ import { DeleteDto, FilterAndPaginationDto } from '../dtos';
3
+ import { Identity, RawEntity } from '../entities';
4
+ import { IDataSourceProvider, ILoggedUserInfo, IService } from '../interfaces';
5
+ import { UtilsService } from '../modules/utils/utils.service';
7
6
  import { HybridCache } from './hybrid-cache.class';
8
- export declare abstract class ApiService<CreateDtoT extends Record<string, unknown>, UpdateDtoT extends {
7
+ export declare abstract class ApiService<CreateDtoT extends object, UpdateDtoT extends {
9
8
  id: string;
10
- }, InterfaceT extends Identity, EntityT extends Identity, RepositoryT extends Repository<EntityT>> implements IService<CreateDtoT, UpdateDtoT, InterfaceT> {
9
+ }, InterfaceT extends Identity, EntityT extends Identity> implements IService<CreateDtoT, UpdateDtoT, InterfaceT> {
11
10
  protected entityName: string;
12
- protected repository: RepositoryT;
13
11
  protected cacheManager: HybridCache;
14
12
  protected utilsService: UtilsService;
15
- protected loggerName: string;
13
+ protected _loggerName: string;
16
14
  protected isCacheable: boolean;
17
- protected logger: Logger;
18
- constructor(entityName: string, repository: RepositoryT, cacheManager: HybridCache, utilsService: UtilsService, loggerName: string, isCacheable?: boolean);
15
+ protected moduleName?: string | undefined;
16
+ protected repository: Repository<EntityT>;
17
+ private repositoryInitialized;
18
+ private _entity?;
19
+ private _dataSourceProvider?;
20
+ constructor(entityName: string, cacheManager: HybridCache, utilsService: UtilsService, _loggerName: string, isCacheable?: boolean, moduleName?: string | undefined, entity?: EntityTarget<EntityT>, dataSourceProvider?: IDataSourceProvider);
21
+ protected resolveEntity(): EntityTarget<EntityT>;
22
+ protected getDataSourceProvider(): IDataSourceProvider;
23
+ protected ensureDataSourceRepository(): Promise<void>;
24
+ protected resolveRepositories(entities: EntityTarget<any>[]): Promise<Repository<any>[]>;
25
+ protected getDataSourceForService(): Promise<DataSource>;
19
26
  insert(dto: CreateDtoT, user: ILoggedUserInfo | null): Promise<InterfaceT>;
20
27
  insertMany(dtos: Array<CreateDtoT>, user: ILoggedUserInfo | null): Promise<InterfaceT[]>;
21
28
  update(dto: UpdateDtoT, user: ILoggedUserInfo | null): Promise<InterfaceT>;
22
29
  updateMany(dtos: UpdateDtoT[], user: ILoggedUserInfo | null): Promise<InterfaceT[]>;
23
- findByIds(ids: string[], user: ILoggedUserInfo | null): Promise<InterfaceT[]>;
30
+ bulkUpsert(toInsert: CreateDtoT[], toUpdate: UpdateDtoT[], user: ILoggedUserInfo | null): Promise<InterfaceT[]>;
31
+ findByIds(ids: string[], user: ILoggedUserInfo | null, select?: string[] | string): Promise<InterfaceT[]>;
24
32
  findById(id: string, user: ILoggedUserInfo | null, select?: string[]): Promise<InterfaceT>;
25
33
  getAll(search: string, filterAndPaginationDto: FilterAndPaginationDto, user: ILoggedUserInfo | null): Promise<{
26
34
  data: Array<InterfaceT>;
@@ -30,24 +38,21 @@ export declare abstract class ApiService<CreateDtoT extends Record<string, unkno
30
38
  clearCacheForAll(): Promise<void>;
31
39
  clearCacheForId(entities: EntityT[]): Promise<void>;
32
40
  private handleError;
33
- protected ensureRepositoryInitialized(): Promise<void>;
41
+ private ensureArray;
42
+ private executeInTransaction;
34
43
  protected beforeInsertOperation(_dto: CreateDtoT | Array<CreateDtoT>, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
35
- protected afterInsertOperation(_entity: EntityT[], _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
44
+ protected afterInsertOperation(_entity: EntityT[], _dto: CreateDtoT | Array<CreateDtoT>, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
36
45
  protected beforeUpdateOperation(_dto: UpdateDtoT | Array<UpdateDtoT>, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
37
- protected afterUpdateOperation(_entity: EntityT[], _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
46
+ protected afterUpdateOperation(_entity: EntityT[], _dto: UpdateDtoT | Array<UpdateDtoT>, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
38
47
  protected beforeDeleteOperation(_dto: DeleteDto, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
39
48
  protected afterDeleteOperation(_entity: Array<{
40
49
  id: string;
41
- }>, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
42
- protected getFilterQuery(query: SelectQueryBuilder<EntityT>, filter: {
43
- [key: string]: any;
44
- }, _user: ILoggedUserInfo | null): Promise<{
50
+ }>, _dto: DeleteDto, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
51
+ protected getFilterQuery(query: SelectQueryBuilder<EntityT>, filter: Record<string, unknown>, _user: ILoggedUserInfo | null): Promise<{
45
52
  query: SelectQueryBuilder<EntityT>;
46
53
  isRaw: boolean;
47
54
  }>;
48
- protected getSortQuery(query: SelectQueryBuilder<EntityT>, sort: {
49
- [key: string]: 'ASC' | 'DESC';
50
- }, _user: ILoggedUserInfo | null): Promise<{
55
+ protected getSortQuery(query: SelectQueryBuilder<EntityT>, sort: Record<string, 'ASC' | 'DESC'>, _user: ILoggedUserInfo | null): Promise<{
51
56
  query: SelectQueryBuilder<EntityT>;
52
57
  isRaw: boolean;
53
58
  }>;
@@ -66,7 +71,7 @@ export declare abstract class ApiService<CreateDtoT extends Record<string, unkno
66
71
  protected convertRequestDtoToEntity(dto: CreateDtoT | UpdateDtoT | Array<CreateDtoT | UpdateDtoT>, user: ILoggedUserInfo | null): Promise<Array<EntityT>>;
67
72
  protected convertSingleDtoToEntity(dto: CreateDtoT | UpdateDtoT, _user: ILoggedUserInfo | null): Promise<EntityT>;
68
73
  protected convertArrayDtoToEntities(dtos: Array<CreateDtoT | UpdateDtoT>, user: ILoggedUserInfo | null): Promise<Array<EntityT>>;
69
- protected convertEntityToResponseDto(entity: EntityT, _isRaw: boolean): InterfaceT;
70
- protected convertEntityListToResponseListDto(entities: EntityT[], _isRaw: boolean): InterfaceT[];
74
+ protected convertEntityToResponseDto(entity: EntityT | RawEntity, _isRaw: boolean): InterfaceT;
75
+ protected convertEntityListToResponseListDto(entities: EntityT[] | RawEntity[], _isRaw: boolean): InterfaceT[];
71
76
  protected getEntityClass(): Promise<EntityT>;
72
77
  }
@@ -0,0 +1,3 @@
1
+ export declare function getJsonColumnType(): "simple-json" | "json" | "jsonb";
2
+ export declare function getCastIntType(): string;
3
+ export declare function buildQuery(sql: string, dbType: string): string;
@@ -1,6 +1,6 @@
1
1
  export * from './api-controller.class';
2
2
  export * from './api-service.class';
3
- export * from './request-scoped-api.service';
3
+ export * from './handle-custome-db';
4
4
  export * from './hybrid-cache.class';
5
- export * from './winston-logger-adapter.class';
6
5
  export * from './winston.logger.class';
6
+ export * from '../constants/permissions';
@@ -1,2 +1,3 @@
1
1
  import { Logger } from 'winston';
2
2
  export declare const instance: Logger;
3
+ export declare function createModuleLogger(moduleName: string): Logger;
@@ -2,9 +2,10 @@ export declare const IS_PUBLIC_KEY = "isPublic";
2
2
  export declare const PERMISSIONS_KEY = "permissions";
3
3
  export declare const CACHE_INSTANCE = "CACHE_INSTANCE";
4
4
  export declare const PERMISSION_GUARD_CONFIG = "PERMISSION_GUARD_CONFIG";
5
- export declare const LOGGER_INSTANCE = "LOGGER_INSTANCE";
6
5
  export declare const IDEMPOTENCY_KEY_HEADER = "x-idempotency-key";
7
6
  export declare const REQUEST_ID_HEADER = "x-request-id";
8
7
  export declare const CLIENT_TYPE_HEADER = "x-client-type";
9
8
  export declare const PERMISSIONS_CACHE_PREFIX = "permissions";
10
9
  export declare const IDEMPOTENCY_CACHE_PREFIX = "idempotency";
10
+ export * from './permissions';
11
+ export * from './message-keys';
@@ -0,0 +1,45 @@
1
+ export declare const AUTH_MESSAGES: {
2
+ readonly TOKEN_REQUIRED: "auth.token.required";
3
+ readonly TOKEN_INVALID: "auth.token.invalid";
4
+ readonly ENTITY_BELOG_ANOTHER_COMPANY: "entity.belongs.another.company";
5
+ readonly COMPANY_NO_ACCESS: "auth.company.no.access";
6
+ };
7
+ export declare const ERROR_MESSAGES: {
8
+ readonly NOT_FOUND: "error.not.found";
9
+ readonly VALIDATION: "error.validation";
10
+ readonly UNAUTHORIZED: "error.unauthorized";
11
+ readonly FORBIDDEN: "error.forbidden";
12
+ readonly CONFLICT: "error.conflict";
13
+ readonly DB_FOREIGN_KEY: "error.db.foreign.key";
14
+ readonly DB_NOT_NULL: "error.db.not.null";
15
+ readonly DB_INVALID_INPUT: "error.db.invalid.input";
16
+ readonly DB_VALUE_TOO_LONG: "error.db.value.too.long";
17
+ readonly DB_CHECK_CONSTRAINT: "error.db.check.constraint";
18
+ readonly DB_DEADLOCK: "error.db.deadlock";
19
+ readonly INTERNAL: "error.internal";
20
+ readonly SERVICE_UNAVAILABLE: "error.service.unavailable";
21
+ readonly UNKNOWN: "error.unknown";
22
+ readonly HTTP: "error.http";
23
+ readonly GENERIC: "error.generic";
24
+ readonly PERMISSION_SYSTEM_UNAVAILABLE: "error.permission.system.unavailable";
25
+ readonly INSUFFICIENT_PERMISSIONS: "error.insufficient.permissions";
26
+ readonly INSUFFICIENT_PERMISSIONS_OR: "error.insufficient.permissions.or";
27
+ readonly NO_PERMISSIONS_FOUND: "error.no.permissions.found";
28
+ readonly ENDPOINT_DISABLED: "error.endpoint.disabled";
29
+ };
30
+ export declare const SYSTEM_MESSAGES: {
31
+ readonly REPOSITORY_NOT_AVAILABLE: "system.repository.not.available";
32
+ readonly DATASOURCE_NOT_AVAILABLE: "system.datasource.not.available";
33
+ readonly DATABASE_CONFIG_NOT_AVAILABLE: "system.database.config.not.available";
34
+ readonly SERVICE_NOT_AVAILABLE: "system.service.not.available";
35
+ readonly CONFIG_REQUIRED: "system.config.required";
36
+ readonly INTERNAL_ERROR: "system.internal.error";
37
+ readonly DUPLICATE_REQUEST: "system.duplicate.request";
38
+ readonly INVALID_TENANT_ID: "system.invalid.tenant.id";
39
+ readonly TENANT_NOT_FOUND: "system.tenant.not.found";
40
+ readonly TENANT_HEADER_REQUIRED: "system.tenant.header.required";
41
+ readonly MISSING_PARAMETER: "system.missing.parameter";
42
+ readonly SDK_NOT_INSTALLED: "system.sdk.not.installed";
43
+ readonly PATH_TRAVERSAL_DETECTED: "system.path.traversal.detected";
44
+ readonly INVALID_FILE_KEY: "system.invalid.file.key";
45
+ };