@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.
- package/dist/factories/whatsapp.factory.d.ts.map +1 -1
- package/dist/factories/whatsapp.factory.js +1 -1
- package/dist/factories/whatsapp.factory.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.d.ts +5 -1
- package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js +32 -2
- package/dist/helpers/model-metadata-helper.service.js.map +1 -1
- package/dist/helpers/string.helper.d.ts +2 -0
- package/dist/helpers/string.helper.d.ts.map +1 -0
- package/dist/helpers/string.helper.js +10 -0
- package/dist/helpers/string.helper.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/repository/security-rule.repository.d.ts.map +1 -1
- package/dist/repository/security-rule.repository.js +16 -9
- package/dist/repository/security-rule.repository.js.map +1 -1
- package/dist/repository/user.repository.d.ts +12 -0
- package/dist/repository/user.repository.d.ts.map +1 -0
- package/dist/repository/user.repository.js +34 -0
- package/dist/repository/user.repository.js.map +1 -0
- package/dist/seeders/seed-data/solid-core-metadata.json +40 -6
- package/dist/services/chatter-message.service.d.ts +8 -2
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +155 -30
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/crud-helper.service.d.ts +7 -3
- package/dist/services/crud-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +17 -5
- package/dist/services/crud-helper.service.js.map +1 -1
- package/dist/services/user.service.d.ts +3 -2
- package/dist/services/user.service.d.ts.map +1 -1
- package/dist/services/user.service.js +2 -2
- package/dist/services/user.service.js.map +1 -1
- package/dist/services/view-metadata.service.d.ts +3 -1
- package/dist/services/view-metadata.service.d.ts.map +1 -1
- package/dist/services/view-metadata.service.js +5 -2
- package/dist/services/view-metadata.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +2 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts +3 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +10 -5
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/factories/whatsapp.factory.ts +2 -1
- package/src/helpers/model-metadata-helper.service.ts +30 -2
- package/src/helpers/string.helper.ts +4 -0
- package/src/index.ts +1 -0
- package/src/repository/security-rule.repository.ts +25 -12
- package/src/repository/user.repository.ts +17 -0
- package/src/seeders/seed-data/solid-core-metadata.json +40 -6
- package/src/services/chatter-message.service.ts +185 -35
- package/src/services/crud-helper.service.ts +24 -6
- package/src/services/user.service.ts +3 -2
- package/src/services/view-metadata.service.ts +3 -1
- package/src/solid-core.module.ts +3 -0
- 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.
|
|
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
|
|
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
|
}
|
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
|
-
//
|
|
30
|
-
securityRules.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
170
|
-
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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,
|
|
213
|
-
messageDetail.newValue = this.formatFieldValue(field,
|
|
214
|
-
messageDetail.oldValueDisplay = this.formatFieldValueDisplay(field,
|
|
215
|
-
messageDetail.newValueDisplay = this.formatFieldValueDisplay(field,
|
|
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
|
-
|
|
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 (
|
|
311
|
-
return
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
188
|
-
|
|
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:
|
|
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];
|