@flusys/nestjs-localization 7.0.2 → 8.0.0-beta.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 (117) hide show
  1. package/config/index.d.ts +3 -3
  2. package/controllers/index.d.ts +3 -3
  3. package/controllers/language.controller.d.ts +4 -4
  4. package/controllers/translation-key.controller.d.ts +3 -3
  5. package/controllers/translation.controller.d.ts +4 -4
  6. package/docs/index.d.ts +1 -1
  7. package/docs/localization-swagger.config.d.ts +1 -1
  8. package/dtos/index.d.ts +3 -3
  9. package/dtos/translation.dto.d.ts +2 -2
  10. package/entities/index.d.ts +6 -6
  11. package/entities/language.entity.d.ts +1 -1
  12. package/entities/translation-key.entity.d.ts +1 -1
  13. package/entities/translation.entity.d.ts +2 -2
  14. package/fesm/349.js +1179 -0
  15. package/fesm/692.js +1205 -0
  16. package/fesm/config/index.js +101 -3
  17. package/fesm/controllers/index.js +15 -3
  18. package/fesm/docs/index.js +126 -1
  19. package/fesm/dtos/index.js +680 -3
  20. package/fesm/entities/index.js +405 -8
  21. package/fesm/index.js +257 -7
  22. package/fesm/interfaces/index.js +18 -4
  23. package/fesm/modules/index.js +152 -1
  24. package/fesm/runtime.js +85 -0
  25. package/fesm/services/index.js +15 -5
  26. package/index.d.ts +7 -7
  27. package/interfaces/index.d.ts +4 -4
  28. package/interfaces/language.interface.d.ts +1 -1
  29. package/interfaces/localization-module-options.interface.d.ts +3 -3
  30. package/interfaces/translation-key.interface.d.ts +1 -1
  31. package/interfaces/translation.interface.d.ts +3 -3
  32. package/modules/index.d.ts +1 -1
  33. package/modules/localization.module.d.ts +2 -2
  34. package/package.json +25 -34
  35. package/services/index.d.ts +5 -5
  36. package/services/language.service.d.ts +6 -6
  37. package/services/localization-config.service.d.ts +3 -3
  38. package/services/localization-datasource.provider.d.ts +3 -3
  39. package/services/translation-key.service.d.ts +6 -6
  40. package/services/translation.service.d.ts +5 -5
  41. package/cjs/config/event-actions.js +0 -33
  42. package/cjs/config/index.js +0 -20
  43. package/cjs/config/localization.constants.js +0 -11
  44. package/cjs/config/message-keys.js +0 -41
  45. package/cjs/controllers/index.js +0 -20
  46. package/cjs/controllers/language.controller.js +0 -188
  47. package/cjs/controllers/language.controller.spec.js +0 -154
  48. package/cjs/controllers/translation-key.controller.js +0 -135
  49. package/cjs/controllers/translation-key.controller.spec.js +0 -70
  50. package/cjs/controllers/translation.controller.js +0 -195
  51. package/cjs/controllers/translation.controller.spec.js +0 -169
  52. package/cjs/docs/index.js +0 -11
  53. package/cjs/docs/localization-swagger.config.js +0 -111
  54. package/cjs/docs/localization-swagger.config.spec.js +0 -30
  55. package/cjs/dtos/index.js +0 -20
  56. package/cjs/dtos/language.dto.js +0 -213
  57. package/cjs/dtos/translation-key.dto.js +0 -215
  58. package/cjs/dtos/translation.dto.js +0 -269
  59. package/cjs/entities/index.js +0 -42
  60. package/cjs/entities/index.spec.js +0 -18
  61. package/cjs/entities/language.entity.js +0 -126
  62. package/cjs/entities/translation-key.entity.js +0 -126
  63. package/cjs/entities/translation.entity.js +0 -120
  64. package/cjs/index.js +0 -24
  65. package/cjs/interfaces/index.js +0 -21
  66. package/cjs/interfaces/language.interface.js +0 -4
  67. package/cjs/interfaces/localization-module-options.interface.js +0 -4
  68. package/cjs/interfaces/translation-key.interface.js +0 -4
  69. package/cjs/interfaces/translation.interface.js +0 -4
  70. package/cjs/modules/index.js +0 -18
  71. package/cjs/modules/localization.module.js +0 -116
  72. package/cjs/modules/localization.module.spec.js +0 -132
  73. package/cjs/services/index.js +0 -22
  74. package/cjs/services/language.service.js +0 -181
  75. package/cjs/services/language.service.spec.js +0 -256
  76. package/cjs/services/localization-config.service.js +0 -66
  77. package/cjs/services/localization-config.service.spec.js +0 -80
  78. package/cjs/services/localization-datasource.provider.js +0 -79
  79. package/cjs/services/localization-datasource.provider.spec.js +0 -85
  80. package/cjs/services/translation-key.service.js +0 -179
  81. package/cjs/services/translation-key.service.spec.js +0 -303
  82. package/cjs/services/translation.service.js +0 -228
  83. package/cjs/services/translation.service.spec.js +0 -530
  84. package/fesm/config/event-actions.js +0 -12
  85. package/fesm/config/localization.constants.js +0 -1
  86. package/fesm/config/message-keys.js +0 -20
  87. package/fesm/controllers/language.controller.js +0 -178
  88. package/fesm/controllers/language.controller.spec.js +0 -150
  89. package/fesm/controllers/translation-key.controller.js +0 -125
  90. package/fesm/controllers/translation-key.controller.spec.js +0 -66
  91. package/fesm/controllers/translation.controller.js +0 -185
  92. package/fesm/controllers/translation.controller.spec.js +0 -165
  93. package/fesm/docs/localization-swagger.config.js +0 -101
  94. package/fesm/docs/localization-swagger.config.spec.js +0 -26
  95. package/fesm/dtos/language.dto.js +0 -192
  96. package/fesm/dtos/translation-key.dto.js +0 -194
  97. package/fesm/dtos/translation.dto.js +0 -239
  98. package/fesm/entities/index.spec.js +0 -14
  99. package/fesm/entities/language.entity.js +0 -116
  100. package/fesm/entities/translation-key.entity.js +0 -116
  101. package/fesm/entities/translation.entity.js +0 -110
  102. package/fesm/interfaces/language.interface.js +0 -1
  103. package/fesm/interfaces/localization-module-options.interface.js +0 -1
  104. package/fesm/interfaces/translation-key.interface.js +0 -1
  105. package/fesm/interfaces/translation.interface.js +0 -1
  106. package/fesm/modules/localization.module.js +0 -106
  107. package/fesm/modules/localization.module.spec.js +0 -128
  108. package/fesm/services/language.service.js +0 -171
  109. package/fesm/services/language.service.spec.js +0 -252
  110. package/fesm/services/localization-config.service.js +0 -56
  111. package/fesm/services/localization-config.service.spec.js +0 -76
  112. package/fesm/services/localization-datasource.provider.js +0 -69
  113. package/fesm/services/localization-datasource.provider.spec.js +0 -81
  114. package/fesm/services/translation-key.service.js +0 -169
  115. package/fesm/services/translation-key.service.spec.js +0 -299
  116. package/fesm/services/translation.service.js +0 -218
  117. package/fesm/services/translation.service.spec.js +0 -526
