@solidstarters/solid-core 1.2.56 → 1.2.58
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/controllers/chatter-message-details.controller.d.ts +41 -0
- package/dist/controllers/chatter-message-details.controller.d.ts.map +1 -0
- package/dist/controllers/chatter-message-details.controller.js +179 -0
- package/dist/controllers/chatter-message-details.controller.js.map +1 -0
- package/dist/controllers/chatter-message.controller.d.ts +44 -0
- package/dist/controllers/chatter-message.controller.d.ts.map +1 -0
- package/dist/controllers/chatter-message.controller.js +199 -0
- package/dist/controllers/chatter-message.controller.js.map +1 -0
- package/dist/dtos/create-chatter-message-details.dto.d.ts +10 -0
- package/dist/dtos/create-chatter-message-details.dto.d.ts.map +1 -0
- package/dist/dtos/create-chatter-message-details.dto.js +66 -0
- package/dist/dtos/create-chatter-message-details.dto.js.map +1 -0
- package/dist/dtos/create-chatter-message.dto.d.ts +10 -0
- package/dist/dtos/create-chatter-message.dto.d.ts.map +1 -0
- package/dist/dtos/create-chatter-message.dto.js +65 -0
- package/dist/dtos/create-chatter-message.dto.js.map +1 -0
- package/dist/dtos/create-field-metadata.dto.d.ts +1 -0
- package/dist/dtos/create-field-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/create-field-metadata.dto.js +6 -1
- package/dist/dtos/create-field-metadata.dto.js.map +1 -1
- package/dist/dtos/post-chatter-message.dto.d.ts +7 -0
- package/dist/dtos/post-chatter-message.dto.d.ts.map +1 -0
- package/dist/dtos/post-chatter-message.dto.js +41 -0
- package/dist/dtos/post-chatter-message.dto.js.map +1 -0
- package/dist/dtos/update-chatter-message-details.dto.d.ts +11 -0
- package/dist/dtos/update-chatter-message-details.dto.d.ts.map +1 -0
- package/dist/dtos/update-chatter-message-details.dto.js +70 -0
- package/dist/dtos/update-chatter-message-details.dto.js.map +1 -0
- package/dist/dtos/update-chatter-message.dto.d.ts +11 -0
- package/dist/dtos/update-chatter-message.dto.d.ts.map +1 -0
- package/dist/dtos/update-chatter-message.dto.js +74 -0
- package/dist/dtos/update-chatter-message.dto.js.map +1 -0
- package/dist/entities/chatter-message-details.entity.d.ts +11 -0
- package/dist/entities/chatter-message-details.entity.d.ts.map +1 -0
- package/dist/entities/chatter-message-details.entity.js +52 -0
- package/dist/entities/chatter-message-details.entity.js.map +1 -0
- package/dist/entities/chatter-message.entity.d.ts +11 -0
- package/dist/entities/chatter-message.entity.d.ts.map +1 -0
- package/dist/entities/chatter-message.entity.js +53 -0
- package/dist/entities/chatter-message.entity.js.map +1 -0
- package/dist/entities/field-metadata.entity.d.ts +1 -0
- package/dist/entities/field-metadata.entity.d.ts.map +1 -1
- package/dist/entities/field-metadata.entity.js +5 -1
- package/dist/entities/field-metadata.entity.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +517 -1
- package/dist/services/chatter-message-details.service.d.ts +24 -0
- package/dist/services/chatter-message-details.service.d.ts.map +1 -0
- package/dist/services/chatter-message-details.service.js +59 -0
- package/dist/services/chatter-message-details.service.js.map +1 -0
- package/dist/services/chatter-message.service.d.ts +37 -0
- package/dist/services/chatter-message.service.d.ts.map +1 -0
- package/dist/services/chatter-message.service.js +279 -0
- package/dist/services/chatter-message.service.js.map +1 -0
- package/dist/services/crud-helper.service.d.ts +3 -0
- package/dist/services/crud-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +30 -10
- package/dist/services/crud-helper.service.js.map +1 -1
- package/dist/services/crud.service.d.ts +3 -5
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +49 -63
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/user-context.service.d.ts +10 -0
- package/dist/services/user-context.service.d.ts.map +1 -0
- package/dist/services/user-context.service.js +42 -0
- package/dist/services/user-context.service.js.map +1 -0
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +18 -2
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts +17 -0
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -0
- package/dist/subscribers/audit.subscriber.js +83 -0
- package/dist/subscribers/audit.subscriber.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/controllers/chatter-message-details.controller.ts +92 -0
- package/src/controllers/chatter-message.controller.ts +105 -0
- package/src/dtos/create-chatter-message-details.dto.ts +40 -0
- package/src/dtos/create-chatter-message.dto.ts +34 -0
- package/src/dtos/create-field-metadata.dto.ts +4 -0
- package/src/dtos/post-chatter-message.dto.ts +19 -0
- package/src/dtos/update-chatter-message-details.dto.ts +43 -0
- package/src/dtos/update-chatter-message.dto.ts +41 -0
- package/src/entities/chatter-message-details.entity.ts +25 -0
- package/src/entities/chatter-message.entity.ts +22 -0
- package/src/entities/field-metadata.entity.ts +3 -0
- package/src/index.ts +10 -2
- package/src/seeders/module-metadata-seeder.service.ts +1 -1
- package/src/seeders/seed-data/solid-core-metadata.json +518 -2
- package/src/services/chatter-message-details.service.ts +34 -0
- package/src/services/chatter-message.service.ts +301 -0
- package/src/services/crud-helper.service.ts +39 -13
- package/src/services/crud.service.ts +16 -81
- package/src/services/user-context.service.ts +31 -0
- package/src/solid-core.module.ts +18 -3
- package/src/subscribers/audit.subscriber.ts +73 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
|
|
3
|
+
import { DiscoveryService, ModuleRef } from "@nestjs/core";
|
|
4
|
+
import { EntityManager, Repository, EntityMetadata } from 'typeorm';
|
|
5
|
+
|
|
6
|
+
import { CRUDService } from 'src/services/crud.service';
|
|
7
|
+
import { ModelMetadataService } from 'src/services/model-metadata.service';
|
|
8
|
+
import { ModuleMetadataService } from 'src/services/module-metadata.service';
|
|
9
|
+
import { ConfigService } from '@nestjs/config';
|
|
10
|
+
import { FileService } from 'src/services/file.service';
|
|
11
|
+
import { CrudHelperService } from 'src/services/crud-helper.service';
|
|
12
|
+
import { PostChatterMessageDto } from 'src/dtos/post-chatter-message.dto';
|
|
13
|
+
import { SolidRequestContextDto } from 'src/dtos/solid-request-context.dto';
|
|
14
|
+
import { ChatterMessage } from '../entities/chatter-message.entity';
|
|
15
|
+
import { getMediaStorageProvider } from './mediaStorageProviders';
|
|
16
|
+
import { MediaStorageProviderType } from '../dtos/create-media-storage-provider-metadata.dto';
|
|
17
|
+
import { ChatterMessageDetails } from '../entities/chatter-message-details.entity';
|
|
18
|
+
import { ModelMetadata } from 'src/entities/model-metadata.entity';
|
|
19
|
+
import { UserContextService } from './user-context.service';
|
|
20
|
+
@Injectable()
|
|
21
|
+
export class ChatterMessageService extends CRUDService<ChatterMessage>{
|
|
22
|
+
constructor(
|
|
23
|
+
readonly modelMetadataService: ModelMetadataService,
|
|
24
|
+
readonly moduleMetadataService: ModuleMetadataService,
|
|
25
|
+
readonly configService: ConfigService,
|
|
26
|
+
readonly fileService: FileService,
|
|
27
|
+
readonly discoveryService: DiscoveryService,
|
|
28
|
+
readonly crudHelperService: CrudHelperService,
|
|
29
|
+
@InjectEntityManager()
|
|
30
|
+
readonly entityManager: EntityManager,
|
|
31
|
+
@InjectRepository(ChatterMessage, 'default')
|
|
32
|
+
readonly repo: Repository<ChatterMessage>,
|
|
33
|
+
@InjectRepository(ChatterMessageDetails, 'default')
|
|
34
|
+
readonly chatterMessageDetailsRepo: Repository<ChatterMessageDetails>,
|
|
35
|
+
readonly moduleRef: ModuleRef,
|
|
36
|
+
@InjectRepository(ModelMetadata)
|
|
37
|
+
private readonly modelMetadataRepo: Repository<ModelMetadata>,
|
|
38
|
+
readonly userContextService: UserContextService
|
|
39
|
+
) {
|
|
40
|
+
super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService,entityManager, repo, 'chatterMessage', 'solid-core', moduleRef, userContextService);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async postMessage(postDto: PostChatterMessageDto, solidRequestContext: SolidRequestContextDto, files: Express.Multer.File[] = []) {
|
|
44
|
+
const chatterMessage = new ChatterMessage();
|
|
45
|
+
chatterMessage.messageType = 'custom';
|
|
46
|
+
chatterMessage.messageSubType = postDto.messageSubType || 'general';
|
|
47
|
+
chatterMessage.messageBody = postDto.messageBody;
|
|
48
|
+
chatterMessage.coModelEntityId = postDto.coModelEntityId;
|
|
49
|
+
chatterMessage.coModelName = postDto.coModelName;
|
|
50
|
+
|
|
51
|
+
const userId = typeof solidRequestContext.activeUser === 'object'
|
|
52
|
+
? solidRequestContext.activeUser.sub
|
|
53
|
+
: solidRequestContext.activeUser;
|
|
54
|
+
|
|
55
|
+
chatterMessage.user = { id: userId } as any;
|
|
56
|
+
|
|
57
|
+
const savedMessage = await this.repo.save(chatterMessage);
|
|
58
|
+
|
|
59
|
+
if (files && files.length > 0) {
|
|
60
|
+
const model = await this.modelMetadataService.findOneBySingularName('chatterMessage', {
|
|
61
|
+
fields: {
|
|
62
|
+
model: true,
|
|
63
|
+
mediaStorageProvider: true,
|
|
64
|
+
},
|
|
65
|
+
module: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const mediaFields = model.fields.filter(field => field.type === 'mediaSingle' || field.type === 'mediaMultiple');
|
|
69
|
+
|
|
70
|
+
for (const mediaField of mediaFields) {
|
|
71
|
+
const media = files.filter(multerFile => multerFile.fieldname === mediaField.name);
|
|
72
|
+
if (media.length > 0) {
|
|
73
|
+
const storageProviderMetadata = mediaField.mediaStorageProvider;
|
|
74
|
+
const storageProviderType = storageProviderMetadata.type as MediaStorageProviderType;
|
|
75
|
+
const storageProvider = await getMediaStorageProvider(this.moduleRef, storageProviderType);
|
|
76
|
+
await storageProvider.store(media, savedMessage, mediaField);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return savedMessage;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async postAuditMessageOnInsert(entity: any, metadata: EntityMetadata, activeUser: any, messageQueue: boolean = false) {
|
|
85
|
+
const model = await this.modelMetadataRepo.findOne({
|
|
86
|
+
where: {
|
|
87
|
+
displayName: metadata.name
|
|
88
|
+
},
|
|
89
|
+
relations: {
|
|
90
|
+
fields: true,
|
|
91
|
+
module: true
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (!model || !model.enableAuditTracking) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const auditFields = model.fields.filter(field =>
|
|
100
|
+
field.enableAuditTracking &&
|
|
101
|
+
!['oneToMany', 'mediaSingle', 'mediaMultiple', 'computed', 'richText', 'json'].includes(field.type)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const chatterMessage = new ChatterMessage();
|
|
105
|
+
chatterMessage.messageType = 'audit';
|
|
106
|
+
chatterMessage.messageSubType = 'insert';
|
|
107
|
+
chatterMessage.coModelEntityId = entity.id;
|
|
108
|
+
chatterMessage.coModelName = model.singularName;
|
|
109
|
+
chatterMessage.messageBody = `New ${model.displayName} created`;
|
|
110
|
+
|
|
111
|
+
if (activeUser) {
|
|
112
|
+
const userId = typeof activeUser === 'object' ? activeUser.sub : activeUser;
|
|
113
|
+
chatterMessage.user = { id: userId } as any;
|
|
114
|
+
} else {
|
|
115
|
+
chatterMessage.user = null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const savedMessage = await this.repo.save(chatterMessage);
|
|
119
|
+
|
|
120
|
+
for (const field of auditFields) {
|
|
121
|
+
const fieldValue = entity[field.name];
|
|
122
|
+
if (fieldValue !== undefined && fieldValue !== null) {
|
|
123
|
+
const messageDetail = new ChatterMessageDetails();
|
|
124
|
+
messageDetail.chatterMessage = savedMessage;
|
|
125
|
+
messageDetail.fieldName = field.name;
|
|
126
|
+
messageDetail.oldValue = null;
|
|
127
|
+
messageDetail.oldValueDisplay = null;
|
|
128
|
+
messageDetail.newValue = this.formatFieldValue(field, fieldValue);
|
|
129
|
+
messageDetail.newValueDisplay = this.formatFieldValueDisplay(field, fieldValue);
|
|
130
|
+
await this.chatterMessageDetailsRepo.save(messageDetail);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEntity: any, activeUser: any, messageQueue: boolean = false) {
|
|
136
|
+
const model = await this.modelMetadataRepo.findOne({
|
|
137
|
+
where: {
|
|
138
|
+
displayName: metadata.name
|
|
139
|
+
},
|
|
140
|
+
relations: {
|
|
141
|
+
fields: true,
|
|
142
|
+
module: true
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!model || !model.enableAuditTracking) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const auditFields = model.fields.filter(field =>
|
|
151
|
+
field.enableAuditTracking &&
|
|
152
|
+
!['oneToMany', 'mediaSingle', 'mediaMultiple', 'computed', 'richText', 'json'].includes(field.type)
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const relationFields = auditFields.filter(field =>
|
|
156
|
+
field.type === 'relation'
|
|
157
|
+
);
|
|
158
|
+
if (relationFields.length > 0) {
|
|
159
|
+
const populatedEntity = await this.entityManager.findOne(metadata.target, {
|
|
160
|
+
where: { id: databaseEntity.id },
|
|
161
|
+
relations: relationFields.map(field => field.name)
|
|
162
|
+
});
|
|
163
|
+
if (populatedEntity) {
|
|
164
|
+
databaseEntity = populatedEntity;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const changedFields = auditFields.filter(field => {
|
|
169
|
+
const newValue = entity[field.name];
|
|
170
|
+
const oldValue = databaseEntity[field.name];
|
|
171
|
+
return this.hasValueChanged(newValue, oldValue);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (changedFields.length === 0) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const chatterMessage = new ChatterMessage();
|
|
179
|
+
chatterMessage.messageType = 'audit';
|
|
180
|
+
chatterMessage.messageSubType = 'update';
|
|
181
|
+
chatterMessage.coModelEntityId = entity.id;
|
|
182
|
+
chatterMessage.coModelName = model.singularName;
|
|
183
|
+
chatterMessage.messageBody = `${model.displayName} updated`;
|
|
184
|
+
|
|
185
|
+
if (activeUser) {
|
|
186
|
+
const userId = typeof activeUser === 'object' ? activeUser.sub : activeUser;
|
|
187
|
+
chatterMessage.user = { id: userId } as any;
|
|
188
|
+
} else {
|
|
189
|
+
chatterMessage.user = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const savedMessage = await this.repo.save(chatterMessage);
|
|
193
|
+
|
|
194
|
+
for (const field of changedFields) {
|
|
195
|
+
const messageDetail = new ChatterMessageDetails();
|
|
196
|
+
messageDetail.chatterMessage = savedMessage;
|
|
197
|
+
messageDetail.fieldName = field.name;
|
|
198
|
+
messageDetail.oldValue = this.formatFieldValue(field, databaseEntity[field.name]);
|
|
199
|
+
messageDetail.newValue = this.formatFieldValue(field, entity[field.name]);
|
|
200
|
+
messageDetail.oldValueDisplay = this.formatFieldValueDisplay(field, databaseEntity[field.name]);
|
|
201
|
+
messageDetail.newValueDisplay = this.formatFieldValueDisplay(field, entity[field.name]);
|
|
202
|
+
await this.chatterMessageDetailsRepo.save(messageDetail);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async postAuditMessageOnDelete(entity: any, metadata: EntityMetadata, databaseEntity: any, activeUser: any, messageQueue: boolean = false) {
|
|
207
|
+
const model = await this.modelMetadataRepo.findOne({
|
|
208
|
+
where: {
|
|
209
|
+
displayName: metadata.name
|
|
210
|
+
},
|
|
211
|
+
relations: {
|
|
212
|
+
fields: true,
|
|
213
|
+
module: true
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (!model || !model.enableAuditTracking) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const chatterMessage = new ChatterMessage();
|
|
222
|
+
chatterMessage.messageType = 'audit';
|
|
223
|
+
chatterMessage.messageSubType = 'delete';
|
|
224
|
+
chatterMessage.coModelEntityId = databaseEntity.id;
|
|
225
|
+
chatterMessage.coModelName = model.singularName;
|
|
226
|
+
chatterMessage.messageBody = `${model.displayName} deleted`;
|
|
227
|
+
|
|
228
|
+
if (activeUser) {
|
|
229
|
+
const userId = typeof activeUser === 'object' ? activeUser.sub : activeUser;
|
|
230
|
+
chatterMessage.user = { id: userId } as any;
|
|
231
|
+
} else {
|
|
232
|
+
chatterMessage.user = null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
await this.repo.save(chatterMessage);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private formatFieldValue(field: any, value: any): string {
|
|
239
|
+
if (value === null || value === undefined) {
|
|
240
|
+
return '';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (field.type === 'selectionStatic' || field.type === 'selectionDynamic') {
|
|
244
|
+
return `${value}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (field.type === 'relation') {
|
|
248
|
+
if (field.relationType === "many-to-one") {
|
|
249
|
+
return value.id;
|
|
250
|
+
}
|
|
251
|
+
if (field.relationType === 'manyToMany') {
|
|
252
|
+
return value.map(item => item.id).join(', ');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
return value.toString();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private formatFieldValueDisplay(field: any, value: any): string {
|
|
261
|
+
if (value === null || value === undefined) {
|
|
262
|
+
return '';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (field.type === 'selectionStatic' || field.type === 'selectionDynamic') {
|
|
266
|
+
return `${value}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (field.type === 'relation') {
|
|
270
|
+
if (field.relationType === "many-to-one") {
|
|
271
|
+
return value.name;
|
|
272
|
+
}
|
|
273
|
+
if (field.relationType === 'many-toMany') {
|
|
274
|
+
return value.map(item => item.name).join(', ');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
return value.toString();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private hasValueChanged(newValue: any, oldValue: any): boolean {
|
|
283
|
+
if (newValue === oldValue) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (newValue === null && oldValue === null) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (newValue === undefined && oldValue === undefined) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (Array.isArray(newValue) && Array.isArray(oldValue)) {
|
|
296
|
+
return JSON.stringify(newValue) !== JSON.stringify(oldValue);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -52,7 +52,8 @@ export class CrudHelperService {
|
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
else { // Recursively call the applyFilters method to handle nested conditions
|
|
55
|
-
|
|
55
|
+
const joinField = `${alias}.${key}`;
|
|
56
|
+
if (!this.isRelationJoined(selectQb, joinField)) selectQb.leftJoin(joinField, key);
|
|
56
57
|
this.applyFilters(qb, primaryFilterObj, key, selectQb);
|
|
57
58
|
}
|
|
58
59
|
});
|
|
@@ -150,6 +151,11 @@ export class CrudHelperService {
|
|
|
150
151
|
return queryBuilder.expressionMap.joinAttributes.some(join => join.entityOrProperty === joinProperty);
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
private hasJoins(queryBuilder: SelectQueryBuilder<any>): boolean {
|
|
155
|
+
return queryBuilder.expressionMap.joinAttributes.length > 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
153
159
|
buildFilterQuery(qb: SelectQueryBuilder<any>, basicFilterDto: BasicFilterDto, entityAlias: string): SelectQueryBuilder<any> { //TODO : Check how to pass a type to SelectQueryBuilder instead of any
|
|
154
160
|
let { limit, offset, showSoftDeleted, filters } = basicFilterDto;
|
|
155
161
|
const { fields, sort, groupBy, populate = [] } = basicFilterDto;
|
|
@@ -163,6 +169,11 @@ export class CrudHelperService {
|
|
|
163
169
|
throw new Error('buildFilterQuery: Only 1 Group by field is supported currently');
|
|
164
170
|
}
|
|
165
171
|
|
|
172
|
+
// Depending upon the populate option, apply the join clause
|
|
173
|
+
if (normalizedPopulate && normalizedPopulate.length) {
|
|
174
|
+
this.buildPopulateQuery(normalizedPopulate, entityAlias, qb);
|
|
175
|
+
}
|
|
176
|
+
|
|
166
177
|
if (filters) {
|
|
167
178
|
qb.where(new Brackets(whereQb => {
|
|
168
179
|
this.applyFilters(whereQb, filters, entityAlias, qb);
|
|
@@ -177,15 +188,6 @@ export class CrudHelperService {
|
|
|
177
188
|
}));
|
|
178
189
|
}
|
|
179
190
|
|
|
180
|
-
// Depending upon the populate option, apply the join clause
|
|
181
|
-
if (normalizedPopulate && normalizedPopulate.length) {
|
|
182
|
-
normalizedPopulate.forEach((relation) => {
|
|
183
|
-
// Check if the relation is already joined, if not then join it
|
|
184
|
-
const joinProperty = `${entityAlias}.${relation}`;
|
|
185
|
-
if (!this.isRelationJoined(qb, joinProperty)) qb.leftJoinAndSelect(joinProperty, relation);
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
191
|
// Depending upon the order option, apply the order by clause
|
|
190
192
|
if (normalizedSort && normalizedSort.length) {
|
|
191
193
|
const orderOptions = this.orderOptions(normalizedSort);
|
|
@@ -214,9 +216,33 @@ export class CrudHelperService {
|
|
|
214
216
|
qb.addGroupBy(`${entityAlias}.${field}`);
|
|
215
217
|
});
|
|
216
218
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (
|
|
219
|
+
|
|
220
|
+
// Apply the pagination options & handle the case when the query has joins
|
|
221
|
+
if (limit) this.hasJoins(qb) ? qb.take(limit) : qb.limit(limit);
|
|
222
|
+
if (offset) this.hasJoins(qb) ? qb.skip(offset): qb.offset(offset);
|
|
223
|
+
return qb;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private buildPopulateQuery(normalizedPopulate: string[], entityAlias: string, qb: SelectQueryBuilder<any>) {
|
|
227
|
+
normalizedPopulate.forEach((relation) => {
|
|
228
|
+
this.buildJoinQueryForRelation(qb, entityAlias, relation);
|
|
229
|
+
});
|
|
230
|
+
return qb;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private buildJoinQueryForRelation(qb: SelectQueryBuilder<any>, entityAlias: string, relation: string) {
|
|
234
|
+
// We split the joinProperty to get the alias of the entity we are joining
|
|
235
|
+
const relationParts = relation.split('.');
|
|
236
|
+
let parentAlias = entityAlias;
|
|
237
|
+
relationParts.forEach((part, i) => {
|
|
238
|
+
const joinProperty = `${parentAlias}.${part}`;
|
|
239
|
+
// Check if the relation is already joined, if not then join it
|
|
240
|
+
if (!this.isRelationJoined(qb, joinProperty)) {
|
|
241
|
+
const joinAlias = relationParts.slice(0, i + 1).join('_');
|
|
242
|
+
qb.leftJoinAndSelect(joinProperty, joinAlias);
|
|
243
|
+
}
|
|
244
|
+
parentAlias = part; // Update the parent alias for the next iteration
|
|
245
|
+
});
|
|
220
246
|
return qb;
|
|
221
247
|
}
|
|
222
248
|
|
|
@@ -34,7 +34,8 @@ import { FileService } from "./file.service";
|
|
|
34
34
|
import { getMediaStorageProvider } from "./mediaStorageProviders";
|
|
35
35
|
import { ModelMetadataService } from "./model-metadata.service";
|
|
36
36
|
import { ModuleMetadataService } from "./module-metadata.service";
|
|
37
|
-
|
|
37
|
+
import { UserContextService } from "./user-context.service";
|
|
38
|
+
import { Optional } from "@nestjs/common";
|
|
38
39
|
const DEFAULT_LIMIT = 10;
|
|
39
40
|
const DEFAULT_OFFSET = 0;
|
|
40
41
|
export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDto, so we get the proper types in our service
|
|
@@ -50,7 +51,8 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
50
51
|
readonly repo: Repository<T>,
|
|
51
52
|
readonly modelName: string,
|
|
52
53
|
readonly moduleName: string,
|
|
53
|
-
readonly moduleRef: ModuleRef
|
|
54
|
+
readonly moduleRef: ModuleRef,
|
|
55
|
+
@Optional() readonly userContextService?: UserContextService
|
|
54
56
|
//We can just have the Model Entity here
|
|
55
57
|
) { }
|
|
56
58
|
|
|
@@ -66,6 +68,9 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
66
68
|
// Check wheather user has create permission for model
|
|
67
69
|
if (solidRequestContext.activeUser) {
|
|
68
70
|
const hasPermission = this.crudHelperService.hasCreatePermissionOnModel(solidRequestContext.activeUser, model.singularName);
|
|
71
|
+
if (this.userContextService) {
|
|
72
|
+
this.userContextService.setUser(solidRequestContext.activeUser);
|
|
73
|
+
}
|
|
69
74
|
if (!hasPermission) {
|
|
70
75
|
throw new BadRequestException('Forbidden');
|
|
71
76
|
}
|
|
@@ -99,20 +104,6 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
99
104
|
}
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
private async loadInverseRelationFields() {
|
|
103
|
-
const fieldMetadataRepo = this.entityManager.getRepository(FieldMetadata);
|
|
104
|
-
// Since the fields in the dto could be a result of being on a inverse side of a relation, we need to get the field configuration from the inverse side to process it
|
|
105
|
-
const inverseRelationFields = await fieldMetadataRepo.find({
|
|
106
|
-
where: {
|
|
107
|
-
type: 'relation',
|
|
108
|
-
relationCoModelSingularName: this.modelName,
|
|
109
|
-
relationCreateInverse: true,
|
|
110
|
-
},
|
|
111
|
-
relations: ['model'],
|
|
112
|
-
});
|
|
113
|
-
return inverseRelationFields;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
107
|
private async loadModel() {
|
|
117
108
|
return await this.modelMetadataService.findOneBySingularName(this.modelName, {
|
|
118
109
|
fields: {
|
|
@@ -174,6 +165,9 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
174
165
|
// Check wheather user has update permission for model
|
|
175
166
|
if (solidRequestContext.activeUser) {
|
|
176
167
|
const hasPermission = this.crudHelperService.hasUpdatePermissionOnModel(solidRequestContext.activeUser, model.singularName);
|
|
168
|
+
if (this.userContextService) {
|
|
169
|
+
this.userContextService.setUser(solidRequestContext.activeUser);
|
|
170
|
+
}
|
|
177
171
|
if (!hasPermission) {
|
|
178
172
|
throw new BadRequestException('Forbidden');
|
|
179
173
|
}
|
|
@@ -414,28 +408,22 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
414
408
|
const alias = 'entity';
|
|
415
409
|
// Extract the required keys from the input query
|
|
416
410
|
let { limit, offset, populateMedia, populateGroup, groupFilter } = basicFilterDto;
|
|
417
|
-
const
|
|
411
|
+
const {singularName} = await this.loadModel();
|
|
418
412
|
// Check wheather user has update permission for model
|
|
419
413
|
if (solidRequestContext.activeUser) {
|
|
420
|
-
const hasPermission = this.crudHelperService.hasReadPermissionOnModel(solidRequestContext.activeUser,
|
|
414
|
+
const hasPermission = this.crudHelperService.hasReadPermissionOnModel(solidRequestContext.activeUser, singularName);
|
|
421
415
|
if (!hasPermission) {
|
|
422
416
|
throw new BadRequestException('Forbidden');
|
|
423
417
|
}
|
|
424
418
|
}
|
|
425
419
|
|
|
426
|
-
// Exclude one-to-many and many-to-one relations from the initial filter query, since they will be queried separately
|
|
427
|
-
const relationsExcludedFromInitialQuery = this.relationsExcludedFromInitialQuery(model, basicFilterDto.populate);
|
|
428
|
-
basicFilterDto = this.getRevisedFilterDto(basicFilterDto, relationsExcludedFromInitialQuery);
|
|
429
|
-
|
|
430
420
|
// Create above query on pincode table using query builder
|
|
431
421
|
var qb: SelectQueryBuilder<T> = this.repo.createQueryBuilder(alias)
|
|
432
422
|
qb = this.crudHelperService.buildFilterQuery(qb, basicFilterDto, alias);
|
|
433
423
|
|
|
434
424
|
if (basicFilterDto.groupBy) {
|
|
435
|
-
const relationsExcludedFromInitialQuery = this.relationsExcludedFromInitialQuery(model, groupFilter.populate);
|
|
436
|
-
groupFilter = this.getRevisedFilterDto(groupFilter, relationsExcludedFromInitialQuery);
|
|
437
425
|
// Get the records and the count
|
|
438
|
-
const { groupMeta, groupRecords } = await this.handleGroupFind(qb, groupFilter, populateGroup, alias, populateMedia
|
|
426
|
+
const { groupMeta, groupRecords } = await this.handleGroupFind(qb, groupFilter, populateGroup, alias, populateMedia);
|
|
439
427
|
return {
|
|
440
428
|
groupMeta,
|
|
441
429
|
groupRecords,
|
|
@@ -443,7 +431,7 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
443
431
|
}
|
|
444
432
|
else {
|
|
445
433
|
// Get the records and the count
|
|
446
|
-
const { meta, records } = await this.handleNonGroupFind(qb, populateMedia, offset, limit, alias
|
|
434
|
+
const { meta, records } = await this.handleNonGroupFind(qb, populateMedia, offset, limit, alias);
|
|
447
435
|
return {
|
|
448
436
|
meta,
|
|
449
437
|
records,
|
|
@@ -451,28 +439,9 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
451
439
|
}
|
|
452
440
|
}
|
|
453
441
|
|
|
454
|
-
private
|
|
455
|
-
const normalizedPopulate = this.crudHelperService.normalize(basicFilterDto.populate);
|
|
456
|
-
if (normalizedPopulate.length === 0 || relationsExcludedFromInitialQuery.length === 0) return basicFilterDto;
|
|
457
|
-
return { ...basicFilterDto, populate: normalizedPopulate.filter(populate => !relationsExcludedFromInitialQuery.includes(populate)) };
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
private relationsExcludedFromInitialQuery(model: ModelMetadata, relationsToBePopulated: string[] = []): string[] {
|
|
461
|
-
const relationToBeExcluded =
|
|
462
|
-
model.fields
|
|
463
|
-
.filter(field => field.type === 'relation' && [RelationType.manyTomany, RelationType.oneToMany].includes(field.relationType as RelationType))
|
|
464
|
-
.map(field => field.name);
|
|
465
|
-
return relationsToBePopulated.filter(relation => relationToBeExcluded.includes(relation));
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
private async handleNonGroupFind(qb: SelectQueryBuilder<T>, populateMedia: string[], offset: number, limit: number, alias: string, relationsExcludedFromInitialQuery: string[]) {
|
|
442
|
+
private async handleNonGroupFind(qb: SelectQueryBuilder<T>, populateMedia: string[], offset: number, limit: number, alias: string) {
|
|
469
443
|
const [entities, count] = await qb.getManyAndCount();
|
|
470
444
|
|
|
471
|
-
// Populate the excluded relations for the entities
|
|
472
|
-
if (relationsExcludedFromInitialQuery.length > 0) {
|
|
473
|
-
await this.populateExcludedRelations(entities, relationsExcludedFromInitialQuery, alias);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
445
|
// Populate the entity with the media
|
|
477
446
|
if (populateMedia && populateMedia.length > 0) {
|
|
478
447
|
await this.handlePopulateMedia(populateMedia, entities);
|
|
@@ -481,36 +450,7 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
481
450
|
return this.wrapFindResponse(offset, limit, count, entities);
|
|
482
451
|
}
|
|
483
452
|
|
|
484
|
-
private async
|
|
485
|
-
//@ts-ignore
|
|
486
|
-
const ids = entities.map(entity => entity.id);
|
|
487
|
-
|
|
488
|
-
// Fire a query to get the records from the relation entity which match the ids
|
|
489
|
-
// Create a map with key as the entity id and value as the qb records
|
|
490
|
-
const relationEntitiesMap = {};
|
|
491
|
-
for (const relation of relationsExcludedFromInitialQuery) {
|
|
492
|
-
const qb = this.repo.createQueryBuilder(`${alias}`)
|
|
493
|
-
.leftJoinAndSelect(`${alias}.${relation}`, relation)
|
|
494
|
-
.where(`${alias}.id IN (:...ids)`, { ids })
|
|
495
|
-
// .limit(DEFAULT_LIMIT)
|
|
496
|
-
// .offset(DEFAULT_OFFSET);
|
|
497
|
-
const relationEntities = await qb.getMany();
|
|
498
|
-
relationEntitiesMap[relation] = relationEntities;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Iterate over the map and assign the relation entities to the entity
|
|
502
|
-
for (const relation of relationsExcludedFromInitialQuery) {
|
|
503
|
-
for (const entity of entities) {
|
|
504
|
-
const entityRelations = relationEntitiesMap[relation]
|
|
505
|
-
//@ts-ignore
|
|
506
|
-
.filter((joinedEntity: T) => joinedEntity.id === entity.id)
|
|
507
|
-
.flatMap((joinedEntity: T) => joinedEntity[relation]);
|
|
508
|
-
entity[relation] = entityRelations;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
private async handleGroupFind(qb: SelectQueryBuilder<T>, groupFilter: BasicFilterDto, populateGroup: boolean, alias: string, populateMedia: string[], relationsExcludedFromInitialQuery: string[]) {
|
|
453
|
+
private async handleGroupFind(qb: SelectQueryBuilder<T>, groupFilter: BasicFilterDto, populateGroup: boolean, alias: string, populateMedia: string[]) {
|
|
514
454
|
const groupByResult = await qb.getRawMany();
|
|
515
455
|
|
|
516
456
|
const groupMeta = [];
|
|
@@ -523,11 +463,6 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
523
463
|
groupByQb = this.crudHelperService.buildGroupByRecordsQuery(groupByQb, group, alias);
|
|
524
464
|
const [entities, count] = await groupByQb.getManyAndCount();
|
|
525
465
|
|
|
526
|
-
// Populate the excluded relations for the entities
|
|
527
|
-
if (relationsExcludedFromInitialQuery.length > 0) {
|
|
528
|
-
await this.populateExcludedRelations(entities, relationsExcludedFromInitialQuery, alias);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
466
|
// Populate the entity with the media
|
|
532
467
|
if (populateMedia && populateMedia.length > 0) {
|
|
533
468
|
await this.handlePopulateMedia(populateMedia, entities);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
+
import { ActiveUserData } from '../interfaces/active-user-data.interface';
|
|
3
|
+
|
|
4
|
+
@Injectable()
|
|
5
|
+
export class UserContextService {
|
|
6
|
+
private readonly logger = new Logger(UserContextService.name);
|
|
7
|
+
private static currentUser: any = null;
|
|
8
|
+
|
|
9
|
+
setUser(user: any) {
|
|
10
|
+
this.logger.debug(`Setting user: ${JSON.stringify(user)}`);
|
|
11
|
+
UserContextService.currentUser = user;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getUser() {
|
|
15
|
+
return UserContextService.currentUser;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
runWithUser<T>(user: any, callback: () => T): T {
|
|
19
|
+
const previousUser = this.getUser();
|
|
20
|
+
try {
|
|
21
|
+
this.setUser(user);
|
|
22
|
+
return callback();
|
|
23
|
+
} finally {
|
|
24
|
+
this.setUser(previousUser);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getCurrentUser(): ActiveUserData | null {
|
|
29
|
+
return this.getUser();
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/solid-core.module.ts
CHANGED
|
@@ -154,7 +154,14 @@ import { RequestContextService } from './services/request-context.service';
|
|
|
154
154
|
import { SecurityRuleRepository } from './repository/security-rule.repository';
|
|
155
155
|
import { SecurityRuleSubscriber } from './subscribers/security-rule.subscriber';
|
|
156
156
|
import { ListOfValuesController } from './controllers/list-of-values.controller';
|
|
157
|
-
|
|
157
|
+
import { ChatterMessage } from './entities/chatter-message.entity';
|
|
158
|
+
import { ChatterMessageService } from './services/chatter-message.service';
|
|
159
|
+
import { ChatterMessageController } from './controllers/chatter-message.controller';
|
|
160
|
+
import { ChatterMessageDetails } from './entities/chatter-message-details.entity';
|
|
161
|
+
import { ChatterMessageDetailsService } from './services/chatter-message-details.service';
|
|
162
|
+
import { ChatterMessageDetailsController } from './controllers/chatter-message-details.controller';
|
|
163
|
+
import { AuditSubscriber } from './subscribers/audit.subscriber';
|
|
164
|
+
import { UserContextService } from './services/user-context.service';
|
|
158
165
|
|
|
159
166
|
@Global()
|
|
160
167
|
@Module({
|
|
@@ -203,6 +210,8 @@ import { ListOfValuesController } from './controllers/list-of-values.controller'
|
|
|
203
210
|
TypeOrmModule.forFeature([SecurityRule]),
|
|
204
211
|
TypeOrmModule.forFeature([SavedFilters]),
|
|
205
212
|
TypeOrmModule.forFeature([ListOfValues]),
|
|
213
|
+
TypeOrmModule.forFeature([ChatterMessage]),
|
|
214
|
+
TypeOrmModule.forFeature([ChatterMessageDetails]),
|
|
206
215
|
// TypeOrmModule.forFeature([User]),
|
|
207
216
|
],
|
|
208
217
|
controllers: [
|
|
@@ -232,7 +241,9 @@ import { ListOfValuesController } from './controllers/list-of-values.controller'
|
|
|
232
241
|
UserViewMetadataController,
|
|
233
242
|
SecurityRuleController,
|
|
234
243
|
SavedFiltersController,
|
|
235
|
-
ListOfValuesController
|
|
244
|
+
ListOfValuesController,
|
|
245
|
+
ChatterMessageController,
|
|
246
|
+
ChatterMessageDetailsController
|
|
236
247
|
],
|
|
237
248
|
providers: [
|
|
238
249
|
{
|
|
@@ -333,7 +344,11 @@ import { ListOfValuesController } from './controllers/list-of-values.controller'
|
|
|
333
344
|
SecurityRuleRepository,
|
|
334
345
|
SecurityRuleSubscriber,
|
|
335
346
|
RequestContextService,
|
|
336
|
-
SavedFiltersService
|
|
347
|
+
SavedFiltersService,
|
|
348
|
+
ChatterMessageService,
|
|
349
|
+
ChatterMessageDetailsService,
|
|
350
|
+
AuditSubscriber,
|
|
351
|
+
UserContextService,
|
|
337
352
|
],
|
|
338
353
|
exports: [
|
|
339
354
|
ModuleMetadataService,
|