@solidstarters/solid-core 1.2.63 → 1.2.66
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.controller.d.ts +1 -2
- package/dist/controllers/chatter-message.controller.d.ts.map +1 -1
- package/dist/controllers/chatter-message.controller.js +3 -7
- package/dist/controllers/chatter-message.controller.js.map +1 -1
- package/dist/decorators/active-user.decorator.d.ts.map +1 -1
- package/dist/decorators/active-user.decorator.js.map +1 -1
- package/dist/decorators/solid-request-context.decorator.d.ts.map +1 -1
- package/dist/decorators/solid-request-context.decorator.js.map +1 -1
- package/dist/guards/access-token.guard.d.ts +3 -1
- package/dist/guards/access-token.guard.d.ts.map +1 -1
- package/dist/guards/access-token.guard.js +6 -2
- package/dist/guards/access-token.guard.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +3 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/services/chatter-message-details.service.d.ts +1 -3
- package/dist/services/chatter-message-details.service.d.ts.map +1 -1
- package/dist/services/chatter-message-details.service.js +3 -6
- package/dist/services/chatter-message-details.service.js.map +1 -1
- package/dist/services/chatter-message.service.d.ts +7 -8
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +28 -19
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/crud-helper.service.d.ts +1 -0
- package/dist/services/crud-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +14 -1
- package/dist/services/crud-helper.service.js.map +1 -1
- package/dist/services/crud.service.d.ts +7 -3
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +91 -88
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/field-metadata.service.d.ts.map +1 -1
- package/dist/services/field-metadata.service.js +23 -11
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/services/request-context.service.d.ts +4 -4
- package/dist/services/request-context.service.d.ts.map +1 -1
- package/dist/services/request-context.service.js +6 -10
- package/dist/services/request-context.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +6 -2
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts +1 -3
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +7 -12
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/src/controllers/chatter-message.controller.ts +1 -2
- package/src/decorators/active-user.decorator.ts +1 -0
- package/src/decorators/solid-request-context.decorator.ts +2 -1
- package/src/guards/access-token.guard.ts +3 -0
- package/src/index.ts +0 -1
- package/src/interfaces.ts +5 -1
- package/src/services/chatter-message-details.service.ts +1 -3
- package/src/services/chatter-message.service.ts +29 -20
- package/src/services/crud-helper.service.ts +19 -1
- package/src/services/crud.service.ts +110 -64
- package/src/services/field-metadata.service.ts +23 -12
- package/src/services/request-context.service.ts +5 -6
- package/src/solid-core.module.ts +5 -2
- package/src/subscribers/audit.subscriber.ts +5 -9
- package/dist/services/user-context.service.d.ts +0 -10
- package/dist/services/user-context.service.d.ts.map +0 -1
- package/dist/services/user-context.service.js +0 -42
- package/dist/services/user-context.service.js.map +0 -1
- package/src/services/1.js +0 -6
- package/src/services/user-context.service.ts +0 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.66",
|
|
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",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"mailgen": "^2.0.28",
|
|
52
52
|
"mongoose": "^8.7.0",
|
|
53
53
|
"mysql2": "^3.13.0",
|
|
54
|
+
"nestjs-cls": "^5.4.3",
|
|
54
55
|
"nodemailer": "^6.9.13",
|
|
55
56
|
"passport": "^0.7.0",
|
|
56
57
|
"passport-google-oauth2": "^0.2.0",
|
|
@@ -98,8 +98,7 @@ export class ChatterMessageController {
|
|
|
98
98
|
async postMessage(
|
|
99
99
|
@Body() postDto: PostChatterMessageDto,
|
|
100
100
|
@UploadedFiles() files: Array<Express.Multer.File>,
|
|
101
|
-
@SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto
|
|
102
101
|
) {
|
|
103
|
-
return this.service.postMessage(postDto,
|
|
102
|
+
return this.service.postMessage(postDto, files);
|
|
104
103
|
}
|
|
105
104
|
}
|
|
@@ -5,6 +5,7 @@ import { REQUEST_USER_KEY } from "../constants";
|
|
|
5
5
|
export const ActiveUser = createParamDecorator(
|
|
6
6
|
(field: keyof ActiveUserData | undefined, ctx: ExecutionContext) => {
|
|
7
7
|
const request = ctx.switchToHttp().getRequest();
|
|
8
|
+
// TODO idealiy we should use RequestContextService and getActiveUser method. (Works for async scenarios too)
|
|
8
9
|
const user: ActiveUserData | undefined = request[REQUEST_USER_KEY];
|
|
9
10
|
return field ? user?.[field] : user;
|
|
10
11
|
},
|
|
@@ -6,7 +6,8 @@ import { SolidRequestContextDto } from 'src/dtos/solid-request-context.dto';
|
|
|
6
6
|
export const SolidRequestContextDecorator = createParamDecorator(
|
|
7
7
|
(data: unknown, ctx: ExecutionContext): SolidRequestContextDto => {
|
|
8
8
|
const request = ctx.switchToHttp().getRequest();
|
|
9
|
-
|
|
9
|
+
// TODO idealiy we should use RequestContextService and getActiveUser method. (Works for async scenarios too)
|
|
10
|
+
const activeUser: ActiveUserData | undefined = request[REQUEST_USER_KEY];
|
|
10
11
|
|
|
11
12
|
// Create a new instance of SolidRequestContextDto and stamp user data
|
|
12
13
|
const solidRequestContext = new SolidRequestContextDto();
|
|
@@ -12,6 +12,7 @@ import { ActiveUserData } from '../interfaces/active-user-data.interface';
|
|
|
12
12
|
import { jwtConfig } from '../config/iam.config';
|
|
13
13
|
import { REQUEST_USER_KEY } from "../constants";
|
|
14
14
|
import { PermissionMetadataService } from '../services/permission-metadata.service';
|
|
15
|
+
import { ClsService } from 'nestjs-cls';
|
|
15
16
|
|
|
16
17
|
@Injectable()
|
|
17
18
|
export class AccessTokenGuard implements CanActivate {
|
|
@@ -20,6 +21,7 @@ export class AccessTokenGuard implements CanActivate {
|
|
|
20
21
|
@Inject(jwtConfig.KEY)
|
|
21
22
|
private readonly jwtConfiguration: ConfigType<typeof jwtConfig>,
|
|
22
23
|
private readonly permissionsService: PermissionMetadataService,
|
|
24
|
+
private readonly cls: ClsService
|
|
23
25
|
) { }
|
|
24
26
|
|
|
25
27
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
@@ -40,6 +42,7 @@ export class AccessTokenGuard implements CanActivate {
|
|
|
40
42
|
payload.permissions = permissions.map((permission) => permission.name);
|
|
41
43
|
|
|
42
44
|
request[REQUEST_USER_KEY] = payload;
|
|
45
|
+
this.cls.set(REQUEST_USER_KEY, payload);
|
|
43
46
|
// console.log(`About to set payload in the request user key:`);
|
|
44
47
|
// console.log(payload);
|
|
45
48
|
} catch {
|
package/src/index.ts
CHANGED
|
@@ -228,7 +228,6 @@ export * from './services/security-rule.service'
|
|
|
228
228
|
export * from './services/request-context.service'
|
|
229
229
|
export * from './services/chatter-message.service'
|
|
230
230
|
export * from './services/chatter-message-details.service'
|
|
231
|
-
export * from './services/user-context.service'
|
|
232
231
|
// Repositories
|
|
233
232
|
export * from './repository/solid-base.repository'
|
|
234
233
|
export * from './repository/security-rule.repository'
|
package/src/interfaces.ts
CHANGED
|
@@ -11,7 +11,6 @@ import { FileService } from 'src/services/file.service';
|
|
|
11
11
|
import { CrudHelperService } from 'src/services/crud-helper.service';
|
|
12
12
|
|
|
13
13
|
import { ChatterMessageDetails } from '../entities/chatter-message-details.entity';
|
|
14
|
-
import { UserContextService } from './user-context.service';
|
|
15
14
|
|
|
16
15
|
@Injectable()
|
|
17
16
|
export class ChatterMessageDetailsService extends CRUDService<ChatterMessageDetails>{
|
|
@@ -27,8 +26,7 @@ export class ChatterMessageDetailsService extends CRUDService<ChatterMessageDeta
|
|
|
27
26
|
@InjectRepository(ChatterMessageDetails, 'default')
|
|
28
27
|
readonly repo: Repository<ChatterMessageDetails>,
|
|
29
28
|
readonly moduleRef: ModuleRef,
|
|
30
|
-
readonly userContextService: UserContextService
|
|
31
29
|
) {
|
|
32
|
-
super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'chatterMessageDetails', 'solid-core', moduleRef
|
|
30
|
+
super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'chatterMessageDetails', 'solid-core', moduleRef);
|
|
33
31
|
}
|
|
34
32
|
}
|
|
@@ -16,7 +16,7 @@ import { getMediaStorageProvider } from './mediaStorageProviders';
|
|
|
16
16
|
import { MediaStorageProviderType } from '../dtos/create-media-storage-provider-metadata.dto';
|
|
17
17
|
import { ChatterMessageDetails } from '../entities/chatter-message-details.entity';
|
|
18
18
|
import { ModelMetadata } from 'src/entities/model-metadata.entity';
|
|
19
|
-
import {
|
|
19
|
+
import { RequestContextService } from './request-context.service';
|
|
20
20
|
@Injectable()
|
|
21
21
|
export class ChatterMessageService extends CRUDService<ChatterMessage>{
|
|
22
22
|
constructor(
|
|
@@ -35,24 +35,27 @@ export class ChatterMessageService extends CRUDService<ChatterMessage>{
|
|
|
35
35
|
readonly moduleRef: ModuleRef,
|
|
36
36
|
@InjectRepository(ModelMetadata)
|
|
37
37
|
private readonly modelMetadataRepo: Repository<ModelMetadata>,
|
|
38
|
-
readonly
|
|
38
|
+
readonly requestContextService: RequestContextService
|
|
39
39
|
) {
|
|
40
|
-
super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService,entityManager, repo, 'chatterMessage', 'solid-core', moduleRef
|
|
40
|
+
super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService,entityManager, repo, 'chatterMessage', 'solid-core', moduleRef);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async postMessage(postDto: PostChatterMessageDto,
|
|
43
|
+
async postMessage(postDto: PostChatterMessageDto, files: Express.Multer.File[] = []) {
|
|
44
44
|
const chatterMessage = new ChatterMessage();
|
|
45
45
|
chatterMessage.messageType = 'custom';
|
|
46
|
-
chatterMessage.messageSubType = postDto.messageSubType || '
|
|
46
|
+
chatterMessage.messageSubType = postDto.messageSubType || 'post_message';
|
|
47
47
|
chatterMessage.messageBody = postDto.messageBody;
|
|
48
48
|
chatterMessage.coModelEntityId = postDto.coModelEntityId;
|
|
49
49
|
chatterMessage.coModelName = postDto.coModelName;
|
|
50
50
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
const activeUser = this.requestContextService.getActiveUser();
|
|
52
|
+
|
|
53
|
+
if (activeUser) {
|
|
54
|
+
const userId = activeUser?.sub;
|
|
55
|
+
chatterMessage.user = { id: userId } as any;
|
|
56
|
+
} else {
|
|
57
|
+
chatterMessage.user = null;
|
|
58
|
+
}
|
|
56
59
|
|
|
57
60
|
const savedMessage = await this.repo.save(chatterMessage);
|
|
58
61
|
|
|
@@ -81,7 +84,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage>{
|
|
|
81
84
|
return savedMessage;
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
async postAuditMessageOnInsert(entity: any, metadata: EntityMetadata,
|
|
87
|
+
async postAuditMessageOnInsert(entity: any, metadata: EntityMetadata, messageQueue: boolean = false) {
|
|
85
88
|
const model = await this.modelMetadataRepo.findOne({
|
|
86
89
|
where: {
|
|
87
90
|
displayName: metadata.name
|
|
@@ -98,9 +101,12 @@ export class ChatterMessageService extends CRUDService<ChatterMessage>{
|
|
|
98
101
|
|
|
99
102
|
const auditFields = model.fields.filter(field =>
|
|
100
103
|
field.enableAuditTracking &&
|
|
101
|
-
!['
|
|
104
|
+
!['mediaSingle', 'mediaMultiple', 'computed', 'richText', 'json'].includes(field.type) &&
|
|
105
|
+
!(field.type === 'relation' && field.relationType === 'one-to-many')
|
|
102
106
|
);
|
|
103
107
|
|
|
108
|
+
const activeUser = this.requestContextService.getActiveUser();
|
|
109
|
+
|
|
104
110
|
const chatterMessage = new ChatterMessage();
|
|
105
111
|
chatterMessage.messageType = 'audit';
|
|
106
112
|
chatterMessage.messageSubType = 'insert';
|
|
@@ -109,7 +115,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage>{
|
|
|
109
115
|
chatterMessage.messageBody = `New ${model.displayName} created`;
|
|
110
116
|
|
|
111
117
|
if (activeUser) {
|
|
112
|
-
const userId =
|
|
118
|
+
const userId = activeUser?.sub;
|
|
113
119
|
chatterMessage.user = { id: userId } as any;
|
|
114
120
|
} else {
|
|
115
121
|
chatterMessage.user = null;
|
|
@@ -132,7 +138,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage>{
|
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
140
|
|
|
135
|
-
async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEntity: any,
|
|
141
|
+
async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEntity: any, messageQueue: boolean = false) {
|
|
136
142
|
const model = await this.modelMetadataRepo.findOne({
|
|
137
143
|
where: {
|
|
138
144
|
displayName: metadata.name
|
|
@@ -149,7 +155,8 @@ async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEn
|
|
|
149
155
|
|
|
150
156
|
const auditFields = model.fields.filter(field =>
|
|
151
157
|
field.enableAuditTracking &&
|
|
152
|
-
!['
|
|
158
|
+
!['mediaSingle', 'mediaMultiple', 'computed', 'richText', 'json'].includes(field.type) &&
|
|
159
|
+
!(field.type === 'relation' && field.relationType === 'one-to-many')
|
|
153
160
|
);
|
|
154
161
|
|
|
155
162
|
const relationFields = auditFields.filter(field =>
|
|
@@ -164,16 +171,16 @@ async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEn
|
|
|
164
171
|
databaseEntity = populatedEntity;
|
|
165
172
|
}
|
|
166
173
|
}
|
|
167
|
-
|
|
168
174
|
const changedFields = auditFields.filter(field => {
|
|
169
175
|
const newValue = entity[field.name];
|
|
170
176
|
const oldValue = databaseEntity[field.name];
|
|
171
177
|
return this.hasValueChanged(newValue, oldValue);
|
|
172
178
|
});
|
|
173
|
-
|
|
179
|
+
|
|
174
180
|
if (changedFields.length === 0) {
|
|
175
181
|
return;
|
|
176
182
|
}
|
|
183
|
+
const activeUser = this.requestContextService.getActiveUser();
|
|
177
184
|
|
|
178
185
|
const chatterMessage = new ChatterMessage();
|
|
179
186
|
chatterMessage.messageType = 'audit';
|
|
@@ -183,7 +190,7 @@ async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEn
|
|
|
183
190
|
chatterMessage.messageBody = `${model.displayName} updated`;
|
|
184
191
|
|
|
185
192
|
if (activeUser) {
|
|
186
|
-
const userId =
|
|
193
|
+
const userId = activeUser?.sub;
|
|
187
194
|
chatterMessage.user = { id: userId } as any;
|
|
188
195
|
} else {
|
|
189
196
|
chatterMessage.user = null;
|
|
@@ -203,7 +210,7 @@ async postAuditMessageOnUpdate(entity: any, metadata: EntityMetadata, databaseEn
|
|
|
203
210
|
}
|
|
204
211
|
}
|
|
205
212
|
|
|
206
|
-
async postAuditMessageOnDelete(entity: any, metadata: EntityMetadata, databaseEntity: any,
|
|
213
|
+
async postAuditMessageOnDelete(entity: any, metadata: EntityMetadata, databaseEntity: any, messageQueue: boolean = false) {
|
|
207
214
|
const model = await this.modelMetadataRepo.findOne({
|
|
208
215
|
where: {
|
|
209
216
|
displayName: metadata.name
|
|
@@ -225,8 +232,10 @@ async postAuditMessageOnDelete(entity: any, metadata: EntityMetadata, databaseEn
|
|
|
225
232
|
chatterMessage.coModelName = model.singularName;
|
|
226
233
|
chatterMessage.messageBody = `${model.displayName} deleted`;
|
|
227
234
|
|
|
235
|
+
const activeUser = this.requestContextService.getActiveUser();
|
|
236
|
+
|
|
228
237
|
if (activeUser) {
|
|
229
|
-
const userId =
|
|
238
|
+
const userId = activeUser?.sub;
|
|
230
239
|
chatterMessage.user = { id: userId } as any;
|
|
231
240
|
} else {
|
|
232
241
|
chatterMessage.user = null;
|
|
@@ -158,11 +158,18 @@ export class CrudHelperService {
|
|
|
158
158
|
|
|
159
159
|
buildFilterQuery(qb: SelectQueryBuilder<any>, basicFilterDto: BasicFilterDto, entityAlias: string): SelectQueryBuilder<any> { //TODO : Check how to pass a type to SelectQueryBuilder instead of any
|
|
160
160
|
let { limit, offset, showSoftDeleted, filters } = basicFilterDto;
|
|
161
|
-
const { fields, sort, groupBy, populate = [] } = basicFilterDto;
|
|
161
|
+
const { fields, sort, groupBy, populate = [], populateMedia=[] } = basicFilterDto;
|
|
162
162
|
|
|
163
163
|
// Normalize the fields, sort, groupBy and populate options i.e (since they can be either a string or an array of strings, when coming from the request)
|
|
164
164
|
const normalizedFields = this.normalize(fields);
|
|
165
165
|
const normalizedPopulate = this.normalize(populate);
|
|
166
|
+
const normalizedPopulateMedia = this.normalize(populateMedia);
|
|
167
|
+
|
|
168
|
+
// if normalizedPopulateMedia, has any nested media paths, then add then to populate excluding the last part
|
|
169
|
+
const additionalPopulate = this.additionalRelationsRequiredForMediaPopulation(normalizedPopulateMedia);
|
|
170
|
+
// Add the additional populate relations to the normalizedPopulate, if they are not already present
|
|
171
|
+
normalizedPopulate.push(...additionalPopulate.filter((relation) => !normalizedPopulate.includes(relation)));
|
|
172
|
+
|
|
166
173
|
const normalizedSort = this.normalize(sort);
|
|
167
174
|
const normalizedGroupBy = this.normalize(groupBy);
|
|
168
175
|
if (normalizedGroupBy.length > 1) {
|
|
@@ -223,6 +230,17 @@ export class CrudHelperService {
|
|
|
223
230
|
return qb;
|
|
224
231
|
}
|
|
225
232
|
|
|
233
|
+
additionalRelationsRequiredForMediaPopulation(normalizedPopulateMedia: string[]) {
|
|
234
|
+
// Populate relations containing the media field
|
|
235
|
+
return normalizedPopulateMedia
|
|
236
|
+
.filter(pm => pm.includes("."))
|
|
237
|
+
.map((pm) => {
|
|
238
|
+
const mediaPathParts = pm.split('.');
|
|
239
|
+
if (mediaPathParts.length <= 1) return pm;
|
|
240
|
+
return mediaPathParts.slice(0, -1).join('.');
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
226
244
|
private buildPopulateQuery(normalizedPopulate: string[], entityAlias: string, qb: SelectQueryBuilder<any>) {
|
|
227
245
|
normalizedPopulate.forEach((relation) => {
|
|
228
246
|
this.buildJoinQueryForRelation(qb, entityAlias, relation);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BadRequestException } from "@nestjs/common";
|
|
1
|
+
import { BadRequestException, Optional } from "@nestjs/common";
|
|
2
2
|
import { ConfigService } from "@nestjs/config";
|
|
3
3
|
import { DiscoveryService, ModuleRef } from "@nestjs/core";
|
|
4
4
|
import { EntityManager, In, IsNull, Not, QueryFailedError, SelectQueryBuilder } from "typeorm";
|
|
@@ -28,14 +28,12 @@ import { SelectionDynamicFieldCrudManager } from "../helpers/field-crud-managers
|
|
|
28
28
|
import { SelectionStaticFieldCrudManager } from "../helpers/field-crud-managers/SelectionStaticFieldCrudManager";
|
|
29
29
|
import { ShortTextFieldCrudManager } from "../helpers/field-crud-managers/ShortTextFieldCrudManager";
|
|
30
30
|
import { UUIDFieldCrudManager } from "../helpers/field-crud-managers/UUIDFieldCrudManager";
|
|
31
|
-
import { FieldCrudManager } from "../interfaces";
|
|
31
|
+
import { FieldCrudManager, MediaWithFullUrl } from "../interfaces";
|
|
32
32
|
import { CrudHelperService } from "./crud-helper.service";
|
|
33
33
|
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
|
-
import { UserContextService } from "./user-context.service";
|
|
38
|
-
import { Optional } from "@nestjs/common";
|
|
39
37
|
const DEFAULT_LIMIT = 10;
|
|
40
38
|
const DEFAULT_OFFSET = 0;
|
|
41
39
|
export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDto, so we get the proper types in our service
|
|
@@ -52,7 +50,6 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
52
50
|
readonly modelName: string,
|
|
53
51
|
readonly moduleName: string,
|
|
54
52
|
readonly moduleRef: ModuleRef,
|
|
55
|
-
@Optional() readonly userContextService?: UserContextService
|
|
56
53
|
//We can just have the Model Entity here
|
|
57
54
|
) { }
|
|
58
55
|
|
|
@@ -68,9 +65,6 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
68
65
|
// Check wheather user has create permission for model
|
|
69
66
|
if (solidRequestContext.activeUser) {
|
|
70
67
|
const hasPermission = this.crudHelperService.hasCreatePermissionOnModel(solidRequestContext.activeUser, model.singularName);
|
|
71
|
-
if (this.userContextService) {
|
|
72
|
-
this.userContextService.setUser(solidRequestContext.activeUser);
|
|
73
|
-
}
|
|
74
68
|
if (!hasPermission) {
|
|
75
69
|
throw new BadRequestException('Forbidden');
|
|
76
70
|
}
|
|
@@ -165,9 +159,6 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
165
159
|
// Check wheather user has update permission for model
|
|
166
160
|
if (solidRequestContext.activeUser) {
|
|
167
161
|
const hasPermission = this.crudHelperService.hasUpdatePermissionOnModel(solidRequestContext.activeUser, model.singularName);
|
|
168
|
-
if (this.userContextService) {
|
|
169
|
-
this.userContextService.setUser(solidRequestContext.activeUser);
|
|
170
|
-
}
|
|
171
162
|
if (!hasPermission) {
|
|
172
163
|
throw new BadRequestException('Forbidden');
|
|
173
164
|
}
|
|
@@ -496,42 +487,85 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
496
487
|
return r;
|
|
497
488
|
}
|
|
498
489
|
|
|
499
|
-
|
|
500
|
-
const model = await this.
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
mediaStorageProvider: true,
|
|
490
|
+
private async handlePopulateMedia(populateMedia: string[], entities: T[]) {
|
|
491
|
+
const model = await this.entityManager.getRepository(ModelMetadata).findOne({
|
|
492
|
+
where: {
|
|
493
|
+
singularName: this.modelName,
|
|
504
494
|
},
|
|
505
|
-
|
|
495
|
+
relations: ['fields', 'fields.mediaStorageProvider', 'fields.model','module'],
|
|
506
496
|
});
|
|
507
497
|
|
|
508
|
-
|
|
509
|
-
)
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
498
|
+
// Will iterate through every entity & all populateMedia & call getMediaDetails for each field
|
|
499
|
+
for (const entity of entities) {
|
|
500
|
+
const mediaObj: Record<string, any> = {};
|
|
501
|
+
for (const mediaFieldPath of populateMedia) {
|
|
502
|
+
mediaObj[mediaFieldPath] = await this.getMediaObject(mediaFieldPath, model, entity);
|
|
503
|
+
}
|
|
504
|
+
entity['_media'] = mediaObj;
|
|
505
|
+
}
|
|
506
|
+
return entities;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private async getMediaObject(mediaFieldPath: string, model: ModelMetadata, entity: T) {
|
|
510
|
+
if (mediaFieldPath.includes('.')) { // mediaFieldPath is a nested field
|
|
511
|
+
const pathParts = mediaFieldPath.split('.');
|
|
512
|
+
const mediaFieldMetadata = await this.getFieldMetadataRecursively(pathParts, model.fields);
|
|
513
|
+
if (!mediaFieldMetadata) {
|
|
514
|
+
throw new BadRequestException(`Media field ${mediaFieldPath} not found in model ${this.modelName}`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// We can assume that the media field entity model is already populated as part of the entity data
|
|
518
|
+
const mediaFieldEntity = this.getMediaFieldEntity(entity, pathParts);
|
|
519
|
+
if (!mediaFieldEntity) {
|
|
520
|
+
throw new BadRequestException(`Media field path ${mediaFieldPath} is not populated in model ${this.modelName}`);
|
|
521
|
+
}
|
|
522
|
+
const mediaWithFullUrl = await this.getMediaWithFullUrl(mediaFieldEntity, mediaFieldMetadata);
|
|
523
|
+
return mediaWithFullUrl;
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
// mediaFieldPath is a single field
|
|
527
|
+
const mediaFieldMetadata = model.fields.find(field => field.name === mediaFieldPath);
|
|
528
|
+
if (!mediaFieldMetadata) {
|
|
529
|
+
throw new BadRequestException(`Media field ${mediaFieldPath} not found in model ${this.modelName}`);
|
|
530
|
+
}
|
|
531
|
+
const mediaWithFullUrl = await this.getMediaWithFullUrl(entity, mediaFieldMetadata);
|
|
532
|
+
return mediaWithFullUrl;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private getMediaFieldEntity(entity: T, mediaPathParts: string[]) {
|
|
537
|
+
let mediaFieldEntity = entity;
|
|
538
|
+
for (let i = 0; i < mediaPathParts.length - 1; i++) {
|
|
539
|
+
const pathPart = mediaPathParts[i];
|
|
540
|
+
if (mediaFieldEntity[pathPart]) {
|
|
541
|
+
mediaFieldEntity = mediaFieldEntity[pathPart];
|
|
542
|
+
} else {
|
|
543
|
+
throw new BadRequestException(`Media field ${pathPart} not found in entity ${JSON.stringify(entity)}`);
|
|
544
|
+
}
|
|
529
545
|
}
|
|
546
|
+
return mediaFieldEntity;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async getMediaWithFullUrl(mediaEntity: any, mediaFieldMetadata: FieldMetadata): Promise<MediaWithFullUrl>{
|
|
550
|
+
const storageProviderMetadata = mediaFieldMetadata.mediaStorageProvider;
|
|
551
|
+
const storageProviderType = storageProviderMetadata.type as MediaStorageProviderType;
|
|
552
|
+
const storageProvider = await getMediaStorageProvider(this.moduleRef, storageProviderType);
|
|
553
|
+
const mediaDetails = await storageProvider.retrieve(mediaEntity, mediaFieldMetadata);
|
|
554
|
+
return mediaDetails as MediaWithFullUrl;
|
|
530
555
|
}
|
|
531
556
|
|
|
532
557
|
async findOne(id: number, query: any, solidRequestContext: any = {}) {
|
|
533
558
|
const { populate = [], fields = [], populateMedia = [] } = query;
|
|
534
559
|
|
|
560
|
+
// const normalizedFields = this.crudHelperService.normalize(fields);
|
|
561
|
+
const normalizedPopulate = this.crudHelperService.normalize(populate);
|
|
562
|
+
const normalizedPopulateMedia = this.crudHelperService.normalize(populateMedia);
|
|
563
|
+
|
|
564
|
+
// if normalizedPopulateMedia, has any nested media paths, then add then to populate excluding the last part
|
|
565
|
+
const additionalPopulate = this.crudHelperService.additionalRelationsRequiredForMediaPopulation(normalizedPopulateMedia);
|
|
566
|
+
// Add the additional populate relations to the normalizedPopulate, if they are not already present
|
|
567
|
+
normalizedPopulate.push(...additionalPopulate.filter((relation) => !normalizedPopulate.includes(relation)));
|
|
568
|
+
|
|
535
569
|
const model = await this.loadModel();
|
|
536
570
|
// Check wheather user has update permission for model
|
|
537
571
|
if (solidRequestContext.activeUser) {
|
|
@@ -541,42 +575,21 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
541
575
|
}
|
|
542
576
|
}
|
|
543
577
|
|
|
544
|
-
|
|
578
|
+
let entity = await this.repo.findOne({
|
|
545
579
|
where: {
|
|
546
580
|
//@ts-ignore
|
|
547
581
|
id: id,
|
|
548
582
|
},
|
|
549
|
-
relations:
|
|
583
|
+
relations: normalizedPopulate,
|
|
550
584
|
select: fields,
|
|
551
585
|
});
|
|
552
586
|
if (!entity) {
|
|
553
587
|
throw new Error(`Entity [${this.moduleName}.${this.modelName}] with id ${id} not found`);
|
|
554
588
|
}
|
|
555
589
|
// Populate the entity with the media
|
|
556
|
-
if (
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
model: true,
|
|
560
|
-
mediaStorageProvider: true,
|
|
561
|
-
},
|
|
562
|
-
module: true,
|
|
563
|
-
});
|
|
564
|
-
const mediaObj: Record<string, any> = {};
|
|
565
|
-
for (const mediaField of model.fields.filter(field => field.type === 'mediaSingle' || field.type === 'mediaMultiple')) {
|
|
566
|
-
if (!populateMedia.includes(mediaField.name)) {
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
const storageProviderMetadata = mediaField.mediaStorageProvider;
|
|
570
|
-
const storageProviderType = storageProviderMetadata.type as MediaStorageProviderType;
|
|
571
|
-
const storageProvider = await getMediaStorageProvider(this.moduleRef, storageProviderType);
|
|
572
|
-
const mediaResult = await storageProvider.retrieve(entity, mediaField);
|
|
573
|
-
let obj = { [mediaField.name]: mediaResult }
|
|
574
|
-
mediaObj[mediaField.name] = mediaResult;
|
|
575
|
-
// entity['media'][mediaField.name] = await storageProvider.retrieve(entity, mediaField);
|
|
576
|
-
}
|
|
577
|
-
if (Object.keys(mediaObj).length > 0) {
|
|
578
|
-
entity['_media'] = mediaObj;
|
|
579
|
-
}
|
|
590
|
+
if (normalizedPopulateMedia.length > 0) {
|
|
591
|
+
const populatedEntities = await this.handlePopulateMedia(normalizedPopulateMedia, [entity]);
|
|
592
|
+
entity = populatedEntities[0] as Awaited<T>;
|
|
580
593
|
}
|
|
581
594
|
return entity;
|
|
582
595
|
}
|
|
@@ -775,4 +788,37 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
775
788
|
}
|
|
776
789
|
|
|
777
790
|
|
|
791
|
+
async getFieldMetadataRecursively(pathParts: string[], fields: FieldMetadata[]) {
|
|
792
|
+
if (!pathParts || pathParts.length === 0) {
|
|
793
|
+
throw new BadRequestException('Path parts cannot be empty');
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const [currentPart, ...remainingParts] = pathParts;
|
|
797
|
+
const field = fields.find(field => field.name === currentPart);
|
|
798
|
+
|
|
799
|
+
if (!field) {
|
|
800
|
+
throw new BadRequestException(`Field ${currentPart} not found in model ${this.modelName}`);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Base case: last part, return the field
|
|
804
|
+
if (remainingParts.length === 0) {
|
|
805
|
+
return field;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (!field.relationCoModelSingularName) {
|
|
809
|
+
throw new BadRequestException(`Field ${field.name} does not define a relationCoModelSingularName`);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const relationCoModel = await this.entityManager.getRepository(ModelMetadata).findOne({
|
|
813
|
+
where: { singularName: field.relationCoModelSingularName },
|
|
814
|
+
relations: ['fields', 'fields.mediaStorageProvider', 'fields.model'],
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
if (!relationCoModel) {
|
|
818
|
+
throw new BadRequestException(`Model ${field.relationCoModelSingularName} not found`);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return this.getFieldMetadataRecursively(remainingParts, relationCoModel.fields);
|
|
822
|
+
}
|
|
778
823
|
}
|
|
824
|
+
|
|
@@ -503,6 +503,7 @@ export class FieldMetadataService {
|
|
|
503
503
|
"encryptionType",
|
|
504
504
|
"decryptWhen",
|
|
505
505
|
"columnName",
|
|
506
|
+
"enableAuditTracking"
|
|
506
507
|
];
|
|
507
508
|
|
|
508
509
|
case SolidFieldType.bigint:
|
|
@@ -521,7 +522,8 @@ export class FieldMetadataService {
|
|
|
521
522
|
"encrypt",
|
|
522
523
|
"encryptionType",
|
|
523
524
|
"decryptWhen",
|
|
524
|
-
"columnName"
|
|
525
|
+
"columnName",
|
|
526
|
+
"enableAuditTracking"
|
|
525
527
|
];
|
|
526
528
|
|
|
527
529
|
// case SolidFieldType.float:
|
|
@@ -560,7 +562,8 @@ export class FieldMetadataService {
|
|
|
560
562
|
"encrypt",
|
|
561
563
|
"encryptionType",
|
|
562
564
|
"decryptWhen",
|
|
563
|
-
"columnName"
|
|
565
|
+
"columnName",
|
|
566
|
+
"enableAuditTracking"
|
|
564
567
|
];
|
|
565
568
|
|
|
566
569
|
case SolidFieldType.shortText:
|
|
@@ -582,7 +585,8 @@ export class FieldMetadataService {
|
|
|
582
585
|
"encryptionType",
|
|
583
586
|
"decryptWhen",
|
|
584
587
|
"columnName",
|
|
585
|
-
"isUserKey"
|
|
588
|
+
"isUserKey",
|
|
589
|
+
"enableAuditTracking"
|
|
586
590
|
];
|
|
587
591
|
|
|
588
592
|
case SolidFieldType.longtext:
|
|
@@ -665,7 +669,8 @@ export class FieldMetadataService {
|
|
|
665
669
|
"encrypt",
|
|
666
670
|
"encryptionType",
|
|
667
671
|
"decryptWhen",
|
|
668
|
-
"columnName"
|
|
672
|
+
"columnName",
|
|
673
|
+
"enableAuditTracking"
|
|
669
674
|
];
|
|
670
675
|
|
|
671
676
|
case SolidFieldType.date:
|
|
@@ -684,7 +689,8 @@ export class FieldMetadataService {
|
|
|
684
689
|
"encrypt",
|
|
685
690
|
"encryptionType",
|
|
686
691
|
"decryptWhen",
|
|
687
|
-
"columnName"
|
|
692
|
+
"columnName",
|
|
693
|
+
"enableAuditTracking"
|
|
688
694
|
];
|
|
689
695
|
|
|
690
696
|
case SolidFieldType.datetime:
|
|
@@ -703,7 +709,8 @@ export class FieldMetadataService {
|
|
|
703
709
|
"encrypt",
|
|
704
710
|
"encryptionType",
|
|
705
711
|
"decryptWhen",
|
|
706
|
-
"columnName"
|
|
712
|
+
"columnName",
|
|
713
|
+
"enableAuditTracking"
|
|
707
714
|
];
|
|
708
715
|
|
|
709
716
|
case SolidFieldType.time:
|
|
@@ -722,7 +729,8 @@ export class FieldMetadataService {
|
|
|
722
729
|
"encrypt",
|
|
723
730
|
"encryptionType",
|
|
724
731
|
"decryptWhen",
|
|
725
|
-
"columnName"
|
|
732
|
+
"columnName",
|
|
733
|
+
"enableAuditTracking"
|
|
726
734
|
];
|
|
727
735
|
|
|
728
736
|
case SolidFieldType.relation:
|
|
@@ -750,7 +758,8 @@ export class FieldMetadataService {
|
|
|
750
758
|
"columnName",
|
|
751
759
|
"relationJoinTableName",
|
|
752
760
|
"isRelationManyToManyOwner",
|
|
753
|
-
"relationFieldFixedFilter"
|
|
761
|
+
"relationFieldFixedFilter",
|
|
762
|
+
"enableAuditTracking"
|
|
754
763
|
];
|
|
755
764
|
|
|
756
765
|
case SolidFieldType.mediaSingle:
|
|
@@ -812,7 +821,8 @@ export class FieldMetadataService {
|
|
|
812
821
|
"encrypt",
|
|
813
822
|
"encryptionType",
|
|
814
823
|
"decryptWhen",
|
|
815
|
-
"columnName"
|
|
824
|
+
"columnName",
|
|
825
|
+
"enableAuditTracking"
|
|
816
826
|
];
|
|
817
827
|
|
|
818
828
|
case SolidFieldType.password:
|
|
@@ -855,7 +865,8 @@ export class FieldMetadataService {
|
|
|
855
865
|
"encrypt",
|
|
856
866
|
"encryptionType",
|
|
857
867
|
"decryptWhen",
|
|
858
|
-
"columnName"
|
|
868
|
+
"columnName",
|
|
869
|
+
"enableAuditTracking"
|
|
859
870
|
];
|
|
860
871
|
|
|
861
872
|
case SolidFieldType.selectionDynamic:
|
|
@@ -877,8 +888,8 @@ export class FieldMetadataService {
|
|
|
877
888
|
"encryptionType",
|
|
878
889
|
"decryptWhen",
|
|
879
890
|
"columnName",
|
|
880
|
-
"isUserKey"
|
|
881
|
-
|
|
891
|
+
"isUserKey",
|
|
892
|
+
"enableAuditTracking"
|
|
882
893
|
];
|
|
883
894
|
case SolidFieldType.computed:
|
|
884
895
|
return [
|