@@ -1,169 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- function _ts_decorate(decorators, target, key, desc) {
15
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
- 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;
18
- return c > 3 && r && Object.defineProperty(target, key, r), r;
19
- }
20
- function _ts_metadata(k, v) {
21
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
- }
23
- function _ts_param(paramIndex, decorator) {
24
- return function(target, key) {
25
- decorator(target, key, paramIndex);
26
- };
27
- }
28
- import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
29
- import { UtilsService } from '@flusys/nestjs-shared/modules';
30
- import { BadRequestException, ForbiddenException, Inject, Injectable, Scope } from '@nestjs/common';
31
- import { In, IsNull } from 'typeorm';
32
- import { LOCALIZATION_EVENT_ENTITIES, LOCALIZATION_EVENT_MODULE, TRANSLATION_KEY_MESSAGES } from '../config';
33
- import { TranslationKey } from '../entities';
34
- import { LocalizationDataSourceProvider } from './localization-datasource.provider';
35
- export class TranslationKeyService extends ApiService {
36
- async beforeInsertOperation(dto, _user, _queryRunner) {
37
- const dtos = Array.isArray(dto) ? dto : [
38
- dto
39
- ];
40
- await this.validateNoDuplicateKeys(dtos.map((d)=>({
41
- module: d.module,
42
- key: d.key
43
- })));
44
- }
45
- async beforeUpdateOperation(dto, _user, _queryRunner) {
46
- const dtos = Array.isArray(dto) ? dto : [
47
- dto
48
- ];
49
- const ids = dtos.map((d)=>d.id);
50
- await this.ensureDataSourceRepository();
51
- const existingKeys = await this.repository.find({
52
- where: {
53
- id: In(ids),
54
- deletedAt: IsNull()
55
- }
56
- });
57
- // Check if any readonly keys are being updated with key/module changes
58
- for (const d of dtos){
59
- const existing = existingKeys.find((e)=>e.id === d.id);
60
- const changesIdentity = d.key !== undefined && d.key !== existing?.key || d.module !== undefined && d.module !== existing?.module;
61
- if (existing?.isReadonly && changesIdentity) {
62
- throw new ForbiddenException({
63
- message: `Cannot modify key or module of readonly translation key "${existing.key}"`,
64
- messageKey: TRANSLATION_KEY_MESSAGES.READONLY_UPDATE_FORBIDDEN,
65
- messageVariables: {
66
- key: existing.key
67
- }
68
- });
69
- }
70
- }
71
- // Validate no duplicates for keys being changed
72
- const keysToValidate = dtos.filter((d)=>d.key || d.module).map((d)=>{
73
- const existing = existingKeys.find((e)=>e.id === d.id);
74
- return {
75
- module: d.module ?? existing?.module ?? '',
76
- key: d.key ?? existing?.key ?? '',
77
- excludeId: d.id
78
- };
79
- });
80
- if (keysToValidate.length > 0) {
81
- await this.validateNoDuplicateKeys(keysToValidate);
82
- }
83
- }
84
- async beforeDeleteOperation(option, _user, _queryRunner) {
85
- const ids = Array.isArray(option.id) ? option.id : [
86
- option.id
87
- ];
88
- await this.ensureDataSourceRepository();
89
- const readonlyKeys = await this.repository.find({
90
- where: {
91
- id: In(ids),
92
- isReadonly: true,
93
- deletedAt: IsNull()
94
- }
95
- });
96
- if (readonlyKeys.length > 0) {
97
- const keys = readonlyKeys.map((k)=>k.key).join(', ');
98
- throw new ForbiddenException({
99
- message: `Cannot delete readonly translation keys: ${keys}`,
100
- messageKey: TRANSLATION_KEY_MESSAGES.READONLY_DELETE_FORBIDDEN,
101
- messageVariables: {
102
- keys
103
- }
104
- });
105
- }
106
- }
107
- async findByModule(module) {
108
- await this.ensureDataSourceRepository();
109
- return this.repository.find({
110
- where: {
111
- module,
112
- isActive: true,
113
- deletedAt: IsNull()
114
- },
115
- order: {
116
- serial: 'ASC',
117
- key: 'ASC'
118
- }
119
- });
120
- }
121
- async getModules() {
122
- await this.ensureDataSourceRepository();
123
- const result = await this.repository.createQueryBuilder('tk').select('DISTINCT tk.module', 'module').where('tk.deleted_at IS NULL').getRawMany();
124
- return result.map((r)=>r.module);
125
- }
126
- async validateNoDuplicateKeys(keys) {
127
- await this.ensureDataSourceRepository();
128
- for (const { module, key, excludeId } of keys){
129
- const query = this.repository.createQueryBuilder('tk').where('tk.module = :module', {
130
- module
131
- }).andWhere('tk.key = :key', {
132
- key
133
- }).andWhere('tk.deleted_at IS NULL');
134
- if (excludeId) {
135
- query.andWhere('tk.id != :excludeId', {
136
- excludeId
137
- });
138
- }
139
- const existing = await query.getOne();
140
- if (existing) {
141
- throw new BadRequestException({
142
- message: `Translation key "${key}" already exists in module "${module}"`,
143
- messageKey: TRANSLATION_KEY_MESSAGES.DUPLICATE_KEY_IN_MODULE,
144
- messageVariables: {
145
- key,
146
- module
147
- }
148
- });
149
- }
150
- }
151
- }
152
- constructor(cacheManager, utilsService, dataSourceProvider){
153
- super(LOCALIZATION_EVENT_ENTITIES.TRANSLATION_KEY, cacheManager, utilsService, TranslationKeyService.name, true, LOCALIZATION_EVENT_MODULE, dataSourceProvider, TranslationKey), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService;
154
- }
155
- }
156
- TranslationKeyService = _ts_decorate([
157
- Injectable({
158
- scope: Scope.REQUEST
159
- }),
160
- _ts_param(0, Inject('CACHE_INSTANCE')),
161
- _ts_param(1, Inject(UtilsService)),
162
- _ts_param(2, Inject(LocalizationDataSourceProvider)),
163
- _ts_metadata("design:type", Function),
164
- _ts_metadata("design:paramtypes", [
165
- typeof HybridCache === "undefined" ? Object : HybridCache,
166
- typeof UtilsService === "undefined" ? Object : UtilsService,
167
- typeof LocalizationDataSourceProvider === "undefined" ? Object : LocalizationDataSourceProvider
168
- ])
169
- ], TranslationKeyService);
@@ -1,299 +0,0 @@
1
- import { Test } from '@nestjs/testing';
2
- import { UtilsService } from '@flusys/nestjs-shared/modules';
3
- import { ForbiddenException, BadRequestException } from '@nestjs/common';
4
- import { createMockDataSourceProvider, createMockRepository } from '@test-utils/mocks/repository.mock';
5
- import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
6
- import { TranslationKeyService } from './translation-key.service';
7
- import { LocalizationDataSourceProvider } from './localization-datasource.provider';
8
- function buildTranslationKey(overrides = {}) {
9
- return {
10
- id: 'tkey-uuid-1',
11
- module: 'auth',
12
- key: 'auth.login.success',
13
- defaultMessage: 'Login successful',
14
- description: null,
15
- variables: null,
16
- isActive: true,
17
- isReadonly: false,
18
- serial: 1,
19
- createdAt: new Date(),
20
- updatedAt: new Date(),
21
- deletedAt: null,
22
- createdById: null,
23
- updatedById: null,
24
- deletedById: null,
25
- ...overrides
26
- };
27
- }
28
- describe('TranslationKeyService', ()=>{
29
- let service;
30
- let mockRepo;
31
- let qb;
32
- let queryRunner;
33
- beforeEach(async ()=>{
34
- mockRepo = createMockRepository();
35
- qb = mockRepo.createQueryBuilder();
36
- const mockDataSourceProvider = createMockDataSourceProvider(mockRepo);
37
- // executeInTransaction()/delete() always grab a QueryRunner off repository.manager.connection
38
- // before running the before*Operation hooks, so every test (success or rejection) needs one.
39
- queryRunner = {
40
- connect: jest.fn(),
41
- startTransaction: jest.fn(),
42
- commitTransaction: jest.fn(),
43
- rollbackTransaction: jest.fn(),
44
- release: jest.fn(),
45
- manager: {
46
- save: jest.fn(),
47
- softDelete: jest.fn(),
48
- update: jest.fn(),
49
- delete: jest.fn(),
50
- restore: jest.fn()
51
- }
52
- };
53
- mockRepo.manager.connection.createQueryRunner.mockReturnValue(queryRunner);
54
- const mockUtilsService = {
55
- clearCache: jest.fn(),
56
- getCacheKey: jest.fn().mockReturnValue('cache-key'),
57
- trackCacheKey: jest.fn()
58
- };
59
- const module = await Test.createTestingModule({
60
- providers: [
61
- TranslationKeyService,
62
- {
63
- provide: 'CACHE_INSTANCE',
64
- useValue: {
65
- get: jest.fn(),
66
- set: jest.fn()
67
- }
68
- },
69
- {
70
- provide: UtilsService,
71
- useValue: mockUtilsService
72
- },
73
- {
74
- provide: LocalizationDataSourceProvider,
75
- useValue: mockDataSourceProvider
76
- }
77
- ]
78
- }).compile();
79
- service = await module.resolve(TranslationKeyService);
80
- });
81
- describe('findByModule', ()=>{
82
- it('returns active, non-deleted keys for the module ordered by serial then key', async ()=>{
83
- const keys = [
84
- buildTranslationKey({
85
- key: 'auth.login.success',
86
- serial: 1
87
- })
88
- ];
89
- mockRepo.find.mockResolvedValue(keys);
90
- const result = await service.findByModule('auth');
91
- expect(mockRepo.find).toHaveBeenCalledWith({
92
- where: {
93
- module: 'auth',
94
- isActive: true,
95
- deletedAt: expect.anything()
96
- },
97
- order: {
98
- serial: 'ASC',
99
- key: 'ASC'
100
- }
101
- });
102
- expect(result).toEqual(keys);
103
- });
104
- it('returns an empty array for an unknown module', async ()=>{
105
- mockRepo.find.mockResolvedValue([]);
106
- const result = await service.findByModule('does-not-exist');
107
- expect(result).toEqual([]);
108
- });
109
- });
110
- describe('getModules', ()=>{
111
- it('returns distinct module names', async ()=>{
112
- // `getRawMany` isn't part of the shared MockQueryBuilder — attach it for this raw-query path.
113
- const getRawMany = jest.fn().mockResolvedValue([
114
- {
115
- module: 'auth'
116
- },
117
- {
118
- module: 'shared'
119
- }
120
- ]);
121
- qb.getRawMany = getRawMany;
122
- const result = await service.getModules();
123
- expect(result).toEqual([
124
- 'auth',
125
- 'shared'
126
- ]);
127
- });
128
- it('returns an empty array when there are no translation keys yet', async ()=>{
129
- const getRawMany = jest.fn().mockResolvedValue([]);
130
- qb.getRawMany = getRawMany;
131
- const result = await service.getModules();
132
- expect(result).toEqual([]);
133
- });
134
- });
135
- describe('beforeInsertOperation (via insert) — duplicate key validation', ()=>{
136
- it('allows insert when no key with the same module+key exists', async ()=>{
137
- qb.getOne.mockResolvedValue(null);
138
- const created = buildTranslationKey();
139
- mockRepo.create.mockReturnValue({});
140
- queryRunner.manager.save.mockResolvedValue(created);
141
- await expect(service.insert({
142
- module: 'auth',
143
- key: 'auth.login.success',
144
- defaultMessage: 'Login successful'
145
- }, buildMockUser())).resolves.toBeDefined();
146
- });
147
- it('rejects with BadRequestException + messageKey when the module+key combination already exists', async ()=>{
148
- qb.getOne.mockResolvedValue(buildTranslationKey());
149
- await expect(service.insert({
150
- module: 'auth',
151
- key: 'auth.login.success',
152
- defaultMessage: 'Login successful'
153
- }, buildMockUser())).rejects.toMatchObject({
154
- response: expect.objectContaining({
155
- messageKey: 'translation.key.duplicate.key.in.module'
156
- })
157
- });
158
- });
159
- it('rejects with BadRequestException for duplicate module+key in insertMany', async ()=>{
160
- qb.getOne.mockResolvedValueOnce(null).mockResolvedValueOnce(buildTranslationKey({
161
- key: 'auth.logout.success'
162
- }));
163
- await expect(service.insertMany([
164
- {
165
- module: 'auth',
166
- key: 'auth.login.success',
167
- defaultMessage: 'A'
168
- },
169
- {
170
- module: 'auth',
171
- key: 'auth.logout.success',
172
- defaultMessage: 'B'
173
- }
174
- ], buildMockUser())).rejects.toBeInstanceOf(BadRequestException);
175
- });
176
- });
177
- describe('beforeUpdateOperation (via update)', ()=>{
178
- it('rejects with ForbiddenException when modifying key/module of a readonly translation key', async ()=>{
179
- const existing = buildTranslationKey({
180
- id: 'tkey-1',
181
- isReadonly: true,
182
- key: 'system.ready'
183
- });
184
- mockRepo.find.mockResolvedValue([
185
- existing
186
- ]);
187
- await expect(service.update({
188
- id: 'tkey-1',
189
- key: 'system.changed'
190
- }, buildMockUser())).rejects.toBeInstanceOf(ForbiddenException);
191
- await expect(service.update({
192
- id: 'tkey-1',
193
- key: 'system.changed'
194
- }, buildMockUser())).rejects.toMatchObject({
195
- response: expect.objectContaining({
196
- messageKey: 'translation.key.readonly.update.forbidden'
197
- })
198
- });
199
- });
200
- it('allows updating non key/module fields of a readonly translation key', async ()=>{
201
- const existing = buildTranslationKey({
202
- id: 'tkey-1',
203
- isReadonly: true,
204
- key: 'system.ready'
205
- });
206
- mockRepo.find.mockResolvedValue([
207
- existing
208
- ]);
209
- mockRepo.findOne.mockResolvedValue(existing);
210
- queryRunner.manager.save.mockResolvedValue(existing);
211
- await expect(service.update({
212
- id: 'tkey-1',
213
- defaultMessage: 'Updated readonly message'
214
- }, buildMockUser())).resolves.toBeDefined();
215
- });
216
- it('allows resubmitting the same unchanged key/module of a readonly translation key', async ()=>{
217
- // Regression test: the edit form always resends key + module even when only
218
- // other fields changed, so the check must compare values, not just presence.
219
- const existing = buildTranslationKey({
220
- id: 'tkey-1',
221
- isReadonly: true,
222
- key: 'system.ready',
223
- module: 'system'
224
- });
225
- mockRepo.find.mockResolvedValue([
226
- existing
227
- ]);
228
- mockRepo.findOne.mockResolvedValue(existing);
229
- queryRunner.manager.save.mockResolvedValue(existing);
230
- await expect(service.update({
231
- id: 'tkey-1',
232
- key: 'system.ready',
233
- module: 'system',
234
- defaultMessage: 'Updated readonly message'
235
- }, buildMockUser())).resolves.toBeDefined();
236
- });
237
- it('rejects with BadRequestException when the new key/module already exists on another row', async ()=>{
238
- const existing = buildTranslationKey({
239
- id: 'tkey-1',
240
- isReadonly: false,
241
- key: 'auth.old',
242
- module: 'auth'
243
- });
244
- mockRepo.find.mockResolvedValue([
245
- existing
246
- ]);
247
- qb.getOne.mockResolvedValue(buildTranslationKey({
248
- id: 'tkey-other',
249
- key: 'auth.new'
250
- }));
251
- await expect(service.update({
252
- id: 'tkey-1',
253
- key: 'auth.new'
254
- }, buildMockUser())).rejects.toMatchObject({
255
- response: expect.objectContaining({
256
- messageKey: 'translation.key.duplicate.key.in.module'
257
- })
258
- });
259
- // excludeId must be used so the row being updated doesn't collide with itself
260
- expect(qb.andWhere).toHaveBeenCalledWith('tk.id != :excludeId', {
261
- excludeId: 'tkey-1'
262
- });
263
- });
264
- });
265
- describe('beforeDeleteOperation (via delete)', ()=>{
266
- it('rejects with ForbiddenException + messageKey when any target id is readonly', async ()=>{
267
- const readonlyKey = buildTranslationKey({
268
- id: 'tkey-1',
269
- isReadonly: true,
270
- key: 'system.core'
271
- });
272
- mockRepo.find.mockResolvedValue([
273
- readonlyKey
274
- ]);
275
- const dto = {
276
- id: [
277
- 'tkey-1'
278
- ],
279
- type: 'delete'
280
- };
281
- await expect(service.delete(dto, buildMockUser())).rejects.toMatchObject({
282
- response: expect.objectContaining({
283
- messageKey: 'translation.key.readonly.delete.forbidden'
284
- })
285
- });
286
- });
287
- it('allows delete when none of the targeted ids are readonly', async ()=>{
288
- mockRepo.find.mockResolvedValue([]);
289
- const dto = {
290
- id: [
291
- 'tkey-1'
292
- ],
293
- type: 'delete'
294
- };
295
- await expect(service.delete(dto, buildMockUser())).resolves.toBeNull();
296
- expect(queryRunner.manager.softDelete).toHaveBeenCalled();
297
- });
298
- });
299
- });
@@ -1,218 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- function _ts_decorate(decorators, target, key, desc) {
15
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
- 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;
18
- return c > 3 && r && Object.defineProperty(target, key, r), r;
19
- }
20
- function _ts_metadata(k, v) {
21
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
- }
23
- function _ts_param(paramIndex, decorator) {
24
- return function(target, key) {
25
- decorator(target, key, paramIndex);
26
- };
27
- }
28
- import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
29
- import { UtilsService } from '@flusys/nestjs-shared/modules';
30
- import { Inject, Injectable, Scope } from '@nestjs/common';
31
- import { In, IsNull } from 'typeorm';
32
- import { LOCALIZATION_EVENT_ACTIONS, LOCALIZATION_EVENT_ENTITIES, LOCALIZATION_EVENT_MODULE } from '../config';
33
- import { Language, Translation, TranslationKey } from '../entities';
34
- import { LocalizationDataSourceProvider } from './localization-datasource.provider';
35
- export class TranslationService extends ApiService {
36
- async getFilterQuery(query, filter, _user) {
37
- // Handle module filter by joining to translation_key table
38
- if (filter.module) {
39
- query.innerJoin('translation.translationKey', 'tk');
40
- query.andWhere('tk.module = :module', {
41
- module: filter.module
42
- });
43
- }
44
- // Handle standard filters on translation table
45
- if (filter.languageId) {
46
- query.andWhere('translation.languageId = :languageId', {
47
- languageId: filter.languageId
48
- });
49
- }
50
- if (filter.translationKeyId) {
51
- query.andWhere('translation.translationKeyId = :translationKeyId', {
52
- translationKeyId: filter.translationKeyId
53
- });
54
- }
55
- return {
56
- query,
57
- isRaw: false
58
- };
59
- }
60
- async ensureAllReposInitialized() {
61
- await this.ensureDataSourceRepository();
62
- if (!this.languageRepo) {
63
- const ds = await this.dataSourceProvider.getDataSource();
64
- this.languageRepo = ds.getRepository(Language);
65
- this.translationKeyRepo = ds.getRepository(TranslationKey);
66
- }
67
- }
68
- async getByLanguageCode(languageCode, modules) {
69
- await this.ensureAllReposInitialized();
70
- const language = await this.findActiveLanguageByCode(languageCode);
71
- if (!language) return {};
72
- const qb = this.repository.createQueryBuilder('t').innerJoinAndSelect('t.translationKey', 'tk').where('t.language_id = :languageId', {
73
- languageId: language.id
74
- }).andWhere('t.is_active = true').andWhere('t.deleted_at IS NULL').andWhere('tk.is_active = true').andWhere('tk.deleted_at IS NULL');
75
- if (modules && modules.length > 0) {
76
- qb.andWhere('tk.module IN (:...modules)', {
77
- modules
78
- });
79
- }
80
- return this.toTranslationsMap(await qb.getMany());
81
- }
82
- async getByModuleAndLanguage(module, languageCode) {
83
- await this.ensureAllReposInitialized();
84
- const language = await this.findActiveLanguageByCode(languageCode);
85
- if (!language) return {};
86
- const translations = await this.repository.createQueryBuilder('t').innerJoinAndSelect('t.translationKey', 'tk').where('t.language_id = :languageId', {
87
- languageId: language.id
88
- }).andWhere('tk.module = :module', {
89
- module
90
- }).andWhere('t.is_active = true').andWhere('t.deleted_at IS NULL').andWhere('tk.is_active = true').andWhere('tk.deleted_at IS NULL').getMany();
91
- return this.toTranslationsMap(translations);
92
- }
93
- async bulkUpdate(updates) {
94
- await this.ensureDataSourceRepository();
95
- if (updates.length === 0) return [];
96
- const ds = await this.dataSourceProvider.getDataSource();
97
- const queryRunner = ds.createQueryRunner();
98
- await queryRunner.connect();
99
- await queryRunner.startTransaction();
100
- try {
101
- const resultIds = [];
102
- for (const update of updates){
103
- if (update.id) {
104
- // Update existing translation
105
- await queryRunner.manager.update(Translation, {
106
- id: update.id
107
- }, {
108
- value: update.value,
109
- isVerified: update.isVerified ?? false
110
- });
111
- resultIds.push(update.id);
112
- } else {
113
- // Create new translation
114
- const newTranslation = queryRunner.manager.create(Translation, {
115
- languageId: update.languageId,
116
- translationKeyId: update.translationKeyId,
117
- value: update.value,
118
- isVerified: update.isVerified ?? false,
119
- isActive: true
120
- });
121
- const saved = await queryRunner.manager.save(newTranslation);
122
- resultIds.push(saved.id);
123
- }
124
- }
125
- await queryRunner.commitTransaction();
126
- const saved = await this.repository.find({
127
- where: {
128
- id: In(resultIds)
129
- }
130
- });
131
- await Promise.all([
132
- this.clearCacheForAll(),
133
- this.clearCacheForId(saved)
134
- ]);
135
- await this.publishDomainAction(LOCALIZATION_EVENT_ACTIONS.BULK_UPDATED, {
136
- ids: saved.map((translation)=>translation.id),
137
- metadata: {
138
- count: saved.length
139
- }
140
- });
141
- return saved;
142
- } catch (error) {
143
- await queryRunner.rollbackTransaction();
144
- throw error;
145
- } finally{
146
- await queryRunner.release();
147
- }
148
- }
149
- async getMissingTranslations(languageCode) {
150
- await this.ensureAllReposInitialized();
151
- const language = await this.findActiveLanguageByCode(languageCode);
152
- if (!language) return [];
153
- if (!this.translationKeyRepo) return [];
154
- const allKeys = await this.translationKeyRepo.find({
155
- where: {
156
- isActive: true,
157
- deletedAt: IsNull()
158
- }
159
- });
160
- const existingTranslations = await this.repository.find({
161
- where: {
162
- languageId: language.id,
163
- isActive: true,
164
- deletedAt: IsNull()
165
- },
166
- select: [
167
- 'translationKeyId'
168
- ]
169
- });
170
- const existingKeyIds = new Set(existingTranslations.map((t)=>t.translationKeyId));
171
- return allKeys.filter((k)=>!existingKeyIds.has(k.id));
172
- }
173
- async getTranslationCounts() {
174
- await this.ensureDataSourceRepository();
175
- const counts = await this.repository.createQueryBuilder('t').select('t.translation_key_id', 'keyId').addSelect('COUNT(t.id)', 'count').where('t.is_active = true').andWhere('t.deleted_at IS NULL').groupBy('t.translation_key_id').getRawMany();
176
- const result = {};
177
- for (const row of counts){
178
- result[row.keyId] = parseInt(row.count, 10);
179
- }
180
- return result;
181
- }
182
- async findActiveLanguageByCode(code) {
183
- if (!this.languageRepo) return null;
184
- return this.languageRepo.findOne({
185
- where: {
186
- code,
187
- isActive: true,
188
- deletedAt: IsNull()
189
- }
190
- });
191
- }
192
- toTranslationsMap(translations) {
193
- const result = {};
194
- for (const t of translations){
195
- if (t.translationKey) {
196
- result[t.translationKey.key] = t.value;
197
- }
198
- }
199
- return result;
200
- }
201
- constructor(cacheManager, utilsService, dataSourceProvider){
202
- super(LOCALIZATION_EVENT_ENTITIES.TRANSLATION, cacheManager, utilsService, TranslationService.name, true, LOCALIZATION_EVENT_MODULE, dataSourceProvider, Translation), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "languageRepo", void 0), _define_property(this, "translationKeyRepo", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.languageRepo = null, this.translationKeyRepo = null;
203
- }
204
- }
205
- TranslationService = _ts_decorate([
206
- Injectable({
207
- scope: Scope.REQUEST
208
- }),
209
- _ts_param(0, Inject('CACHE_INSTANCE')),
210
- _ts_param(1, Inject(UtilsService)),
211
- _ts_param(2, Inject(LocalizationDataSourceProvider)),
212
- _ts_metadata("design:type", Function),
213
- _ts_metadata("design:paramtypes", [
214
- typeof HybridCache === "undefined" ? Object : HybridCache,
215
- typeof UtilsService === "undefined" ? Object : UtilsService,
216
- typeof LocalizationDataSourceProvider === "undefined" ? Object : LocalizationDataSourceProvider
217
- ])
218
- ], TranslationService);