rez_core 5.0.145 → 5.0.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/bull.config.js +4 -4
- package/dist/config/bull.config.js.map +1 -1
- package/dist/migrations/1732612800000-AddEntityJsonGinIndex.d.ts +6 -0
- package/dist/migrations/1732612800000-AddEntityJsonGinIndex.js +32 -0
- package/dist/migrations/1732612800000-AddEntityJsonGinIndex.js.map +1 -0
- package/dist/module/entity_json/entity_json.module.js +1 -1
- package/dist/module/entity_json/entity_json.module.js.map +1 -1
- package/dist/module/entity_json/service/entity_json.service.js +1 -1
- package/dist/module/entity_json/service/entity_json.service.js.map +1 -1
- package/dist/module/filter/controller/filter.controller.d.ts +12 -0
- package/dist/module/filter/controller/filter.controller.js +1 -1
- package/dist/module/filter/controller/filter.controller.js.map +1 -1
- package/dist/module/filter/filter.module.js +9 -2
- package/dist/module/filter/filter.module.js.map +1 -1
- package/dist/module/filter/repository/saved-filter.repository.d.ts +5 -1
- package/dist/module/filter/repository/saved-filter.repository.js +5 -1
- package/dist/module/filter/repository/saved-filter.repository.js.map +1 -1
- package/dist/module/filter/service/filter.service.d.ts +37 -1
- package/dist/module/filter/service/filter.service.js +30 -2
- package/dist/module/filter/service/filter.service.js.map +1 -1
- package/dist/module/filter/service/flatjson-filter.service.d.ts +30 -0
- package/dist/module/filter/service/flatjson-filter.service.js +615 -0
- package/dist/module/filter/service/flatjson-filter.service.js.map +1 -0
- package/dist/module/integration/service/wrapper.service.d.ts +1 -3
- package/dist/module/integration/service/wrapper.service.js +2 -24
- package/dist/module/integration/service/wrapper.service.js.map +1 -1
- package/dist/module/linked_attributes/controller/linked_attributes.controller.d.ts +19 -0
- package/dist/module/linked_attributes/controller/linked_attributes.controller.js +77 -0
- package/dist/module/linked_attributes/controller/linked_attributes.controller.js.map +1 -1
- package/dist/module/linked_attributes/dto/create-linked-attribute-smart.dto.d.ts +13 -0
- package/dist/module/linked_attributes/dto/create-linked-attribute-smart.dto.js +64 -0
- package/dist/module/linked_attributes/dto/create-linked-attribute-smart.dto.js.map +1 -0
- package/dist/module/linked_attributes/linked_attributes.module.js +8 -1
- package/dist/module/linked_attributes/linked_attributes.module.js.map +1 -1
- package/dist/module/linked_attributes/service/linked_attributes.service.d.ts +41 -1
- package/dist/module/linked_attributes/service/linked_attributes.service.js +266 -2
- package/dist/module/linked_attributes/service/linked_attributes.service.js.map +1 -1
- package/dist/module/meta/dto/entity-table.dto.d.ts +4 -1
- package/dist/module/meta/dto/entity-table.dto.js.map +1 -1
- package/dist/module/meta/service/media-data.service.js +5 -18
- package/dist/module/meta/service/media-data.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/config/bull.config.ts +4 -4
- package/src/migrations/1732612800000-AddEntityJsonGinIndex.ts +41 -0
- package/src/module/entity_json/docs/FlatJson_Filterin_System.md +2804 -0
- package/src/module/entity_json/entity_json.module.ts +1 -1
- package/src/module/entity_json/service/entity_json.service.ts +1 -1
- package/src/module/filter/controller/filter.controller.ts +1 -3
- package/src/module/filter/filter.module.ts +9 -2
- package/src/module/filter/repository/saved-filter.repository.ts +5 -8
- package/src/module/filter/service/filter.service.ts +49 -0
- package/src/module/filter/service/flatjson-filter.service.ts +888 -0
- package/src/module/filter/test/flatjson-filter.service.spec.ts +415 -0
- package/src/module/integration/service/wrapper.service.ts +0 -37
- package/src/module/linked_attributes/controller/linked_attributes.controller.ts +86 -0
- package/src/module/linked_attributes/dto/create-linked-attribute-smart.dto.ts +54 -0
- package/src/module/linked_attributes/linked_attributes.module.ts +9 -2
- package/src/module/linked_attributes/service/linked_attributes.service.ts +548 -3
- package/src/module/linked_attributes/test/linked-attributes.service.spec.ts +244 -0
- package/src/module/meta/dto/entity-table.dto.ts +1 -2
- package/src/module/meta/service/media-data.service.ts +9 -27
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { FlatjsonFilterService } from '../service/flatjson-filter.service';
|
|
3
|
+
import { EntityManager } from 'typeorm';
|
|
4
|
+
import { EntityMasterService } from 'src/module/meta/service/entity-master.service';
|
|
5
|
+
import { AttributeMasterService } from 'src/module/meta/service/attribute-master.service';
|
|
6
|
+
import { ResolverService } from 'src/module/meta/service/resolver.service';
|
|
7
|
+
import { LoggingService } from 'src/utils/service/loggingUtil.service';
|
|
8
|
+
import { ConfigService } from '@nestjs/config';
|
|
9
|
+
import { SavedFilterRepositoryService } from '../repository/saved-filter.repository';
|
|
10
|
+
|
|
11
|
+
describe('FlatjsonFilterService - Phase 2 Tests', () => {
|
|
12
|
+
let service: FlatjsonFilterService;
|
|
13
|
+
|
|
14
|
+
const mockEntityManager = {
|
|
15
|
+
getRepository: jest.fn(),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const mockEntityMasterService = {};
|
|
19
|
+
const mockAttributeMasterService = {
|
|
20
|
+
findAttributesByMappedEntityType: jest.fn(),
|
|
21
|
+
};
|
|
22
|
+
const mockResolverService = {
|
|
23
|
+
resolveEntitiesByIds: jest.fn(),
|
|
24
|
+
};
|
|
25
|
+
const mockLoggingService = {
|
|
26
|
+
log: jest.fn(),
|
|
27
|
+
};
|
|
28
|
+
const mockConfigService = {};
|
|
29
|
+
const mockSavedFilterRepositoryService = {
|
|
30
|
+
getDetailsByCode: jest.fn(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
35
|
+
providers: [
|
|
36
|
+
FlatjsonFilterService,
|
|
37
|
+
{ provide: EntityManager, useValue: mockEntityManager },
|
|
38
|
+
{ provide: EntityMasterService, useValue: mockEntityMasterService },
|
|
39
|
+
{
|
|
40
|
+
provide: AttributeMasterService,
|
|
41
|
+
useValue: mockAttributeMasterService,
|
|
42
|
+
},
|
|
43
|
+
{ provide: ResolverService, useValue: mockResolverService },
|
|
44
|
+
{ provide: LoggingService, useValue: mockLoggingService },
|
|
45
|
+
{ provide: ConfigService, useValue: mockConfigService },
|
|
46
|
+
{
|
|
47
|
+
provide: SavedFilterRepositoryService,
|
|
48
|
+
useValue: mockSavedFilterRepositoryService,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
}).compile();
|
|
52
|
+
|
|
53
|
+
service = module.get<FlatjsonFilterService>(FlatjsonFilterService);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
jest.clearAllMocks();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('Task 2.3: Text Conditions', () => {
|
|
61
|
+
it('should build equal condition for text', () => {
|
|
62
|
+
const condition = service['buildTextCondition'](
|
|
63
|
+
'LEAD__name',
|
|
64
|
+
'equal',
|
|
65
|
+
'John',
|
|
66
|
+
'param1',
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(condition).not.toBeNull();
|
|
70
|
+
expect(condition?.query).toContain("json_data->>'LEAD__name'");
|
|
71
|
+
expect(condition?.query).toContain('=');
|
|
72
|
+
expect(condition?.params.param1).toBe('john'); // lowercase
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should build contains condition with LIKE', () => {
|
|
76
|
+
const condition = service['buildTextCondition'](
|
|
77
|
+
'LEAD__name',
|
|
78
|
+
'contains',
|
|
79
|
+
'John',
|
|
80
|
+
'param1',
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(condition?.query).toContain('LIKE');
|
|
84
|
+
expect(condition?.params.param1).toBe('%john%');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should build starts_with condition', () => {
|
|
88
|
+
const condition = service['buildTextCondition'](
|
|
89
|
+
'LEAD__name',
|
|
90
|
+
'starts_with',
|
|
91
|
+
'John',
|
|
92
|
+
'param1',
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(condition?.params.param1).toBe('john%');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should build ends_with condition', () => {
|
|
99
|
+
const condition = service['buildTextCondition'](
|
|
100
|
+
'LEAD__name',
|
|
101
|
+
'ends_with',
|
|
102
|
+
'Smith',
|
|
103
|
+
'param1',
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(condition?.params.param1).toBe('%smith');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should build empty condition', () => {
|
|
110
|
+
const condition = service['buildTextCondition'](
|
|
111
|
+
'LEAD__name',
|
|
112
|
+
'empty',
|
|
113
|
+
null,
|
|
114
|
+
'param1',
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(condition?.query).toContain('IS NULL');
|
|
118
|
+
expect(Object.keys(condition?.params || {})).toHaveLength(0);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('Task 2.4: Number Conditions', () => {
|
|
123
|
+
it('should build equal condition for numbers', () => {
|
|
124
|
+
const condition = service['buildNumberCondition'](
|
|
125
|
+
'LEAD__age',
|
|
126
|
+
'equal',
|
|
127
|
+
25,
|
|
128
|
+
'param1',
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
expect(condition?.query).toContain('::numeric');
|
|
132
|
+
expect(condition?.query).toContain('=');
|
|
133
|
+
expect(condition?.params.param1).toBe(25);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should build greater_than condition', () => {
|
|
137
|
+
const condition = service['buildNumberCondition'](
|
|
138
|
+
'LEAD__age',
|
|
139
|
+
'greater_than',
|
|
140
|
+
18,
|
|
141
|
+
'param1',
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(condition?.query).toContain('>');
|
|
145
|
+
expect(condition?.params.param1).toBe(18);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should build between condition with array', () => {
|
|
149
|
+
const condition = service['buildNumberCondition'](
|
|
150
|
+
'LEAD__age',
|
|
151
|
+
'between',
|
|
152
|
+
[18, 65],
|
|
153
|
+
'param1',
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(condition?.query).toContain('BETWEEN');
|
|
157
|
+
expect(condition?.params.param1_min).toBe(18);
|
|
158
|
+
expect(condition?.params.param1_max).toBe(65);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should build between condition with string', () => {
|
|
162
|
+
const condition = service['buildNumberCondition'](
|
|
163
|
+
'LEAD__age',
|
|
164
|
+
'between',
|
|
165
|
+
'18, 65',
|
|
166
|
+
'param1',
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(condition?.query).toContain('BETWEEN');
|
|
170
|
+
expect(condition?.params.param1_min).toBe(18);
|
|
171
|
+
expect(condition?.params.param1_max).toBe(65);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('Task 2.5: Date Conditions', () => {
|
|
176
|
+
it('should build equal condition for dates', () => {
|
|
177
|
+
const condition = service['buildDateCondition'](
|
|
178
|
+
'LEAD__created_date',
|
|
179
|
+
'equal',
|
|
180
|
+
'2024-01-01',
|
|
181
|
+
'param1',
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(condition?.query).toContain('::bigint');
|
|
185
|
+
expect(condition?.query).toContain('=');
|
|
186
|
+
expect(typeof condition?.params.param1).toBe('number');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should build before condition', () => {
|
|
190
|
+
const condition = service['buildDateCondition'](
|
|
191
|
+
'LEAD__created_date',
|
|
192
|
+
'before',
|
|
193
|
+
'2024-12-31',
|
|
194
|
+
'param1',
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(condition?.query).toContain('<');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should build between condition for dates', () => {
|
|
201
|
+
const condition = service['buildDateCondition'](
|
|
202
|
+
'LEAD__created_date',
|
|
203
|
+
'between',
|
|
204
|
+
['2024-01-01', '2024-12-31'],
|
|
205
|
+
'param1',
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(condition?.query).toContain('BETWEEN');
|
|
209
|
+
expect(condition?.params.param1_start).toBeDefined();
|
|
210
|
+
expect(condition?.params.param1_end).toBeDefined();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should build today condition', () => {
|
|
214
|
+
const condition = service['buildDateCondition'](
|
|
215
|
+
'LEAD__created_date',
|
|
216
|
+
'today',
|
|
217
|
+
null,
|
|
218
|
+
'param1',
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(condition?.query).toContain('BETWEEN');
|
|
222
|
+
expect(condition?.params.param1_start).toBeDefined();
|
|
223
|
+
expect(condition?.params.param1_end).toBeDefined();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should build in_last_day condition', () => {
|
|
227
|
+
const condition = service['buildDateCondition'](
|
|
228
|
+
'LEAD__created_date',
|
|
229
|
+
'in_last_day',
|
|
230
|
+
7,
|
|
231
|
+
'param1',
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(condition?.query).toContain('BETWEEN');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('Task 2.6: Select/Multiselect Conditions', () => {
|
|
239
|
+
it('should build equal condition for select', () => {
|
|
240
|
+
const condition = service['buildSelectCondition'](
|
|
241
|
+
'LEAD__status',
|
|
242
|
+
'equal',
|
|
243
|
+
'NEW',
|
|
244
|
+
'param1',
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(condition?.query).toContain("json_data->>'LEAD__status'");
|
|
248
|
+
expect(condition?.params.param1).toBe('NEW');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should build IN condition for array values', () => {
|
|
252
|
+
const condition = service['buildSelectCondition'](
|
|
253
|
+
'LEAD__status',
|
|
254
|
+
'equal',
|
|
255
|
+
['NEW', 'ACTIVE'],
|
|
256
|
+
'param1',
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
expect(condition?.query).toContain('ANY');
|
|
260
|
+
expect(Array.isArray(condition?.params.param1)).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should build contains condition for multiselect', () => {
|
|
264
|
+
const condition = service['buildMultiSelectCondition'](
|
|
265
|
+
'LEAD__languages',
|
|
266
|
+
'contains',
|
|
267
|
+
'hindi',
|
|
268
|
+
'param1',
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
expect(condition?.query).toContain('?');
|
|
272
|
+
expect(condition?.params.param1).toBe('hindi');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should build contains_all condition', () => {
|
|
276
|
+
const condition = service['buildMultiSelectCondition'](
|
|
277
|
+
'LEAD__languages',
|
|
278
|
+
'contains_all',
|
|
279
|
+
['hindi', 'english'],
|
|
280
|
+
'param1',
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
expect(condition?.query).toContain('?&');
|
|
284
|
+
expect(Array.isArray(condition?.params.param1)).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should build contains_any condition', () => {
|
|
288
|
+
const condition = service['buildMultiSelectCondition'](
|
|
289
|
+
'LEAD__languages',
|
|
290
|
+
'contains_any',
|
|
291
|
+
['hindi', 'english'],
|
|
292
|
+
'param1',
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
expect(condition?.query).toContain('?|');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should handle comma-separated string input', () => {
|
|
299
|
+
const condition = service['buildMultiSelectCondition'](
|
|
300
|
+
'LEAD__languages',
|
|
301
|
+
'contains_any',
|
|
302
|
+
'hindi, english, tamil',
|
|
303
|
+
'param1',
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
expect(Array.isArray(condition?.params.param1)).toBe(true);
|
|
307
|
+
expect(condition?.params.param1).toHaveLength(3);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe('Task 2.9: Sorting', () => {
|
|
312
|
+
it('should apply numeric sorting for number fields', () => {
|
|
313
|
+
const mockQb = {
|
|
314
|
+
orderBy: jest.fn().mockReturnThis(),
|
|
315
|
+
addOrderBy: jest.fn().mockReturnThis(),
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const attributeMetaMap = {
|
|
319
|
+
age: {
|
|
320
|
+
data_type: 'number',
|
|
321
|
+
flat_json_key: 'LEAD__age',
|
|
322
|
+
mapped_entity_type: 'LEAD',
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const sortby = [{ sortColum: 'age', sortType: 'DESC' }];
|
|
327
|
+
|
|
328
|
+
service['applyJsonbSorting'](mockQb as any, sortby, attributeMetaMap);
|
|
329
|
+
|
|
330
|
+
expect(mockQb.orderBy).toHaveBeenCalled();
|
|
331
|
+
const call = mockQb.orderBy.mock.calls[0];
|
|
332
|
+
expect(call[0]).toContain('::numeric');
|
|
333
|
+
expect(call[1]).toBe('DESC');
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should apply bigint sorting for date fields', () => {
|
|
337
|
+
const mockQb = {
|
|
338
|
+
orderBy: jest.fn().mockReturnThis(),
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const attributeMetaMap = {
|
|
342
|
+
created_date: {
|
|
343
|
+
data_type: 'date',
|
|
344
|
+
flat_json_key: 'LEAD__created_date',
|
|
345
|
+
mapped_entity_type: 'LEAD',
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const sortby = [{ sortColum: 'created_date', sortType: 'ASC' }];
|
|
350
|
+
|
|
351
|
+
service['applyJsonbSorting'](mockQb as any, sortby, attributeMetaMap);
|
|
352
|
+
|
|
353
|
+
const call = mockQb.orderBy.mock.calls[0];
|
|
354
|
+
expect(call[0]).toContain('::bigint');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should apply text sorting for text fields', () => {
|
|
358
|
+
const mockQb = {
|
|
359
|
+
orderBy: jest.fn().mockReturnThis(),
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const attributeMetaMap = {
|
|
363
|
+
name: {
|
|
364
|
+
data_type: 'text',
|
|
365
|
+
flat_json_key: 'LEAD__name',
|
|
366
|
+
mapped_entity_type: 'LEAD',
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const sortby = [{ sortColum: 'name', sortType: 'ASC' }];
|
|
371
|
+
|
|
372
|
+
service['applyJsonbSorting'](mockQb as any, sortby, attributeMetaMap);
|
|
373
|
+
|
|
374
|
+
const call = mockQb.orderBy.mock.calls[0];
|
|
375
|
+
expect(call[0]).not.toContain('::');
|
|
376
|
+
expect(call[0]).toContain("->>"); // Text extraction
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('Edge Cases', () => {
|
|
381
|
+
it('should handle NULL values in text conditions', () => {
|
|
382
|
+
const condition = service['buildTextCondition'](
|
|
383
|
+
'LEAD__name',
|
|
384
|
+
'equal',
|
|
385
|
+
null,
|
|
386
|
+
'param1',
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
expect(condition).not.toBeNull();
|
|
390
|
+
expect(condition?.params.param1).toBe('');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should return null for unsupported operators', () => {
|
|
394
|
+
const condition = service['buildTextCondition'](
|
|
395
|
+
'LEAD__name',
|
|
396
|
+
'invalid_operator',
|
|
397
|
+
'test',
|
|
398
|
+
'param1',
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
expect(condition).toBeNull();
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should handle empty array in multiselect', () => {
|
|
405
|
+
const condition = service['buildMultiSelectCondition'](
|
|
406
|
+
'LEAD__languages',
|
|
407
|
+
'contains',
|
|
408
|
+
[],
|
|
409
|
+
'param1',
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
expect(condition?.query).toBe('1=1'); // Always true
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
});
|
|
@@ -3,8 +3,6 @@ import { GenericMessageDto, IntegrationService } from './integration.service';
|
|
|
3
3
|
import { IcsMeetingService } from 'src/module/ics/service/ics.service';
|
|
4
4
|
import { LoggingService } from 'src/utils/service/loggingUtil.service';
|
|
5
5
|
import { ReflectionHelper } from '../../../utils/service/reflection-helper.service';
|
|
6
|
-
import { MediaDataService } from 'src/module/meta/service/media-data.service';
|
|
7
|
-
import axios from 'axios';
|
|
8
6
|
|
|
9
7
|
@Injectable()
|
|
10
8
|
export class WrapperService {
|
|
@@ -15,7 +13,6 @@ export class WrapperService {
|
|
|
15
13
|
private readonly icsService: IcsMeetingService,
|
|
16
14
|
private readonly loggingService: LoggingService,
|
|
17
15
|
private readonly reflectionHelper: ReflectionHelper,
|
|
18
|
-
private readonly mediaService: MediaDataService,
|
|
19
16
|
) {}
|
|
20
17
|
|
|
21
18
|
/**
|
|
@@ -408,40 +405,6 @@ export class WrapperService {
|
|
|
408
405
|
[],
|
|
409
406
|
);
|
|
410
407
|
|
|
411
|
-
// attachments: [1, 2, 3, 4]
|
|
412
|
-
|
|
413
|
-
if (entity.attachments && entity.attachments.length > 0) {
|
|
414
|
-
mailPayload.attachments = [];
|
|
415
|
-
|
|
416
|
-
for (const attachmentId of entity.attachments) {
|
|
417
|
-
// Step 1: Download or get media file
|
|
418
|
-
const url = await this.mediaService.getMediaDownloadUrl(
|
|
419
|
-
attachmentId,
|
|
420
|
-
{},
|
|
421
|
-
60000,
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
if (!url?.signedUrl) continue;
|
|
425
|
-
|
|
426
|
-
// Step 2: Fetch file content
|
|
427
|
-
const response = await axios.get(url.signedUrl, {
|
|
428
|
-
responseType: 'arraybuffer',
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
// Step 3: Convert to base64
|
|
432
|
-
const base64Content = Buffer.from(response.data).toString('base64');
|
|
433
|
-
|
|
434
|
-
// Step 4: Push formatted attachment
|
|
435
|
-
mailPayload.attachments.push({
|
|
436
|
-
content: base64Content,
|
|
437
|
-
type:
|
|
438
|
-
response.headers['content-type'] || 'application/octet-stream',
|
|
439
|
-
filename: `attachment-${attachmentId}`,
|
|
440
|
-
disposition: 'attachment',
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
408
|
// Step 5️⃣ Send via Integration Service
|
|
446
409
|
const result =
|
|
447
410
|
await this.integrationService.sendGenericMessage(mailPayload);
|
|
@@ -6,10 +6,15 @@ import {
|
|
|
6
6
|
Post,
|
|
7
7
|
Req,
|
|
8
8
|
UseGuards,
|
|
9
|
+
Param,
|
|
10
|
+
Query,
|
|
9
11
|
} from '@nestjs/common';
|
|
12
|
+
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
10
13
|
import { LinkedAttributesService } from '../service/linked_attributes.service';
|
|
11
14
|
import { JwtAuthGuard } from 'src/module/auth/guards/jwt.guard';
|
|
15
|
+
import { CreateLinkedAttributeSmartDto } from '../dto/create-linked-attribute-smart.dto';
|
|
12
16
|
|
|
17
|
+
@ApiTags('Linked Attributes')
|
|
13
18
|
@Controller('linked-attributes')
|
|
14
19
|
export class LinkedAttributesController {
|
|
15
20
|
constructor(
|
|
@@ -34,4 +39,85 @@ export class LinkedAttributesController {
|
|
|
34
39
|
loggedInUser,
|
|
35
40
|
);
|
|
36
41
|
}
|
|
42
|
+
|
|
43
|
+
@Post('smart')
|
|
44
|
+
@UseGuards(JwtAuthGuard)
|
|
45
|
+
@ApiOperation({ summary: 'Create linked attribute with auto-generation' })
|
|
46
|
+
@ApiResponse({
|
|
47
|
+
status: 201,
|
|
48
|
+
description: 'Linked attribute created successfully',
|
|
49
|
+
})
|
|
50
|
+
@ApiResponse({ status: 400, description: 'Validation failed' })
|
|
51
|
+
async createSmart(
|
|
52
|
+
@Body() dto: CreateLinkedAttributeSmartDto,
|
|
53
|
+
@Req() req: any,
|
|
54
|
+
) {
|
|
55
|
+
const loggedInUser = req.user?.userData;
|
|
56
|
+
return await this.linkedAttributesService.createLinkedAttributeSmart(
|
|
57
|
+
dto,
|
|
58
|
+
loggedInUser,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Post(':id/backfill')
|
|
63
|
+
@UseGuards(JwtAuthGuard)
|
|
64
|
+
@ApiOperation({
|
|
65
|
+
summary: 'Backfill existing entities with new linked attribute',
|
|
66
|
+
})
|
|
67
|
+
@ApiResponse({ status: 200, description: 'Backfill completed' })
|
|
68
|
+
@ApiResponse({ status: 404, description: 'Linked attribute not found' })
|
|
69
|
+
async backfill(@Param('id') id: number, @Req() req: any) {
|
|
70
|
+
const loggedInUser = req.user?.userData;
|
|
71
|
+
return await this.linkedAttributesService.backfillLinkedAttribute(
|
|
72
|
+
id,
|
|
73
|
+
loggedInUser,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Post('backfill-all')
|
|
78
|
+
@UseGuards(JwtAuthGuard)
|
|
79
|
+
@ApiOperation({ summary: 'Backfill all entities of a given type' })
|
|
80
|
+
@ApiResponse({ status: 200, description: 'Backfill completed' })
|
|
81
|
+
async backfillAll(
|
|
82
|
+
@Query('entity_type') entity_type: string,
|
|
83
|
+
@Req() req: any,
|
|
84
|
+
) {
|
|
85
|
+
const loggedInUser = req.user?.userData;
|
|
86
|
+
return await this.linkedAttributesService.backfillAllForEntity(
|
|
87
|
+
entity_type,
|
|
88
|
+
loggedInUser,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@Get('preview-key')
|
|
93
|
+
@UseGuards(JwtAuthGuard)
|
|
94
|
+
@ApiOperation({
|
|
95
|
+
summary: 'Preview generated attribute key before creation',
|
|
96
|
+
})
|
|
97
|
+
@ApiResponse({ status: 200, description: 'Preview generated successfully' })
|
|
98
|
+
async previewKey(
|
|
99
|
+
@Query('mapped_entity_type') mapped_entity_type: string,
|
|
100
|
+
@Query('entity_type') entity_type: string,
|
|
101
|
+
@Query('attribute_key') attribute_key: string,
|
|
102
|
+
@Query('organization_id') organization_id: number,
|
|
103
|
+
@Req() req: any,
|
|
104
|
+
) {
|
|
105
|
+
const loggedInUser = req.user?.userData;
|
|
106
|
+
|
|
107
|
+
const sequence =
|
|
108
|
+
await this.linkedAttributesService['generateNextSequence'](
|
|
109
|
+
mapped_entity_type,
|
|
110
|
+
entity_type,
|
|
111
|
+
attribute_key,
|
|
112
|
+
organization_id,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const generated_key = this.linkedAttributesService.generateAttributeKey(
|
|
116
|
+
entity_type,
|
|
117
|
+
attribute_key,
|
|
118
|
+
sequence,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return { generated_key, sequence };
|
|
122
|
+
}
|
|
37
123
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IsString,
|
|
3
|
+
IsNotEmpty,
|
|
4
|
+
IsArray,
|
|
5
|
+
IsOptional,
|
|
6
|
+
IsBoolean,
|
|
7
|
+
ValidateNested,
|
|
8
|
+
} from 'class-validator';
|
|
9
|
+
import { Type } from 'class-transformer';
|
|
10
|
+
|
|
11
|
+
export class FilterConditionDto {
|
|
12
|
+
@IsString()
|
|
13
|
+
@IsNotEmpty()
|
|
14
|
+
filter_attribute: string;
|
|
15
|
+
|
|
16
|
+
@IsString()
|
|
17
|
+
@IsNotEmpty()
|
|
18
|
+
filter_operator: string;
|
|
19
|
+
|
|
20
|
+
@IsNotEmpty()
|
|
21
|
+
filter_value: any;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class CreateLinkedAttributeSmartDto {
|
|
25
|
+
@IsString()
|
|
26
|
+
@IsNotEmpty()
|
|
27
|
+
field_name: string;
|
|
28
|
+
|
|
29
|
+
@IsString()
|
|
30
|
+
@IsNotEmpty()
|
|
31
|
+
mapped_entity_type: string;
|
|
32
|
+
|
|
33
|
+
@IsString()
|
|
34
|
+
@IsNotEmpty()
|
|
35
|
+
applicable_entity_type: string;
|
|
36
|
+
|
|
37
|
+
@IsString()
|
|
38
|
+
@IsNotEmpty()
|
|
39
|
+
applicable_attribute_key: string;
|
|
40
|
+
|
|
41
|
+
@IsString()
|
|
42
|
+
@IsNotEmpty()
|
|
43
|
+
saved_filter_code: string;
|
|
44
|
+
|
|
45
|
+
// @IsArray()
|
|
46
|
+
// @IsOptional()
|
|
47
|
+
// @ValidateNested({ each: true })
|
|
48
|
+
// @Type(() => FilterConditionDto)
|
|
49
|
+
// filter_conditions?: FilterConditionDto[];
|
|
50
|
+
|
|
51
|
+
@IsBoolean()
|
|
52
|
+
@IsOptional()
|
|
53
|
+
backfill?: boolean;
|
|
54
|
+
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import { Module } from '@nestjs/common';
|
|
1
|
+
import { Module, forwardRef } from '@nestjs/common';
|
|
2
2
|
import { EntityModule } from '../meta/entity.module';
|
|
3
|
+
import { FilterModule } from '../filter/filter.module';
|
|
4
|
+
import { EntityJSONModule } from '../entity_json/entity_json.module';
|
|
3
5
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
4
6
|
import { LinkedAttributes } from './entity/linked_attribute.entity';
|
|
5
7
|
import { LinkedAttributesController } from './controller/linked_attributes.controller';
|
|
6
8
|
import { LinkedAttributesService } from './service/linked_attributes.service';
|
|
7
9
|
|
|
8
10
|
@Module({
|
|
9
|
-
imports: [
|
|
11
|
+
imports: [
|
|
12
|
+
EntityModule,
|
|
13
|
+
forwardRef(() => FilterModule),
|
|
14
|
+
forwardRef(() => EntityJSONModule),
|
|
15
|
+
TypeOrmModule.forFeature([LinkedAttributes]),
|
|
16
|
+
],
|
|
10
17
|
controllers: [LinkedAttributesController],
|
|
11
18
|
providers: [
|
|
12
19
|
{ provide: 'LinkedAttributesService', useClass: LinkedAttributesService },
|