@solidstarters/solid-core 1.2.158 → 1.2.160

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 (61) hide show
  1. package/dist/factories/whatsapp.factory.d.ts.map +1 -1
  2. package/dist/factories/whatsapp.factory.js +1 -1
  3. package/dist/factories/whatsapp.factory.js.map +1 -1
  4. package/dist/helpers/model-metadata-helper.service.d.ts +5 -1
  5. package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
  6. package/dist/helpers/model-metadata-helper.service.js +32 -2
  7. package/dist/helpers/model-metadata-helper.service.js.map +1 -1
  8. package/dist/helpers/string.helper.d.ts +2 -0
  9. package/dist/helpers/string.helper.d.ts.map +1 -0
  10. package/dist/helpers/string.helper.js +10 -0
  11. package/dist/helpers/string.helper.js.map +1 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/repository/security-rule.repository.d.ts.map +1 -1
  17. package/dist/repository/security-rule.repository.js +16 -9
  18. package/dist/repository/security-rule.repository.js.map +1 -1
  19. package/dist/repository/user.repository.d.ts +12 -0
  20. package/dist/repository/user.repository.d.ts.map +1 -0
  21. package/dist/repository/user.repository.js +34 -0
  22. package/dist/repository/user.repository.js.map +1 -0
  23. package/dist/seeders/seed-data/solid-core-metadata.json +40 -6
  24. package/dist/services/chatter-message.service.d.ts +8 -2
  25. package/dist/services/chatter-message.service.d.ts.map +1 -1
  26. package/dist/services/chatter-message.service.js +155 -30
  27. package/dist/services/chatter-message.service.js.map +1 -1
  28. package/dist/services/crud-helper.service.d.ts +7 -3
  29. package/dist/services/crud-helper.service.d.ts.map +1 -1
  30. package/dist/services/crud-helper.service.js +17 -5
  31. package/dist/services/crud-helper.service.js.map +1 -1
  32. package/dist/services/user.service.d.ts +3 -2
  33. package/dist/services/user.service.d.ts.map +1 -1
  34. package/dist/services/user.service.js +2 -2
  35. package/dist/services/user.service.js.map +1 -1
  36. package/dist/services/view-metadata.service.d.ts +3 -1
  37. package/dist/services/view-metadata.service.d.ts.map +1 -1
  38. package/dist/services/view-metadata.service.js +5 -2
  39. package/dist/services/view-metadata.service.js.map +1 -1
  40. package/dist/solid-core.module.d.ts.map +1 -1
  41. package/dist/solid-core.module.js +2 -0
  42. package/dist/solid-core.module.js.map +1 -1
  43. package/dist/subscribers/audit.subscriber.d.ts +3 -1
  44. package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
  45. package/dist/subscribers/audit.subscriber.js +10 -5
  46. package/dist/subscribers/audit.subscriber.js.map +1 -1
  47. package/dist/tsconfig.tsbuildinfo +1 -1
  48. package/package.json +1 -1
  49. package/src/factories/whatsapp.factory.ts +2 -1
  50. package/src/helpers/model-metadata-helper.service.ts +30 -2
  51. package/src/helpers/string.helper.ts +4 -0
  52. package/src/index.ts +1 -0
  53. package/src/repository/security-rule.repository.ts +25 -12
  54. package/src/repository/user.repository.ts +17 -0
  55. package/src/seeders/seed-data/solid-core-metadata.json +40 -6
  56. package/src/services/chatter-message.service.ts +185 -35
  57. package/src/services/crud-helper.service.ts +24 -6
  58. package/src/services/user.service.ts +3 -2
  59. package/src/services/view-metadata.service.ts +3 -1
  60. package/src/solid-core.module.ts +3 -0
  61. package/src/subscribers/audit.subscriber.ts +9 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidstarters/solid-core",
