@bearei/nest 0.0.0-stage → 2.0.0-dev.2

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 (145) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/LICENSE +21 -0
  3. package/README.md +100 -2
  4. package/README.zh-CN.md +103 -0
  5. package/dist/action-log/action-log.interface.d.ts +12 -0
  6. package/dist/action-log/action-log.interface.js +2 -0
  7. package/dist/action-log/action-log.module.d.ts +2 -0
  8. package/dist/action-log/action-log.module.js +22 -0
  9. package/dist/action-log/action-log.service.d.ts +10 -0
  10. package/dist/action-log/action-log.service.js +42 -0
  11. package/dist/action-log/index.d.ts +3 -0
  12. package/dist/action-log/index.js +19 -0
  13. package/dist/authentication/authentication-context.interceptor.d.ts +7 -0
  14. package/dist/authentication/authentication-context.interceptor.js +70 -0
  15. package/dist/authentication/authentication.enum.d.ts +6 -0
  16. package/dist/authentication/authentication.enum.js +9 -0
  17. package/dist/authentication/authentication.guard.d.ts +13 -0
  18. package/dist/authentication/authentication.guard.js +92 -0
  19. package/dist/authentication/authentication.interface.d.ts +2 -0
  20. package/dist/authentication/authentication.interface.js +2 -0
  21. package/dist/authentication/index.d.ts +4 -0
  22. package/dist/authentication/index.js +20 -0
  23. package/dist/authorization/authorization.interface.d.ts +3 -0
  24. package/dist/authorization/authorization.interface.js +2 -0
  25. package/dist/authorization/authorization.module.d.ts +2 -0
  26. package/dist/authorization/authorization.module.js +22 -0
  27. package/dist/authorization/authorization.service.d.ts +10 -0
  28. package/dist/authorization/authorization.service.js +56 -0
  29. package/dist/authorization/index.d.ts +3 -0
  30. package/dist/authorization/index.js +19 -0
  31. package/dist/base-service/base-service.interface.d.ts +84 -0
  32. package/dist/base-service/base-service.interface.js +2 -0
  33. package/dist/base-service/base-service.module.d.ts +2 -0
  34. package/dist/base-service/base-service.module.js +22 -0
  35. package/dist/base-service/base-service.service.d.ts +27 -0
  36. package/dist/base-service/base-service.service.js +517 -0
  37. package/dist/base-service/index.d.ts +3 -0
  38. package/dist/base-service/index.js +19 -0
  39. package/dist/common.dto.d.ts +19 -0
  40. package/dist/common.dto.js +111 -0
  41. package/dist/common.entity.d.ts +11 -0
  42. package/dist/common.entity.js +76 -0
  43. package/dist/common.enum.d.ts +19 -0
  44. package/dist/common.enum.js +24 -0
  45. package/dist/common.interface.d.ts +19 -0
  46. package/dist/common.interface.js +2 -0
  47. package/dist/database/database.module.d.ts +5 -0
  48. package/dist/database/database.module.js +58 -0
  49. package/dist/database/index.d.ts +1 -0
  50. package/dist/database/index.js +17 -0
  51. package/dist/decorators/allow-access.decorator.d.ts +3 -0
  52. package/dist/decorators/allow-access.decorator.js +7 -0
  53. package/dist/decorators/index.d.ts +8 -0
  54. package/dist/decorators/index.js +24 -0
  55. package/dist/decorators/is-captcha.decorator.d.ts +2 -0
  56. package/dist/decorators/is-captcha.decorator.js +23 -0
  57. package/dist/decorators/is-nano-id.decorator.d.ts +2 -0
  58. package/dist/decorators/is-nano-id.decorator.js +22 -0
  59. package/dist/decorators/is-password.decorator.d.ts +2 -0
  60. package/dist/decorators/is-password.decorator.js +23 -0
  61. package/dist/decorators/is-version.decorator.d.ts +2 -0
  62. package/dist/decorators/is-version.decorator.js +22 -0
  63. package/dist/decorators/omit-entity-keys.decorator.d.ts +2 -0
  64. package/dist/decorators/omit-entity-keys.decorator.js +7 -0
  65. package/dist/decorators/require-permissions.decorator.d.ts +3 -0
  66. package/dist/decorators/require-permissions.decorator.js +7 -0
  67. package/dist/decorators/require-subscription-features.decorator.d.ts +3 -0
  68. package/dist/decorators/require-subscription-features.decorator.js +7 -0
  69. package/dist/exception/exception.enum.d.ts +12 -0
  70. package/dist/exception/exception.enum.js +16 -0
  71. package/dist/exception/exception.interface.d.ts +9 -0
  72. package/dist/exception/exception.interface.js +2 -0
  73. package/dist/exception/http.exception.d.ts +4 -0
  74. package/dist/exception/http.exception.js +47 -0
  75. package/dist/exception/index.d.ts +3 -0
  76. package/dist/exception/index.js +19 -0
  77. package/dist/external/external.interface.d.ts +58 -0
  78. package/dist/external/external.interface.js +2 -0
  79. package/dist/external/external.module.d.ts +2 -0
  80. package/dist/external/external.module.js +24 -0
  81. package/dist/external/external.service.d.ts +15 -0
  82. package/dist/external/external.service.js +160 -0
  83. package/dist/external/index.d.ts +3 -0
  84. package/dist/external/index.js +19 -0
  85. package/dist/index.d.ts +17 -0
  86. package/dist/index.js +39 -0
  87. package/dist/jwt/index.d.ts +3 -0
  88. package/dist/jwt/index.js +19 -0
  89. package/dist/jwt/jwt.interface.d.ts +15 -0
  90. package/dist/jwt/jwt.interface.js +2 -0
  91. package/dist/jwt/jwt.module.d.ts +2 -0
  92. package/dist/jwt/jwt.module.js +43 -0
  93. package/dist/jwt/jwt.service.d.ts +10 -0
  94. package/dist/jwt/jwt.service.js +52 -0
  95. package/dist/permission/index.d.ts +3 -0
  96. package/dist/permission/index.js +19 -0
  97. package/dist/permission/permission.enum.d.ts +70 -0
  98. package/dist/permission/permission.enum.js +73 -0
  99. package/dist/permission/permission.guard.d.ts +9 -0
  100. package/dist/permission/permission.guard.js +55 -0
  101. package/dist/permission/permission.interface.d.ts +2 -0
  102. package/dist/permission/permission.interface.js +2 -0
  103. package/dist/redis/index.d.ts +3 -0
  104. package/dist/redis/index.js +19 -0
  105. package/dist/redis/redis.interface.d.ts +4 -0
  106. package/dist/redis/redis.interface.js +2 -0
  107. package/dist/redis/redis.module.d.ts +2 -0
  108. package/dist/redis/redis.module.js +22 -0
  109. package/dist/redis/redis.service.d.ts +14 -0
  110. package/dist/redis/redis.service.js +51 -0
  111. package/dist/subscription-feature/index.d.ts +3 -0
  112. package/dist/subscription-feature/index.js +19 -0
  113. package/dist/subscription-feature/subscription-feature.enum.d.ts +3 -0
  114. package/dist/subscription-feature/subscription-feature.enum.js +6 -0
  115. package/dist/subscription-feature/subscription-feature.interceptor.d.ts +9 -0
  116. package/dist/subscription-feature/subscription-feature.interceptor.js +68 -0
  117. package/dist/subscription-feature/subscription-feature.interface.d.ts +2 -0
  118. package/dist/subscription-feature/subscription-feature.interface.js +2 -0
  119. package/dist/utils/create-response-sanitizer/create-response-sanitizer.utils.d.ts +1 -0
  120. package/dist/utils/create-response-sanitizer/create-response-sanitizer.utils.js +23 -0
  121. package/dist/utils/create-response-sanitizer/index.d.ts +1 -0
  122. package/dist/utils/create-response-sanitizer/index.js +17 -0
  123. package/dist/utils/extract-bearer-token/extract-bearer-token.utils.d.ts +1 -0
  124. package/dist/utils/extract-bearer-token/extract-bearer-token.utils.js +13 -0
  125. package/dist/utils/extract-bearer-token/index.d.ts +1 -0
  126. package/dist/utils/extract-bearer-token/index.js +17 -0
  127. package/dist/utils/format-mobile-number/format-mobile-number.utils.d.ts +1 -0
  128. package/dist/utils/format-mobile-number/format-mobile-number.utils.js +5 -0
  129. package/dist/utils/format-mobile-number/index.d.ts +1 -0
  130. package/dist/utils/format-mobile-number/index.js +17 -0
  131. package/dist/utils/generate-random-number-string/generate-random-number-string.utils.d.ts +1 -0
  132. package/dist/utils/generate-random-number-string/generate-random-number-string.utils.js +6 -0
  133. package/dist/utils/generate-random-number-string/index.d.ts +1 -0
  134. package/dist/utils/generate-random-number-string/index.js +17 -0
  135. package/dist/utils/index.d.ts +6 -0
  136. package/dist/utils/index.js +22 -0
  137. package/dist/utils/run-with-database-retry/index.d.ts +1 -0
  138. package/dist/utils/run-with-database-retry/index.js +17 -0
  139. package/dist/utils/run-with-database-retry/run-with-database-retry.utils.d.ts +1 -0
  140. package/dist/utils/run-with-database-retry/run-with-database-retry.utils.js +23 -0
  141. package/dist/utils/sign/index.d.ts +1 -0
  142. package/dist/utils/sign/index.js +17 -0
  143. package/dist/utils/sign/sign.utils.d.ts +5 -0
  144. package/dist/utils/sign/sign.utils.js +82 -0
  145. package/package.json +126 -3
