@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.
Files changed (100) hide show
  1. package/dist/controllers/chatter-message-details.controller.d.ts +41 -0
  2. package/dist/controllers/chatter-message-details.controller.d.ts.map +1 -0
  3. package/dist/controllers/chatter-message-details.controller.js +179 -0
  4. package/dist/controllers/chatter-message-details.controller.js.map +1 -0
  5. package/dist/controllers/chatter-message.controller.d.ts +44 -0
  6. package/dist/controllers/chatter-message.controller.d.ts.map +1 -0
  7. package/dist/controllers/chatter-message.controller.js +199 -0
  8. package/dist/controllers/chatter-message.controller.js.map +1 -0
  9. package/dist/dtos/create-chatter-message-details.dto.d.ts +10 -0
  10. package/dist/dtos/create-chatter-message-details.dto.d.ts.map +1 -0
  11. package/dist/dtos/create-chatter-message-details.dto.js +66 -0
  12. package/dist/dtos/create-chatter-message-details.dto.js.map +1 -0
  13. package/dist/dtos/create-chatter-message.dto.d.ts +10 -0
  14. package/dist/dtos/create-chatter-message.dto.d.ts.map +1 -0
  15. package/dist/dtos/create-chatter-message.dto.js +65 -0
  16. package/dist/dtos/create-chatter-message.dto.js.map +1 -0
  17. package/dist/dtos/create-field-metadata.dto.d.ts +1 -0
  18. package/dist/dtos/create-field-metadata.dto.d.ts.map +1 -1
  19. package/dist/dtos/create-field-metadata.dto.js +6 -1
  20. package/dist/dtos/create-field-metadata.dto.js.map +1 -1
  21. package/dist/dtos/post-chatter-message.dto.d.ts +7 -0
  22. package/dist/dtos/post-chatter-message.dto.d.ts.map +1 -0
  23. package/dist/dtos/post-chatter-message.dto.js +41 -0
  24. package/dist/dtos/post-chatter-message.dto.js.map +1 -0
  25. package/dist/dtos/update-chatter-message-details.dto.d.ts +11 -0
  26. package/dist/dtos/update-chatter-message-details.dto.d.ts.map +1 -0
  27. package/dist/dtos/update-chatter-message-details.dto.js +70 -0
  28. package/dist/dtos/update-chatter-message-details.dto.js.map +1 -0
  29. package/dist/dtos/update-chatter-message.dto.d.ts +11 -0
  30. package/dist/dtos/update-chatter-message.dto.d.ts.map +1 -0
  31. package/dist/dtos/update-chatter-message.dto.js +74 -0
  32. package/dist/dtos/update-chatter-message.dto.js.map +1 -0
  33. package/dist/entities/chatter-message-details.entity.d.ts +11 -0
  34. package/dist/entities/chatter-message-details.entity.d.ts.map +1 -0
  35. package/dist/entities/chatter-message-details.entity.js +52 -0
  36. package/dist/entities/chatter-message-details.entity.js.map +1 -0
  37. package/dist/entities/chatter-message.entity.d.ts +11 -0
  38. package/dist/entities/chatter-message.entity.d.ts.map +1 -0
  39. package/dist/entities/chatter-message.entity.js +53 -0
  40. package/dist/entities/chatter-message.entity.js.map +1 -0
  41. package/dist/entities/field-metadata.entity.d.ts +1 -0
  42. package/dist/entities/field-metadata.entity.d.ts.map +1 -1
  43. package/dist/entities/field-metadata.entity.js +5 -1
  44. package/dist/entities/field-metadata.entity.js.map +1 -1
  45. package/dist/index.d.ts +10 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +10 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/seeders/module-metadata-seeder.service.js +1 -1
  50. package/dist/seeders/seed-data/solid-core-metadata.json +517 -1
  51. package/dist/services/chatter-message-details.service.d.ts +24 -0
  52. package/dist/services/chatter-message-details.service.d.ts.map +1 -0
  53. package/dist/services/chatter-message-details.service.js +59 -0
  54. package/dist/services/chatter-message-details.service.js.map +1 -0
  55. package/dist/services/chatter-message.service.d.ts +37 -0
  56. package/dist/services/chatter-message.service.d.ts.map +1 -0
  57. package/dist/services/chatter-message.service.js +279 -0
  58. package/dist/services/chatter-message.service.js.map +1 -0
  59. package/dist/services/crud-helper.service.d.ts +3 -0
  60. package/dist/services/crud-helper.service.d.ts.map +1 -1
  61. package/dist/services/crud-helper.service.js +30 -10
  62. package/dist/services/crud-helper.service.js.map +1 -1
  63. package/dist/services/crud.service.d.ts +3 -5
  64. package/dist/services/crud.service.d.ts.map +1 -1
  65. package/dist/services/crud.service.js +49 -63
  66. package/dist/services/crud.service.js.map +1 -1
  67. package/dist/services/user-context.service.d.ts +10 -0
  68. package/dist/services/user-context.service.d.ts.map +1 -0
  69. package/dist/services/user-context.service.js +42 -0
  70. package/dist/services/user-context.service.js.map +1 -0
  71. package/dist/solid-core.module.d.ts.map +1 -1
  72. package/dist/solid-core.module.js +18 -2
  73. package/dist/solid-core.module.js.map +1 -1
  74. package/dist/subscribers/audit.subscriber.d.ts +17 -0
  75. package/dist/subscribers/audit.subscriber.d.ts.map +1 -0
  76. package/dist/subscribers/audit.subscriber.js +83 -0
  77. package/dist/subscribers/audit.subscriber.js.map +1 -0
  78. package/dist/tsconfig.tsbuildinfo +1 -1
  79. package/package.json +1 -1
  80. package/src/controllers/chatter-message-details.controller.ts +92 -0
  81. package/src/controllers/chatter-message.controller.ts +105 -0
  82. package/src/dtos/create-chatter-message-details.dto.ts +40 -0
  83. package/src/dtos/create-chatter-message.dto.ts +34 -0
  84. package/src/dtos/create-field-metadata.dto.ts +4 -0
  85. package/src/dtos/post-chatter-message.dto.ts +19 -0
  86. package/src/dtos/update-chatter-message-details.dto.ts +43 -0
  87. package/src/dtos/update-chatter-message.dto.ts +41 -0
  88. package/src/entities/chatter-message-details.entity.ts +25 -0
  89. package/src/entities/chatter-message.entity.ts +22 -0
  90. package/src/entities/field-metadata.entity.ts +3 -0
  91. package/src/index.ts +10 -2
  92. package/src/seeders/module-metadata-seeder.service.ts +1 -1
  93. package/src/seeders/seed-data/solid-core-metadata.json +518 -2
  94. package/src/services/chatter-message-details.service.ts +34 -0
  95. package/src/services/chatter-message.service.ts +301 -0
  96. package/src/services/crud-helper.service.ts +39 -13
  97. package/src/services/crud.service.ts +16 -81
  98. package/src/services/user-context.service.ts +31 -0
  99. package/src/solid-core.module.ts +18 -3
  100. 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
- selectQb.leftJoin(`${alias}.${key}`, key);
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
- // Apply the pagination options
218
- if (limit) qb.limit(limit);
219
- if (offset) qb.offset(offset);
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 model = await this.loadModel();
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, model.singularName);
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, relationsExcludedFromInitialQuery);
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, relationsExcludedFromInitialQuery);
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 getRevisedFilterDto(basicFilterDto: BasicFilterDto, relationsExcludedFromInitialQuery: string[]): BasicFilterDto {
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 populateExcludedRelations(entities: T[], relationsExcludedFromInitialQuery: string[], alias: string) {
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
+ }
@@ -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,