3
- "version": "1.2.158",
3
+ "version": "1.2.160",
4
4
  "description": "This module is a NestJS module containing all the required core providers required by a Solid application",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,11 +22,12 @@ export class WhatsAppFactory {
22
22
 
23
23
  getWhatsappService(name: string = null): IWhatsAppTransport {
24
24
  // This is the default provider
25
- const whatsappServiceName = this.commonConfiguration.whatsappProvider || name;
25
+ const whatsappServiceName = name || this.commonConfiguration.whatsappProvider;
26
26
  if (!whatsappServiceName) {
27
27
  throw new Error("Unable to resolve whatsapp provider")
28
28
  }
29
29
  const whatsappProviders = this.solidRegistry.getWhatsappProviders();
30
+
30
31
  // Return the instance which matches the whatsappServiceName
31
32
  if (!whatsappProviders.length) {
32
33
  // throw new Error("No mail providers are registered.");
@@ -2,13 +2,18 @@
2
2
 
3
3
  import { Injectable, Logger } from "@nestjs/common";
4
4
  import { SolidRegistry } from "./solid-registry";
5
- import { In } from "typeorm";
5
+ import { In, Repository } from "typeorm";
6
+ import { InjectRepository } from "@nestjs/typeorm";
7
+ import { ModelMetadata } from "src/entities/model-metadata.entity";
6
8
 
7
9
  @Injectable()
8
10
  export class ModelMetadataHelperService {
9
11
  private readonly logger = new Logger(ModelMetadataHelperService.name);
10
12
 
11
- constructor(private readonly registry: SolidRegistry) {
13
+ constructor(private readonly registry: SolidRegistry,
14
+ @InjectRepository(ModelMetadata)
15
+ private readonly modelMetadataRepo: Repository<ModelMetadata>,
16
+ ) {
12
17
  }
13
18
 
14
19
  getSystemFieldsMetadata(): any[] {
@@ -105,5 +110,28 @@ export class ModelMetadataHelperService {
105
110
  return systemFieldsMetadata;
106
111
  }
107
112
 
113
+ async loadFieldHierarchy(modelName: any) {
114
+ const model = await this.modelMetadataRepo.findOne({
115
+ where: {
116
+ singularName: modelName,
117
+ },
118
+ relations: {
119
+ fields: true,
120
+ parentModel: {
121
+ fields: true,
122
+ }
123
+ }
124
+ });
125
+ const fields = [];
126
+ if (model) {
127
+ // Add the fields of the current model
128
+ fields.push(...model.fields);
108
129
 
130
+ // Add the fields of the parent model
131
+ if (model.parentModel) {
132
+ fields.push(...model.parentModel.fields);
133
+ }
134
+ }
135
+ return fields;
136
+ }
109
137
  }
@@ -0,0 +1,4 @@
1
+ export const lowerFirst = (str: string): string => {
2
+ if (!str) return str;
3
+ return str.charAt(0).toLowerCase() + str.slice(1);
4
+ }
package/src/index.ts CHANGED
@@ -279,6 +279,7 @@ export * from './factories/mail.factory'
279
279
  export * from './repository/solid-base.repository'
280
280
  export * from './repository/security-rule.repository'
281
281
  export * from './repository/field.repository'
282
+ export * from './repository/chatter-message.repository'
282
283
 
283
284
 
284
285
  //softDeleteAwareEventSubscriber.subscriber.ts
@@ -8,8 +8,8 @@ import { RoleMetadata } from 'src/entities/role-metadata.entity';
8
8
  import { SecurityRule } from 'src/entities/security-rule.entity';
9
9
  import { SolidRegistry } from 'src/helpers/solid-registry';
10
10
  import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
11
- import { CrudHelperService } from 'src/services/crud-helper.service';
12
- import { DataSource, Repository, SelectQueryBuilder } from 'typeorm';
11
+ import { CrudHelperService, FilterCombinator } from 'src/services/crud-helper.service';
12
+ import { Brackets, DataSource, Repository, SelectQueryBuilder } from 'typeorm';
13
13
 
14
14
  @Injectable()
15
15
  export class SecurityRuleRepository extends Repository<SecurityRule> {
@@ -26,18 +26,31 @@ export class SecurityRuleRepository extends Repository<SecurityRule> {
26
26
  // Fetch the security rules for the model and roles
27
27
  const securityRules = this.solidRegistry.getSecurityRules(modelSingularName, activeUser.roles);
28
28
 
29
- // Loop through the security rules and add only rules that are json parseable and have a rule
30
- securityRules.forEach((rule: SecurityRule) => {
31
- try {
32
- // Parse the security rule and call the buildFilter method to build the query from the security rule
33
- const parsedRule = JSON.parse(this.resolveSecurityRuleConfig(rule.securityRuleConfig, activeUser)) as SecurityRuleConfig;
29
+ // If no security rules, return the original query builder
30
+ if (!securityRules.length) {
31
+ return qb;
32
+ }
33
+
34
+ // Apply each security rule to the query builder. The rules are combined with OR logic at the top level.
35
+ qb.andWhere(new Brackets((outerQb) => {
36
+ for (const rule of securityRules) {
37
+ try {
38
+ const parsedRule = JSON.parse(
39
+ this.resolveSecurityRuleConfig(rule.securityRuleConfig, activeUser)
40
+ ) as SecurityRuleConfig;
41
+
34
42
  if (parsedRule && parsedRule.filters) {
35
- this.crudHelperService.buildFilterQuery(qb, parsedRule, securityRuleAlias);
43
+ outerQb.orWhere( // combine each rule-group with OR at the outer level
44
+ new Brackets((innerQb) => {
45
+ this.crudHelperService.applyFilters(innerQb, parsedRule.filters, securityRuleAlias, qb); // AND within a rule
46
+ })
47
+ );
48
+ }
49
+ } catch (error) {
50
+ this.logger.warn(`Error parsing security rule: ${rule.securityRuleConfig}`, error);
36
51
  }
37
- } catch (error) {
38
- this.logger.warn(`Error parsing security rule: ${rule.securityRuleConfig}`, error);
39
52
  }
40
- });
53
+ }));
41
54
 
42
55
  return qb;
43
56
  }
@@ -85,7 +98,7 @@ export class SecurityRuleRepository extends Repository<SecurityRule> {
85
98
  },
86
99
  });
87
100
  createDto['role'] = role;
88
- }
101
+ }
89
102
 
90
103
  if (createDto.roleUserKey) {
91
104
  const role = await roleRepository.findOne({
@@ -0,0 +1,17 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { User } from "src/entities/user.entity";
3
+ import { RequestContextService } from "src/services/request-context.service";
4
+ import { DataSource } from "typeorm";
5
+ import { SecurityRuleRepository } from "./security-rule.repository";
6
+ import { SolidBaseRepository } from "./solid-base.repository";
7
+
8
+ @Injectable()
9
+ export class UserRepository extends SolidBaseRepository<User> {
10
+ constructor(
11
+ readonly dataSource: DataSource,
12
+ readonly requestContextService: RequestContextService,
13
+ readonly securityRuleRepository: SecurityRuleRepository,
14
+ ) {
15
+ super(User, dataSource, requestContextService, securityRuleRepository);
16
+ }
17
+ }
@@ -1697,6 +1697,7 @@
1697
1697
  "dataSourceType": "postgres",
1698
1698
  "userKeyFieldUserKey": "username",
1699
1699
  "isSystem": true,
1700
+ "enableAuditTracking": true,
1700
1701
  "fields": [
1701
1702
  {
1702
1703
  "name": "fullName",
@@ -1709,7 +1710,8 @@
1709
1710
  "index": false,
1710
1711
  "private": false,
1711
1712
  "encrypt": false,
1712
- "isSystem": true
1713
+ "isSystem": true,
1714
+ "enableAuditTracking": true
1713
1715
  },
1714
1716
  {
1715
1717
  "name": "username",
@@ -1722,7 +1724,8 @@
1722
1724
  "index": true,
1723
1725
  "private": false,
1724
1726
  "encrypt": false,
1725
- "isSystem": true
1727
+ "isSystem": true,
1728
+ "enableAuditTracking": true
1726
1729
  },
1727
1730
  {
1728
1731
  "name": "email",
@@ -1735,7 +1738,8 @@
1735
1738
  "index": true,
1736
1739
  "private": false,
1737
1740
  "encrypt": false,
1738
- "isSystem": true
1741
+ "isSystem": true,
1742
+ "enableAuditTracking": true
1739
1743
  },
1740
1744
  {
1741
1745
  "name": "mobile",
@@ -1748,7 +1752,8 @@
1748
1752
  "index": true,
1749
1753
  "private": false,
1750
1754
  "encrypt": false,
1751
- "isSystem": true
1755
+ "isSystem": true,
1756
+ "enableAuditTracking": true
1752
1757
  },
1753
1758
  {
1754
1759
  "name": "password",
@@ -1884,7 +1889,8 @@
1884
1889
  "relationCascade": "cascade",
1885
1890
  "relationModelModuleName": "solid-core",
1886
1891
  "isSystem": true,
1887
- "isRelationManyToManyOwner": true
1892
+ "isRelationManyToManyOwner": true,
1893
+ "enableAuditTracking": true
1888
1894
  },
1889
1895
  {
1890
1896
  "name": "forgotPasswordConfirmedAt",
@@ -12256,5 +12262,33 @@
12256
12262
  ],
12257
12263
  "checksums": [],
12258
12264
  "listOfValues": [],
12259
- "scheduledJobs": []
12265
+ "scheduledJobs": [],
12266
+ "securityRules": [
12267
+ {
12268
+ "name": "model:User-role:Internal User",
12269
+ "description": "Show User record for the current user only",
12270
+ "roleUserKey": "Internal User",
12271
+ "modelMetadataUserKey": "user",
12272
+ "securityRuleConfig": {
12273
+ "filters": {
12274
+ "id": {
12275
+ "$eq": "$activeUserId"
12276
+ }
12277
+ }
12278
+ }
12279
+ },
12280
+ {
12281
+ "name": "model:User-role:Admin",
12282
+ "description": "Show All User records",
12283
+ "roleUserKey": "Admin",
12284
+ "modelMetadataUserKey": "user",
12285
+ "securityRuleConfig": {
12286
+ "filters": {
12287
+ "id": {
12288
+ "$ne": "0"
12289
+ }
12290
+ }
12291
+ }
12292
+ }
12293
+ ]
12260
12294
  }
@@ -18,6 +18,8 @@ import { ChatterMessageDetails } from '../entities/chatter-message-details.entit
18
18
  import { ModelMetadata } from 'src/entities/model-metadata.entity';
19
19
  import { RequestContextService } from './request-context.service';
20
20
  import { ChatterMessageRepository } from 'src/repository/chatter-message.repository';
21
+ import { lowerFirst } from 'src/helpers/string.helper';
22
+ import { ModelMetadataHelperService } from 'src/helpers/model-metadata-helper.service';
21
23
  @Injectable()
22
24
  export class ChatterMessageService extends CRUDService<ChatterMessage>{
23
25
  constructor(
@@ -36,7 +38,8 @@ export class ChatterMessageService extends CRUDService<ChatterMessage>{
36
38
  readonly moduleRef: ModuleRef,
37
39
  @InjectRepository(ModelMetadata)
38
40
  private readonly modelMetadataRepo: Repository<ModelMetadata>,
39
- readonly requestContextService: RequestContextService
41
+ readonly requestContextService: RequestContextService,
42
+ private readonly modelMetadataHelperService: ModelMetadataHelperService,
40
43
  ) {
41
44
  super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService,entityManager, repo, 'chatterMessage', 'solid-core', moduleRef);
42
45
  }
@@ -91,7 +94,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage>{
91
94
  }
92
95
  const model = await this.modelMetadataRepo.findOne({
93
96
  where: {
94
- displayName: metadata.name
97
+ singularName: lowerFirst(metadata.name)
95
98
  },
96
99
  relations: {
97
100
  fields: true,
@@ -142,13 +145,13 @@ export class ChatterMessageService extends CRUDService<ChatterMessage>{
142
145
  }
143
146
  }
144
147
 
145
- async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEntity: any, messageQueue: boolean = false) {
148
+ async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEntity: any, updatedColumns: any[] = [], messageQueue: boolean = false) {
146
149
  if(!databaseEntity || !entity){
147
150
  return;
148
151
  }
149
152
  const model = await this.modelMetadataRepo.findOne({
150
153
  where: {
151
- displayName: metadata.name
154
+ singularName: lowerFirst(metadata.name)
152
155
  },
153
156
  relations: {
154
157
  fields: true,
@@ -159,34 +162,70 @@ async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEn
159
162
  if (!model || !model.enableAuditTracking) {
160
163
  return;
161
164
  }
165
+
166
+ const modelFields = await this.modelMetadataHelperService.loadFieldHierarchy(model.singularName)
162
167
 
163
- const auditFields = model.fields.filter(field =>
168
+ const auditFields = modelFields.filter(field =>
164
169
  field.enableAuditTracking &&
165
170
  !['mediaSingle', 'mediaMultiple', 'computed', 'richText', 'json'].includes(field.type) &&
166
171
  !(field.type === 'relation' && field.relationType === 'one-to-many')
167
172
  );
168
173
 
169
- const relationFields = auditFields.filter(field =>
170
- field.type === 'relation'
171
- );
172
- if (relationFields.length > 0) {
173
- const populatedEntity = await this.entityManager.findOne(metadata.target, {
174
- where: { id: databaseEntity.id },
175
- relations: relationFields.map(field => field.name)
176
- });
177
- if (populatedEntity) {
178
- databaseEntity = populatedEntity;
179
- }
174
+ const updatedFieldNames = new Set(updatedColumns.map(col => col.propertyName));
175
+
176
+ const allNonRelationFields = auditFields.filter(field => field.type !== 'relation');
177
+ const allRelationFields = auditFields.filter(field => field.type === 'relation');
178
+
179
+ let potentialNonRelationFields = [];
180
+
181
+ if (updatedColumns.length > 0) {
182
+ potentialNonRelationFields = allNonRelationFields.filter(field =>
183
+ updatedFieldNames.has(field.name)
184
+ );
185
+ } else {
186
+ potentialNonRelationFields = allNonRelationFields;
180
187
  }
181
- const changedFields = auditFields.filter(field => {
188
+
189
+ const potentialRelationFields = allRelationFields;
190
+
191
+ const changedNonRelationFields = potentialNonRelationFields.filter(field => {
182
192
  const newValue = entity[field.name];
183
193
  const oldValue = databaseEntity[field.name];
184
194
  return this.hasValueChanged(newValue, oldValue);
185
195
  });
196
+
197
+ const changedRelationFields = [];
198
+ if (potentialRelationFields.length > 0) {
199
+ const populatedOldEntity = await this.populateRelationFields(databaseEntity, potentialRelationFields, metadata);
200
+
201
+ for (const field of potentialRelationFields) {
202
+ const newValue = entity[field.name];
203
+ const oldValue = populatedOldEntity[field.name];
204
+
205
+ if (this.hasRelationValueChanged(field, newValue, oldValue)) {
206
+ changedRelationFields.push({
207
+ field,
208
+ newValue,
209
+ oldValue
210
+ });
211
+ }
212
+ }
213
+ }
214
+
215
+
216
+ const allChangedFields = [
217
+ ...changedNonRelationFields.map(field => ({
218
+ field,
219
+ newValue: entity[field.name],
220
+ oldValue: databaseEntity[field.name]
221
+ })),
222
+ ...changedRelationFields
223
+ ];
186
224
 
187
- if (changedFields.length === 0) {
225
+ if (allChangedFields.length === 0) {
188
226
  return;
189
227
  }
228
+
190
229
  const activeUser = this.requestContextService.getActiveUser();
191
230
 
192
231
  const chatterMessage = new ChatterMessage();
@@ -205,14 +244,14 @@ async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEn
205
244
 
206
245
  const savedMessage = await this.repo.save(chatterMessage);
207
246
 
208
- for (const field of changedFields) {
247
+ for (const { field, newValue, oldValue } of allChangedFields) {
209
248
  const messageDetail = new ChatterMessageDetails();
210
249
  messageDetail.chatterMessage = savedMessage;
211
250
  messageDetail.fieldName = field.name;
212
- messageDetail.oldValue = this.formatFieldValue(field, databaseEntity[field.name]);
213
- messageDetail.newValue = this.formatFieldValue(field, entity[field.name]);
214
- messageDetail.oldValueDisplay = this.formatFieldValueDisplay(field, databaseEntity[field.name]);
215
- messageDetail.newValueDisplay = this.formatFieldValueDisplay(field, entity[field.name]);
251
+ messageDetail.oldValue = this.formatFieldValue(field, oldValue);
252
+ messageDetail.newValue = this.formatFieldValue(field, newValue);
253
+ messageDetail.oldValueDisplay = this.formatFieldValueDisplay(field, oldValue);
254
+ messageDetail.newValueDisplay = this.formatFieldValueDisplay(field, newValue);
216
255
  await this.chatterMessageDetailsRepo.save(messageDetail);
217
256
  }
218
257
  }
@@ -220,7 +259,7 @@ async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEn
220
259
  async postAuditMessageOnDelete(entity: any, metadata: EntityMetadata, databaseEntity: any, messageQueue: boolean = false) {
221
260
  const model = await this.modelMetadataRepo.findOne({
222
261
  where: {
223
- displayName: metadata.name
262
+ singularName: lowerFirst(metadata.name)
224
263
  },
225
264
  relations: {
226
265
  fields: true,
@@ -296,28 +335,139 @@ private formatFieldValueDisplay(field: any, value: any): string {
296
335
  }
297
336
 
298
337
  private hasValueChanged(newValue: any, oldValue: any): boolean {
299
- if (
300
- (newValue === null || newValue === undefined) &&
301
- (oldValue === null || oldValue === undefined)
302
- ) {
338
+ if (newValue === oldValue) {
303
339
  return false;
304
340
  }
305
341
 
306
- if (newValue === oldValue) {
342
+ if (newValue === null && oldValue === null) {
307
343
  return false;
308
344
  }
309
345
 
310
- if (Array.isArray(newValue) && Array.isArray(oldValue)) {
311
- return JSON.stringify(newValue) !== JSON.stringify(oldValue);
346
+ if (newValue === undefined && oldValue === undefined) {
347
+ return false;
348
+ }
349
+
350
+ if (newValue && oldValue && typeof newValue === 'object' && typeof oldValue === 'object') {
351
+ if (newValue.id !== undefined && oldValue.id !== undefined) {
352
+ return newValue.id !== oldValue.id;
353
+ }
354
+
355
+ if (Array.isArray(newValue) && Array.isArray(oldValue)) {
356
+ if (newValue.length !== oldValue.length) {
357
+ return true;
358
+ }
359
+ const newIds = newValue.map(item => item.id || item).sort();
360
+ const oldIds = oldValue.map(item => item.id || item).sort();
361
+ return JSON.stringify(newIds) !== JSON.stringify(oldIds);
362
+ }
312
363
  }
313
364
 
314
- if (
315
- typeof newValue === 'object' && newValue !== null &&
316
- typeof oldValue === 'object' && oldValue !== null
317
- ) {
365
+ if (Array.isArray(newValue) && Array.isArray(oldValue)) {
318
366
  return JSON.stringify(newValue) !== JSON.stringify(oldValue);
319
367
  }
320
368
 
321
369
  return true;
322
370
  }
371
+
372
+ private hasRelationValueChanged(field: any, newValue: any, oldValue: any): boolean {
373
+ if (newValue === oldValue) {
374
+ return false;
375
+ }
376
+
377
+ if ((newValue === null || newValue === undefined) && (oldValue === null || oldValue === undefined)) {
378
+ return false;
379
+ }
380
+
381
+ if (field.relationType === 'many-to-one') {
382
+ const newId = this.extractRelationId(newValue);
383
+ const oldId = this.extractRelationId(oldValue);
384
+ return newId !== oldId;
385
+ }
386
+
387
+ if (field.relationType === 'many-to-many' || field.relationType === 'manyToMany') {
388
+ const newIds = this.extractRelationIds(newValue);
389
+ const oldIds = this.extractRelationIds(oldValue);
390
+
391
+ if (newIds.length !== oldIds.length) {
392
+ return true;
393
+ }
394
+
395
+ newIds.sort();
396
+ oldIds.sort();
397
+
398
+ return JSON.stringify(newIds) !== JSON.stringify(oldIds);
399
+ }
400
+
401
+ return this.hasValueChanged(newValue, oldValue);
402
+ }
403
+
404
+ private extractRelationId(value: any): any {
405
+ if (value === null || value === undefined) {
406
+ return null;
407
+ }
408
+
409
+ if (typeof value === 'string' || typeof value === 'number') {
410
+ return value;
411
+ }
412
+
413
+ if (typeof value === 'object' && value.id !== undefined) {
414
+ return value.id;
415
+ }
416
+
417
+ return null;
418
+ }
419
+
420
+ private extractRelationIds(value: any): any[] {
421
+ if (!Array.isArray(value)) {
422
+ const id = this.extractRelationId(value);
423
+ return id !== null ? [id] : [];
424
+ }
425
+
426
+ return value.map(item => this.extractRelationId(item)).filter(id => id !== null);
427
+ }
428
+
429
+ private async populateRelationFields(databaseEntity: any, relationFields: any[], metadata: EntityMetadata): Promise<any> {
430
+ const populatedEntity = { ...databaseEntity };
431
+
432
+ for (const field of relationFields) {
433
+ const relationValue = databaseEntity[field.name];
434
+
435
+ if (relationValue === null || relationValue === undefined) {
436
+ populatedEntity[field.name] = relationValue;
437
+ continue;
438
+ }
439
+
440
+ const relationMetadata = metadata.relations.find(rel => rel.propertyName === field.name);
441
+ if (!relationMetadata) {
442
+ populatedEntity[field.name] = relationValue;
443
+ continue;
444
+ }
445
+
446
+ const targetEntity = relationMetadata.inverseEntityMetadata || relationMetadata.type;
447
+
448
+ if (field.relationType === 'many-to-one') {
449
+ const relationId = this.extractRelationId(relationValue);
450
+ if (relationId) {
451
+ const relatedEntity = await this.entityManager.findOne(targetEntity as any, {
452
+ where: { id: relationId }
453
+ });
454
+ populatedEntity[field.name] = relatedEntity;
455
+ } else {
456
+ populatedEntity[field.name] = relationValue;
457
+ }
458
+ } else if (field.relationType === 'many-to-many' || field.relationType === 'manyToMany') {
459
+ const relationIds = this.extractRelationIds(relationValue);
460
+ if (relationIds.length > 0) {
461
+ const relatedEntities = await this.entityManager.findByIds(targetEntity as any, relationIds);
462
+ populatedEntity[field.name] = relatedEntities;
463
+ } else {
464
+ populatedEntity[field.name] = relationValue;
465
+ }
466
+ } else {
467
+ populatedEntity[field.name] = relationValue;
468
+ }
469
+ }
470
+
471
+ return populatedEntity;
472
+ }
323
473
  }
@@ -6,6 +6,10 @@ import { SolidRegistry } from "src/helpers/solid-registry";
6
6
  import { Logger } from "@nestjs/common";
7
7
  import { ERROR_MESSAGES } from "src/constants/error-messages";
8
8
 
9
+ export enum FilterCombinator {
10
+ AND = '$and',
11
+ OR = '$or'
12
+ }
9
13
 
10
14
  export class CrudHelperService {
11
15
  constructor(
@@ -24,7 +28,7 @@ export class CrudHelperService {
24
28
  return orderOptions;
25
29
  }
26
30
 
27
- private applyFilters(qb: WhereExpressionBuilder, filters: any, alias: string = 'entity', selectQb: SelectQueryBuilder<any>) {
31
+ applyFilters(qb: WhereExpressionBuilder, filters: any, alias: string = 'entity', selectQb: SelectQueryBuilder<any>) {
28
32
  const normalizedFilters = this.normalizeObjectKeys(filters);
29
33
  if (normalizedFilters.$and) {
30
34
  normalizedFilters.$and.forEach((andFilter: any) => {
@@ -157,8 +161,16 @@ export class CrudHelperService {
157
161
  private hasJoins(queryBuilder: SelectQueryBuilder<any>): boolean {
158
162
  return queryBuilder.expressionMap.joinAttributes.length > 0;
159
163
  }
160
-
161
- buildFilterQuery(qb: SelectQueryBuilder<any>, basicFilterDto: BasicFilterDto, entityAlias: string, internationalisation?: boolean, draftPublishWorkflow?: boolean,moduleRef?:any): SelectQueryBuilder<any> { // TODO : Check how to pass a type to SelectQueryBuilder instead of any
164
+
165
+ buildFilterQuery(
166
+ qb: SelectQueryBuilder<any>,
167
+ basicFilterDto: BasicFilterDto,
168
+ entityAlias: string,
169
+ internationalisation?: boolean,
170
+ draftPublishWorkflow?: boolean,
171
+ moduleRef?: any,
172
+ filterCombinator: FilterCombinator = FilterCombinator.AND
173
+ ): SelectQueryBuilder<any> { // TODO : Check how to pass a type to SelectQueryBuilder instead of any
162
174
  let { limit, offset, showSoftDeleted, filters } = basicFilterDto;
163
175
  const { fields, sort, groupBy, populate = [], populateMedia = [], locale, status } = basicFilterDto;
164
176
 
@@ -184,9 +196,15 @@ export class CrudHelperService {
184
196
  }
185
197
 
186
198
  if (filters) {
187
- qb.andWhere(new Brackets(whereQb => {
188
- this.applyFilters(whereQb, filters, entityAlias, qb);
189
- }));
199
+ if (filterCombinator === FilterCombinator.AND) {
200
+ qb.andWhere(new Brackets(whereQb => {
201
+ this.applyFilters(whereQb, filters, entityAlias, qb);
202
+ }));
203
+ } else if (filterCombinator === FilterCombinator.OR) {
204
+ qb.orWhere(new Brackets(whereQb => {
205
+ this.applyFilters(whereQb, filters, entityAlias, qb);
206
+ }));
207
+ }
190
208
  }
191
209
 
192
210
  let finalLocale = locale
@@ -16,6 +16,7 @@ import { User } from '../entities/user.entity';
16
16
  import { ActiveUserData } from '../interfaces/active-user-data.interface';
17
17
  import { iamConfig } from 'src/config/iam.config';
18
18
  import { ERROR_MESSAGES } from 'src/constants/error-messages';
19
+ import { UserRepository } from 'src/repository/user.repository';
19
20
 
20
21
  @Injectable()
21
22
  export class UserService extends CRUDService<User> {
@@ -28,8 +29,8 @@ export class UserService extends CRUDService<User> {
28
29
  readonly crudHelperService: CrudHelperService,
29
30
  @InjectEntityManager()
30
31
  readonly entityManager: EntityManager,
31
- @InjectRepository(User, 'default')
32
- readonly repo: Repository<User>,
32
+ // @InjectRepository(User, 'default')
33
+ readonly repo: UserRepository,
33
34
  @InjectRepository(RoleMetadata)
34
35
  private readonly roleRepository: Repository<RoleMetadata>,
35
36
  readonly moduleRef: ModuleRef,
@@ -21,6 +21,7 @@ import { ActionMetadataService } from './action-metadata.service';
21
21
  import { SolidIntrospectService } from './solid-introspect.service';
22
22
  import { UserViewMetadataService } from './user-view-metadata.service';
23
23
  import { ViewMetadataRepository } from 'src/repository/view-metadata.repository';
24
+ import { ModelMetadataHelperService } from 'src/helpers/model-metadata-helper.service';
24
25
 
25
26
  @Injectable()
26
27
  export class ViewMetadataService extends CRUDService<ViewMetadata> {
@@ -42,6 +43,7 @@ export class ViewMetadataService extends CRUDService<ViewMetadata> {
42
43
  private readonly fieldMetadataRepo: Repository<FieldMetadata>,
43
44
  @InjectRepository(ModelMetadata)
44
45
  private readonly modelMetadataRepo: Repository<ModelMetadata>,
46
+ private readonly modelMetadataHelperService: ModelMetadataHelperService,
45
47
  readonly moduleRef: ModuleRef
46
48
  ) {
47
49
  super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'viewMetadata', 'solid-core', moduleRef);
@@ -172,7 +174,7 @@ export class ViewMetadataService extends CRUDService<ViewMetadata> {
172
174
  }
173
175
 
174
176
  // 5. Create an easy to use map of field metadata, rather than sending an array of fields it becomes easier to use in the frontend.
175
- const fields = await this.loadFieldHierarchy(modelName);
177
+ const fields = await this.modelMetadataHelperService.loadFieldHierarchy(modelName);
176
178
  const fieldsMap = new Map<string, FieldMetadata>();
177
179
  for (let i = 0; i < fields.length; i++) {
178
180
  const field = fields[i];