@@ -0,0 +1,517 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.BaseService = void 0;
16
+ const contracts_1 = require("@bearei/contracts");
17
+ const common_1 = require("@nestjs/common");
18
+ const dayjs_1 = __importDefault(require("dayjs"));
19
+ const typeorm_1 = require("typeorm");
20
+ const action_log_1 = require("../action-log");
21
+ const common_enum_1 = require("../common.enum");
22
+ const exception_1 = require("../exception");
23
+ const utils_1 = require("../utils");
24
+ const IMMUTABLE_ENTITY_KEYS = ['createdAt', 'deletedAt', 'id', 'internalId', 'updatedAt', 'version'];
25
+ const SERVER_MANAGED_CREATE_KEYS = ['deletedAt', 'internalId', 'version'];
26
+ const ASSOCIATION_FIND_OPTION_KEYS = [
27
+ 'createdAtRange',
28
+ 'ids',
29
+ 'innerJoin',
30
+ 'page',
31
+ 'pageSize',
32
+ 'queryBuilderWhere',
33
+ 'sortOrder',
34
+ 'totalFindOptions',
35
+ 'withTotal'
36
+ ];
37
+ const ASSOCIATION_BATCH_SIZE = 1024;
38
+ const isNotFoundException = (error) => error instanceof common_1.HttpException && error.getStatus() === common_1.HttpStatus.NOT_FOUND;
39
+ const normalizePagination = (page, pageSize) => {
40
+ const normalizedPage = Math.max(1, Math.trunc(Number(page) || 1));
41
+ const normalizedPageSize = Math.min(contracts_1.MAX_PAGE_SIZE, Math.max(1, Math.trunc(Number(pageSize) || contracts_1.DEFAULT_PAGE_SIZE)));
42
+ return { page: normalizedPage, pageSize: normalizedPageSize };
43
+ };
44
+ const buildDefinedWhere = (source) => {
45
+ const where = {};
46
+ for (const [key, value] of Object.entries(source)) {
47
+ if (typeof value !== 'undefined') {
48
+ where[key] = value;
49
+ }
50
+ }
51
+ return where;
52
+ };
53
+ const omitKeys = (source, keys) => {
54
+ const result = {};
55
+ const keySet = new Set(keys);
56
+ for (const [key, value] of Object.entries(source)) {
57
+ if (!keySet.has(key)) {
58
+ result[key] = value;
59
+ }
60
+ }
61
+ return result;
62
+ };
63
+ let BaseService = class BaseService {
64
+ actionLogService;
65
+ constructor(actionLogService) {
66
+ this.actionLogService = actionLogService;
67
+ }
68
+ create(repository, { entity, headers, isLogEnabled = true, preValidationOptions, serviceName }, manager) {
69
+ const actionLogOptions = {
70
+ action: common_enum_1.ACTION.CREATE,
71
+ headers,
72
+ isLogEnabled,
73
+ message: `Create a ${serviceName}.`,
74
+ raw: { entity },
75
+ serviceName
76
+ };
77
+ return (0, utils_1.runWithDatabaseRetry)(async () => {
78
+ const existingEntity = entity.id ?
79
+ await this.findOne(repository, {
80
+ actionLogOptions,
81
+ serviceName,
82
+ where: { id: entity.id }
83
+ }).catch(error => {
84
+ if (!isNotFoundException(error)) {
85
+ throw error;
86
+ }
87
+ return undefined;
88
+ })
89
+ : undefined;
90
+ if (existingEntity) {
91
+ return existingEntity;
92
+ }
93
+ if (preValidationOptions) {
94
+ const definedPreValidationEntries = Object.entries(preValidationOptions.where).filter(([, value]) => typeof value !== 'undefined');
95
+ if (definedPreValidationEntries.length) {
96
+ await this.preValidate(repository, { ...preValidationOptions, actionLogOptions, serviceName });
97
+ }
98
+ }
99
+ const createPayload = omitKeys(entity, SERVER_MANAGED_CREATE_KEYS);
100
+ const createdEntity = repository.create(createPayload);
101
+ const savedEntity = await (manager ? manager.save(createdEntity) : createdEntity.save());
102
+ void this.createSuccessActionLog(savedEntity, actionLogOptions);
103
+ return savedEntity;
104
+ });
105
+ }
106
+ createMany(repository, entities, manager) {
107
+ const createPayloads = entities.map(entity => omitKeys(entity, SERVER_MANAGED_CREATE_KEYS));
108
+ const createdEntities = repository.create(createPayloads);
109
+ return manager ? manager.save(createdEntities) : repository.save(createdEntities);
110
+ }
111
+ delete(repository, { headers, isLogEnabled = true, serviceName, where }, manager) {
112
+ const actionLogOptions = {
113
+ action: common_enum_1.ACTION.DELETE,
114
+ headers,
115
+ isLogEnabled,
116
+ message: `Delete a ${serviceName}.`,
117
+ raw: { where },
118
+ serviceName
119
+ };
120
+ return (0, utils_1.runWithDatabaseRetry)(async () => {
121
+ const entity = await this.findOne(repository, { actionLogOptions, serviceName, where });
122
+ const raw = isLogEnabled ? { ...actionLogOptions.raw, entity: { ...entity } } : {};
123
+ await this.assertNotProhibited(entity, actionLogOptions);
124
+ const result = await (manager ? manager.softRemove(entity) : entity.softRemove());
125
+ void this.createSuccessActionLog(entity, { ...actionLogOptions, raw });
126
+ return result;
127
+ });
128
+ }
129
+ update(repository, { entity, existingEntity, headers, isLogEnabled = true, preValidationOptions, serviceName, where: rawWhere }, manager) {
130
+ const rawActionLogOptions = {
131
+ action: common_enum_1.ACTION.UPDATE,
132
+ headers,
133
+ isLogEnabled,
134
+ message: `Update a ${serviceName}.`,
135
+ raw: { where: rawWhere },
136
+ serviceName
137
+ };
138
+ return (0, utils_1.runWithDatabaseRetry)(async () => {
139
+ const currentEntity = existingEntity ??
140
+ (await this.findOne(repository, {
141
+ actionLogOptions: rawActionLogOptions,
142
+ serviceName,
143
+ where: rawWhere
144
+ }));
145
+ const isOutdatedByUpdatedAt = entity.updatedAt &&
146
+ (0, dayjs_1.default)(entity.updatedAt)
147
+ .utc()
148
+ .isBefore((0, dayjs_1.default)(currentEntity.updatedAt).utc());
149
+ const isOutdatedByVersion = typeof entity.version === 'number' && entity.version < currentEntity.version;
150
+ if (isOutdatedByUpdatedAt || isOutdatedByVersion) {
151
+ return currentEntity;
152
+ }
153
+ const raw = isLogEnabled ? { ...rawActionLogOptions.raw, changes: entity, entity: { ...currentEntity } } : {};
154
+ const actionLogOptions = { ...rawActionLogOptions, raw };
155
+ await this.assertNotProhibited(currentEntity, actionLogOptions);
156
+ if (preValidationOptions) {
157
+ await this.preValidateUpdate({
158
+ actionLogOptions,
159
+ currentEntity,
160
+ preValidationOptions,
161
+ repository,
162
+ serviceName,
163
+ where: rawWhere
164
+ });
165
+ }
166
+ const updatePayload = omitKeys(entity, IMMUTABLE_ENTITY_KEYS);
167
+ repository.merge(currentEntity, updatePayload);
168
+ await (manager ? manager.save(currentEntity) : currentEntity.save());
169
+ void this.createSuccessActionLog(currentEntity, actionLogOptions);
170
+ return currentEntity;
171
+ });
172
+ }
173
+ find(repository, { findManyOptions = {}, findOptions }) {
174
+ return (0, utils_1.runWithDatabaseRetry)(async () => {
175
+ const { createdAtRange, ids, innerJoin, page: rawPage = 1, pageSize: rawPageSize = contracts_1.DEFAULT_PAGE_SIZE, queryBuilderWhere: rawQueryBuilderWhere, sortOrder = common_enum_1.ORDER.ASC, totalFindOptions, withTotal: isWithTotalRequested = false, ...restWhere } = findOptions;
176
+ const { page, pageSize } = normalizePagination(rawPage, rawPageSize);
177
+ const isWithTotal = isWithTotalRequested && page === 1;
178
+ const limit = pageSize;
179
+ const offset = (page - 1) * pageSize;
180
+ if (ids && !ids.length) {
181
+ return { page, pageSize, results: [], total: isWithTotal ? 0 : undefined };
182
+ }
183
+ const where = buildDefinedWhere(restWhere);
184
+ if (ids) {
185
+ where.id = (0, typeorm_1.In)(ids);
186
+ }
187
+ if (createdAtRange) {
188
+ where.createdAt = (0, typeorm_1.Between)(createdAtRange[0], createdAtRange[1]);
189
+ }
190
+ const { alias, distinctOnFields, sortBy = 'internalId', withDeleted: isWithDeleted = false } = findManyOptions;
191
+ const entityName = alias ?? repository.metadata.name;
192
+ const findQueryBuilder = repository.createQueryBuilder(entityName);
193
+ const distinctOnAliasFields = distinctOnFields ? distinctOnFields.map(field => `${entityName}.${field}`) : undefined;
194
+ const orderByCondition = {};
195
+ for (const field of distinctOnAliasFields ?? []) {
196
+ orderByCondition[field] = common_enum_1.ORDER.ASC;
197
+ }
198
+ orderByCondition[`${entityName}.${sortBy}`] = sortOrder;
199
+ findQueryBuilder.orderBy(orderByCondition);
200
+ if (distinctOnAliasFields) {
201
+ findQueryBuilder.distinctOn(distinctOnAliasFields);
202
+ }
203
+ if (isWithDeleted) {
204
+ findQueryBuilder.withDeleted();
205
+ }
206
+ if (rawQueryBuilderWhere) {
207
+ const { expression, parameters } = rawQueryBuilderWhere;
208
+ findQueryBuilder.where(expression, parameters);
209
+ }
210
+ else {
211
+ findQueryBuilder.where(where);
212
+ }
213
+ if (innerJoin) {
214
+ const { alias: innerJoinAlias, condition, parameters, property } = innerJoin;
215
+ findQueryBuilder.innerJoin(property, innerJoinAlias, condition, parameters);
216
+ }
217
+ const totalPromise = isWithTotal ?
218
+ this.count(repository, {
219
+ findManyOptions,
220
+ findOptions: totalFindOptions ?? {
221
+ createdAtRange,
222
+ ids,
223
+ ...(innerJoin && { innerJoin }),
224
+ queryBuilderWhere: rawQueryBuilderWhere,
225
+ ...restWhere
226
+ }
227
+ })
228
+ : undefined;
229
+ const [entities, countResult] = await Promise.all([
230
+ findQueryBuilder.limit(limit).offset(offset).getMany(),
231
+ totalPromise
232
+ ]);
233
+ const total = countResult?.total ?? 0;
234
+ return { page, pageSize, results: entities, total: isWithTotal ? total : undefined };
235
+ });
236
+ }
237
+ count(repository, { findManyOptions = {}, findOptions }) {
238
+ const { createdAtRange, ids, innerJoin, queryBuilderWhere: rawQueryBuilderWhere, rawQuery, ...restWhere } = findOptions;
239
+ const { alias, distinctOnFields, withDeleted: isWithDeleted = false } = findManyOptions;
240
+ return (0, utils_1.runWithDatabaseRetry)(async () => {
241
+ if (rawQuery) {
242
+ const { parameters, sql } = rawQuery;
243
+ const results = await repository.query(sql, parameters);
244
+ const firstResult = results.at(0);
245
+ return { total: Number(firstResult ? firstResult.total : 0) };
246
+ }
247
+ if (ids && !ids.length) {
248
+ return { total: 0 };
249
+ }
250
+ const where = buildDefinedWhere(restWhere);
251
+ if (ids) {
252
+ where.id = (0, typeorm_1.In)(ids);
253
+ }
254
+ if (createdAtRange) {
255
+ where.createdAt = (0, typeorm_1.Between)(createdAtRange[0], createdAtRange[1]);
256
+ }
257
+ const entityName = alias ?? repository.metadata.name;
258
+ const countQueryBuilder = repository.createQueryBuilder(entityName);
259
+ if (rawQueryBuilderWhere) {
260
+ const { expression, parameters } = rawQueryBuilderWhere;
261
+ countQueryBuilder.where(expression, parameters);
262
+ }
263
+ else {
264
+ countQueryBuilder.where(where);
265
+ }
266
+ if (isWithDeleted) {
267
+ countQueryBuilder.withDeleted();
268
+ }
269
+ if (innerJoin) {
270
+ const { alias: innerJoinAlias, condition, parameters, property } = innerJoin;
271
+ countQueryBuilder.innerJoin(property, innerJoinAlias, condition, parameters);
272
+ }
273
+ const distinctOnAliasFields = distinctOnFields ? distinctOnFields.map(field => `${entityName}.${field}`) : undefined;
274
+ if (distinctOnAliasFields) {
275
+ const distinctExpression = distinctOnAliasFields.length === 1 ?
276
+ `COUNT(DISTINCT ${distinctOnAliasFields[0]})`
277
+ : `COUNT(DISTINCT (${distinctOnAliasFields.join(', ')}))`;
278
+ const countResult = await countQueryBuilder
279
+ .select(distinctExpression, 'count')
280
+ .getRawOne();
281
+ return { total: Number(countResult?.count ?? 0) };
282
+ }
283
+ return { total: await countQueryBuilder.getCount() };
284
+ });
285
+ }
286
+ findOne(repository, { actionLogOptions, serviceName, where }) {
287
+ return (0, utils_1.runWithDatabaseRetry)(async () => {
288
+ const definedWhere = buildDefinedWhere(where);
289
+ const fields = Object.keys(definedWhere);
290
+ if (!fields.length) {
291
+ (0, exception_1.throwHttpException)(exception_1.HTTP_EXCEPTION.BAD_REQUEST, {
292
+ message: `The ${serviceName} cannot be queried without defined conditions.`
293
+ });
294
+ }
295
+ const entity = await repository.findOne({ where: definedWhere });
296
+ if (!entity) {
297
+ return this.throwError({
298
+ httpExceptionKind: exception_1.HTTP_EXCEPTION.NOT_FOUND,
299
+ message: `The ${serviceName} does not exist. Find by fields ${fields.join('、')}.`
300
+ }, actionLogOptions);
301
+ }
302
+ return entity;
303
+ });
304
+ }
305
+ updateAssociation(repository, { associationIds: rawAssociationIds, associationKey, findOptions, headers, isLogEnabled = true, serviceName, updateOptionsByAssociationId = {} }, entityManager) {
306
+ const { page: _page, pageSize: _pageSize, ...restFindOptions } = findOptions;
307
+ const associationFields = omitKeys(restFindOptions, ASSOCIATION_FIND_OPTION_KEYS);
308
+ const associationIdsSet = new Set(rawAssociationIds);
309
+ const associationIds = [...associationIdsSet];
310
+ const filterResult = (entity) => !entity.deletedAt;
311
+ const actionLogOptions = {
312
+ action: common_enum_1.ACTION.UPDATE,
313
+ headers,
314
+ isLogEnabled,
315
+ message: `Update ${serviceName} and ${associationKey} associations.`,
316
+ raw: { findOptions },
317
+ serviceName
318
+ };
319
+ return (0, utils_1.runWithDatabaseRetry)(async () => {
320
+ const rawEntities = [];
321
+ for (let page = 1;; page += 1) {
322
+ const { results } = await this.find(repository, {
323
+ findManyOptions: { withDeleted: true },
324
+ findOptions: { ...restFindOptions, page, pageSize: ASSOCIATION_BATCH_SIZE, withTotal: false }
325
+ });
326
+ rawEntities.push(...results);
327
+ if (results.length < ASSOCIATION_BATCH_SIZE) {
328
+ break;
329
+ }
330
+ }
331
+ const raw = isLogEnabled ?
332
+ {
333
+ ...actionLogOptions.raw,
334
+ associationIds,
335
+ associationKey,
336
+ entities: rawEntities.map(entity => ({ ...entity }))
337
+ }
338
+ : {};
339
+ const existingIdsSet = new Set(rawEntities.map(entity => String(entity[associationKey])));
340
+ const entitiesToAdd = [];
341
+ const entitiesToRemove = [];
342
+ const entitiesToRecover = [];
343
+ for (const id of associationIds) {
344
+ if (existingIdsSet.has(id)) {
345
+ continue;
346
+ }
347
+ const updateOptions = updateOptionsByAssociationId[id];
348
+ entitiesToAdd.push(repository.create({
349
+ ...(updateOptions ?? {}),
350
+ ...associationFields,
351
+ [associationKey]: id,
352
+ deletedAt: null
353
+ }));
354
+ }
355
+ for (const entity of rawEntities) {
356
+ if (!associationIdsSet.has(String(entity[associationKey]))) {
357
+ entitiesToRemove.push(entity);
358
+ }
359
+ }
360
+ if (entitiesToRemove.length) {
361
+ await Promise.all(entitiesToRemove.map(entity => this.assertNotProhibited(entity, actionLogOptions)));
362
+ }
363
+ for (const entity of rawEntities) {
364
+ const associationId = String(entity[associationKey]);
365
+ const isRecover = associationIdsSet.has(associationId) && Boolean(entity.deletedAt);
366
+ if (isRecover) {
367
+ const updateOptions = updateOptionsByAssociationId[associationId];
368
+ if (updateOptions) {
369
+ repository.merge(entity, updateOptions);
370
+ }
371
+ entitiesToRecover.push(entity);
372
+ }
373
+ }
374
+ const manager = (entityManager ?? repository);
375
+ if (entitiesToAdd.length) {
376
+ await manager.save(entitiesToAdd);
377
+ }
378
+ if (entitiesToRecover.length) {
379
+ await manager.recover(entitiesToRecover);
380
+ for (const entity of entitiesToRecover) {
381
+ entity.deletedAt = null;
382
+ }
383
+ }
384
+ if (entitiesToRemove.length) {
385
+ await manager.softRemove(entitiesToRemove);
386
+ }
387
+ void this.createSuccessActionLog([...entitiesToAdd, ...entitiesToRecover, ...entitiesToRemove], {
388
+ ...actionLogOptions,
389
+ raw
390
+ });
391
+ return [...rawEntities, ...entitiesToAdd].filter(filterResult);
392
+ });
393
+ }
394
+ preValidate(repository, { actionLogOptions, orValidationKeyGroups: rawOrValidationKeyGroups, serviceName, validationMethod = common_enum_1.VALIDATION_METHOD.AND, where: rawWhere }) {
395
+ return (0, utils_1.runWithDatabaseRetry)(async () => {
396
+ const { excludeId, ...rawRestWhere } = rawWhere;
397
+ const restWhere = {};
398
+ for (const [key, value] of Object.entries(rawRestWhere)) {
399
+ if (typeof value !== 'undefined') {
400
+ restWhere[key] = value;
401
+ }
402
+ }
403
+ const selectKeys = Object.keys(restWhere);
404
+ if (!selectKeys.length) {
405
+ (0, exception_1.throwHttpException)(exception_1.HTTP_EXCEPTION.BAD_REQUEST, {
406
+ message: `Pre-validation of ${serviceName} requires at least one defined condition.`
407
+ });
408
+ }
409
+ const isOrValidation = validationMethod === common_enum_1.VALIDATION_METHOD.OR;
410
+ let normalizedGroups = [];
411
+ if (isOrValidation) {
412
+ const groups = rawOrValidationKeyGroups ?? [];
413
+ const isGroupsShapeValid = groups.length > 0 && groups.every(keys => Array.isArray(keys) && keys.length > 0);
414
+ const filteredGroups = isGroupsShapeValid ?
415
+ groups.map(keys => keys.map(String).filter(key => typeof restWhere[key] !== 'undefined'))
416
+ : [];
417
+ const isGroupsInvalid = !isGroupsShapeValid || filteredGroups.some(keys => !keys.length);
418
+ if (isGroupsInvalid) {
419
+ await this.throwError({
420
+ code: exception_1.HTTP_EXCEPTION_CODE.OR_VALIDATION_PARAMETER_INVALID,
421
+ httpExceptionKind: exception_1.HTTP_EXCEPTION.BAD_REQUEST,
422
+ message: 'When using "or" as the validation method, orValidationKeyGroups must be a non-empty two-dimensional array and every group must contain at least one defined condition. Example: [["key1","key2"],["key1","key3"]].'
423
+ }, actionLogOptions);
424
+ }
425
+ normalizedGroups = filteredGroups;
426
+ }
427
+ const orValidationKeysSet = new Set(normalizedGroups.flat());
428
+ const baseWhere = {};
429
+ for (const [key, value] of Object.entries(restWhere)) {
430
+ if (!orValidationKeysSet.has(key)) {
431
+ baseWhere[key] = value;
432
+ }
433
+ }
434
+ if (excludeId) {
435
+ baseWhere.id = (0, typeorm_1.Not)(excludeId);
436
+ }
437
+ const where = isOrValidation ?
438
+ normalizedGroups.map(keys => {
439
+ const groupWhere = { ...baseWhere };
440
+ for (const key of keys) {
441
+ groupWhere[key] = restWhere[key];
442
+ }
443
+ return groupWhere;
444
+ })
445
+ : baseWhere;
446
+ const select = {};
447
+ for (const key of selectKeys) {
448
+ select[key] = true;
449
+ }
450
+ const entity = await repository.findOne({
451
+ select: select,
452
+ where: where
453
+ });
454
+ if (entity) {
455
+ await this.throwError({
456
+ code: exception_1.HTTP_EXCEPTION_CODE.SERVICE_FIELDS_ALREADY_EXISTS,
457
+ httpExceptionKind: exception_1.HTTP_EXCEPTION.BAD_REQUEST,
458
+ message: `The ${serviceName} fields already exists. ${selectKeys.join('、')}.`
459
+ }, actionLogOptions);
460
+ }
461
+ });
462
+ }
463
+ async assertNotProhibited(entity, actionLogOptions) {
464
+ if (entity.protected !== true) {
465
+ return;
466
+ }
467
+ const serviceNameForMessage = actionLogOptions && actionLogOptions.serviceName ? actionLogOptions.serviceName : 'entity';
468
+ await this.throwError({
469
+ code: exception_1.HTTP_EXCEPTION_CODE.PROHIBITED_OPERATIONS,
470
+ httpExceptionKind: exception_1.HTTP_EXCEPTION.FORBIDDEN,
471
+ message: `The ${serviceNameForMessage} prohibits all operations.`
472
+ }, actionLogOptions);
473
+ }
474
+ createSuccessActionLog(entities, actionLogOptions) {
475
+ return this.actionLogService.success({ entity: entities }, actionLogOptions);
476
+ }
477
+ async throwError({ httpExceptionKind, ...restOptions }, actionLogOptions) {
478
+ const error = (0, exception_1.createHttpException)(httpExceptionKind, restOptions);
479
+ if (actionLogOptions) {
480
+ await this.actionLogService.failure({ error }, { ...actionLogOptions, message: error.message });
481
+ }
482
+ throw error;
483
+ }
484
+ async preValidateUpdate({ actionLogOptions, currentEntity, preValidationOptions, repository, serviceName, where }) {
485
+ const { validationKeys = [], where: preValidationWhere, ...restOptions } = preValidationOptions;
486
+ const validationKeysSet = new Set(validationKeys.map(String));
487
+ for (const [key, value] of Object.entries(preValidationWhere)) {
488
+ if (typeof value !== 'undefined') {
489
+ validationKeysSet.add(key);
490
+ }
491
+ }
492
+ if (!validationKeysSet.size) {
493
+ return;
494
+ }
495
+ const nextWhere = {};
496
+ for (const key of validationKeysSet) {
497
+ const preValidationValue = preValidationWhere[key];
498
+ const whereValue = where[key];
499
+ nextWhere[key] =
500
+ typeof preValidationValue !== 'undefined' ? preValidationValue
501
+ : typeof whereValue !== 'undefined' ? whereValue
502
+ : currentEntity[key];
503
+ }
504
+ nextWhere.excludeId = currentEntity.id;
505
+ await this.preValidate(repository, {
506
+ ...restOptions,
507
+ actionLogOptions,
508
+ serviceName,
509
+ where: nextWhere
510
+ });
511
+ }
512
+ };
513
+ exports.BaseService = BaseService;
514
+ exports.BaseService = BaseService = __decorate([
515
+ (0, common_1.Injectable)(),
516
+ __metadata("design:paramtypes", [action_log_1.ActionLogService])
517
+ ], BaseService);
@@ -0,0 +1,3 @@
1
+ export * from './base-service.interface';
2
+ export * from './base-service.module';
3
+ export * from './base-service.service';
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./base-service.interface"), exports);
18
+ __exportStar(require("./base-service.module"), exports);
19
+ __exportStar(require("./base-service.service"), exports);
@@ -0,0 +1,19 @@
1
+ import { type PaginationQuery } from '@bearei/contracts';
2
+ import type { Order } from './common.interface';
3
+ export declare class ListQueryDto implements PaginationQuery {
4
+ page?: number;
5
+ pageSize?: number;
6
+ withTotal?: boolean;
7
+ sortOrder?: Order;
8
+ ids?: string[];
9
+ createdAtRange?: (Date | string)[];
10
+ }
11
+ export declare class ByIdDto {
12
+ id: string;
13
+ }
14
+ export declare class ByEmailDto {
15
+ email: string;
16
+ }
17
+ export declare class ExcludeIdDto {
18
+ excludeId?: string;
19
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ExcludeIdDto = exports.ByEmailDto = exports.ByIdDto = exports.ListQueryDto = void 0;
16
+ const contracts_1 = require("@bearei/contracts");
17
+ const class_transformer_1 = require("class-transformer");
18
+ const class_validator_1 = require("class-validator");
19
+ const dayjs_1 = __importDefault(require("dayjs"));
20
+ const common_enum_1 = require("./common.enum");
21
+ const decorators_1 = require("./decorators");
22
+ const toNumber = ({ value }) => value === '' || value === undefined || value === null ? value : Number(value);
23
+ class ListQueryDto {
24
+ page;
25
+ pageSize;
26
+ withTotal;
27
+ sortOrder;
28
+ ids;
29
+ createdAtRange;
30
+ }
31
+ exports.ListQueryDto = ListQueryDto;
32
+ __decorate([
33
+ (0, class_validator_1.IsOptional)(),
34
+ (0, class_validator_1.Min)(1),
35
+ (0, class_validator_1.IsInt)(),
36
+ (0, class_transformer_1.Transform)(toNumber),
37
+ __metadata("design:type", Number)
38
+ ], ListQueryDto.prototype, "page", void 0);
39
+ __decorate([
40
+ (0, class_validator_1.IsOptional)(),
41
+ (0, class_validator_1.Max)(contracts_1.MAX_PAGE_SIZE),
42
+ (0, class_validator_1.Min)(1),
43
+ (0, class_validator_1.IsInt)(),
44
+ (0, class_transformer_1.Transform)(toNumber),
45
+ __metadata("design:type", Number)
46
+ ], ListQueryDto.prototype, "pageSize", void 0);
47
+ __decorate([
48
+ (0, class_validator_1.IsOptional)(),
49
+ (0, class_validator_1.IsBoolean)(),
50
+ (0, class_transformer_1.Transform)(({ value }) => (typeof value === 'string' ? value === 'true' : value)),
51
+ __metadata("design:type", Boolean)
52
+ ], ListQueryDto.prototype, "withTotal", void 0);
53
+ __decorate([
54
+ (0, class_validator_1.IsOptional)(),
55
+ (0, class_validator_1.IsEnum)(common_enum_1.ORDER),
56
+ __metadata("design:type", String)
57
+ ], ListQueryDto.prototype, "sortOrder", void 0);
58
+ __decorate([
59
+ (0, class_validator_1.IsOptional)(),
60
+ (0, class_validator_1.ArrayMaxSize)(1024),
61
+ (0, class_validator_1.ArrayMinSize)(1),
62
+ (0, decorators_1.IsNanoId)({ each: true }),
63
+ (0, class_transformer_1.Transform)(({ value }) => (typeof value === 'string' ? value.split(',').filter(item => item) : value)),
64
+ __metadata("design:type", Array)
65
+ ], ListQueryDto.prototype, "ids", void 0);
66
+ __decorate([
67
+ (0, class_validator_1.IsOptional)(),
68
+ (0, class_validator_1.ArrayMaxSize)(2),
69
+ (0, class_validator_1.ArrayMinSize)(2),
70
+ (0, class_validator_1.IsDate)({ each: true }),
71
+ (0, class_transformer_1.Transform)(({ value }) => {
72
+ if (typeof value === 'string') {
73
+ return value
74
+ .split(',')
75
+ .filter(item => item)
76
+ .map(item => dayjs_1.default.utc(item).toDate());
77
+ }
78
+ if (Array.isArray(value)) {
79
+ return value.map(item => (item instanceof Date ? item : dayjs_1.default.utc(item).toDate()));
80
+ }
81
+ return value;
82
+ }),
83
+ __metadata("design:type", Array)
84
+ ], ListQueryDto.prototype, "createdAtRange", void 0);
85
+ class ByIdDto {
86
+ id;
87
+ }
88
+ exports.ByIdDto = ByIdDto;
89
+ __decorate([
90
+ (0, decorators_1.IsNanoId)(),
91
+ (0, class_validator_1.IsDefined)(),
92
+ __metadata("design:type", String)
93
+ ], ByIdDto.prototype, "id", void 0);
94
+ class ByEmailDto {
95
+ email;
96
+ }
97
+ exports.ByEmailDto = ByEmailDto;
98
+ __decorate([
99
+ (0, class_validator_1.IsEmail)(),
100
+ (0, class_validator_1.IsDefined)(),
101
+ __metadata("design:type", String)
102
+ ], ByEmailDto.prototype, "email", void 0);
103
+ class ExcludeIdDto {
104
+ excludeId;
105
+ }
106
+ exports.ExcludeIdDto = ExcludeIdDto;
107
+ __decorate([
108
+ (0, class_validator_1.IsOptional)(),
109
+ (0, decorators_1.IsNanoId)(),
110
+ __metadata("design:type", String)
111
+ ], ExcludeIdDto.prototype, "excludeId", void 0);
@@ -0,0 +1,11 @@
1
+ import { BaseEntity } from 'typeorm';
2
+ export declare class CommonEntity extends BaseEntity {
3
+ internalId: number;
4
+ id: string;
5
+ version: number;
6
+ createdAt: Date | string;
7
+ updatedAt: Date | string;
8
+ deletedAt: Date | string | null;
9
+ beforeInsert(): void;
10
+ beforeUpdate(): void;
11
+ }