@solidstarters/solid-core 1.2.67 → 1.2.69
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/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 +7 -1
- package/dist/dtos/create-field-metadata.dto.js.map +1 -1
- 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/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.d.ts +1 -0
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.js +15 -1
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.js.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionStaticFieldCrudManager.d.ts +2 -1
- package/dist/helpers/field-crud-managers/SelectionStaticFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionStaticFieldCrudManager.js +16 -2
- package/dist/helpers/field-crud-managers/SelectionStaticFieldCrudManager.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +14 -0
- package/dist/services/crud.service.d.ts +4 -3
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +25 -18
- 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 +4 -2
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/dtos/create-field-metadata.dto.ts +5 -0
- package/src/entities/field-metadata.entity.ts +3 -0
- package/src/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.ts +24 -0
- package/src/helpers/field-crud-managers/SelectionStaticFieldCrudManager.ts +25 -3
- package/src/seeders/seed-data/solid-core-metadata.json +14 -0
- package/src/services/crud.service.ts +32 -20
- package/src/services/field-metadata.service.ts +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.69",
|
|
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",
|
|
@@ -10,6 +10,7 @@ export interface SelectionDynamicFieldOptions {
|
|
|
10
10
|
selectionDynamicProviderCtxt: any;
|
|
11
11
|
fieldName: string;
|
|
12
12
|
discoveryService: DiscoveryService;
|
|
13
|
+
isMultiSelect: boolean | undefined | null;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export class SelectionDynamicFieldCrudManager implements FieldCrudManager {
|
|
@@ -19,8 +20,31 @@ export class SelectionDynamicFieldCrudManager implements FieldCrudManager {
|
|
|
19
20
|
|
|
20
21
|
async validate(dto: any): Promise<ValidationError[]> {
|
|
21
22
|
const fieldValue: any = dto[this.options.fieldName];
|
|
23
|
+
const isMultiSelect = this.options?.isMultiSelect;
|
|
24
|
+
|
|
25
|
+
// return this.applyValidations(fieldValue);
|
|
26
|
+
// Handle multi-select scenario
|
|
27
|
+
if (isMultiSelect) {
|
|
28
|
+
let values: any[];
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Try to parse the field value, which should be a JSON stringified array
|
|
32
|
+
values = JSON.parse(fieldValue);
|
|
33
|
+
} catch {
|
|
34
|
+
// If parsing fails, fallback to a single value
|
|
35
|
+
values = [fieldValue];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Apply validations to each value asynchronously
|
|
39
|
+
const allErrors = await Promise.all(values.map(value => this.applyValidations(value)));
|
|
40
|
+
|
|
41
|
+
// Flatten the array of errors and return
|
|
42
|
+
return allErrors.flat();
|
|
43
|
+
} else {
|
|
44
|
+
// For non-multi-select, apply validations to the single field value
|
|
22
45
|
return this.applyValidations(fieldValue);
|
|
23
46
|
}
|
|
47
|
+
}
|
|
24
48
|
|
|
25
49
|
private async applyValidations(fieldValue: any): Promise<ValidationError[]> {
|
|
26
50
|
const errors: ValidationError[] = [];
|
|
@@ -7,6 +7,7 @@ export interface SelectionStaticFieldOptions {
|
|
|
7
7
|
selectionValueType: SelectionValueType;
|
|
8
8
|
required: boolean | undefined | null;
|
|
9
9
|
fieldName: string;
|
|
10
|
+
isMultiSelect: boolean | undefined | null;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export class SelectionStaticFieldCrudManager implements FieldCrudManager {
|
|
@@ -14,9 +15,30 @@ export class SelectionStaticFieldCrudManager implements FieldCrudManager {
|
|
|
14
15
|
constructor(private readonly options: SelectionStaticFieldOptions) {
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
validate(dto: any): ValidationError[] {
|
|
18
|
-
|
|
19
|
-
return this.applyValidations(fieldValue);
|
|
18
|
+
async validate(dto: any): Promise<ValidationError[]> {
|
|
19
|
+
const fieldValue: any = dto[this.options.fieldName];
|
|
20
|
+
// return this.applyValidations(fieldValue);
|
|
21
|
+
const isMultiSelect = this.options?.isMultiSelect;
|
|
22
|
+
if (isMultiSelect) {
|
|
23
|
+
let values: any[];
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Try to parse the field value, which should be a JSON stringified array
|
|
27
|
+
values = JSON.parse(fieldValue);
|
|
28
|
+
} catch {
|
|
29
|
+
// If parsing fails, fallback to a single value
|
|
30
|
+
values = [fieldValue];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Apply validations to each value asynchronously
|
|
34
|
+
const allErrors = await Promise.all(values.map(value => this.applyValidations(value)));
|
|
35
|
+
|
|
36
|
+
// Flatten the array of errors and return
|
|
37
|
+
return allErrors.flat();
|
|
38
|
+
} else {
|
|
39
|
+
// For non-multi-select, apply validations to the single field value
|
|
40
|
+
return this.applyValidations(fieldValue);
|
|
41
|
+
}
|
|
20
42
|
}
|
|
21
43
|
|
|
22
44
|
private applyValidations(fieldValue: any): ValidationError[] {
|
|
@@ -910,6 +910,20 @@
|
|
|
910
910
|
"encrypt": false,
|
|
911
911
|
"columnName": "relation_join_table_name",
|
|
912
912
|
"isSystem": true
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
"name": "isMultiSelect",
|
|
916
|
+
"displayName": "Is MultiSelect",
|
|
917
|
+
"type": "boolean",
|
|
918
|
+
"ormType": "boolean",
|
|
919
|
+
"defaultValue": "false",
|
|
920
|
+
"required": false,
|
|
921
|
+
"unique": false,
|
|
922
|
+
"index": false,
|
|
923
|
+
"private": false,
|
|
924
|
+
"encrypt": false,
|
|
925
|
+
"columnName": "is_multiSelect",
|
|
926
|
+
"isSystem": true
|
|
913
927
|
}
|
|
914
928
|
]
|
|
915
929
|
},
|
|
@@ -34,8 +34,7 @@ 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
|
-
|
|
38
|
-
const DEFAULT_OFFSET = 0;
|
|
37
|
+
import { isArray } from "class-validator";
|
|
39
38
|
export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDto, so we get the proper types in our service
|
|
40
39
|
|
|
41
40
|
constructor(
|
|
@@ -366,7 +365,7 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
366
365
|
// Validation against the selectionStatic values. No transformation is required
|
|
367
366
|
// If the value is not in the selectionStatic values, then throw
|
|
368
367
|
// Also validate against the selectionType
|
|
369
|
-
const options = { ...commonOptions, selectionStaticValues: fieldMetadata.selectionStaticValues, selectionValueType: fieldMetadata.selectionValueType as SelectionValueType };
|
|
368
|
+
const options = { ...commonOptions, selectionStaticValues: fieldMetadata.selectionStaticValues, selectionValueType: fieldMetadata.selectionValueType as SelectionValueType, isMultiSelect: fieldMetadata.isMultiSelect};
|
|
370
369
|
return new SelectionStaticFieldCrudManager(options);
|
|
371
370
|
}
|
|
372
371
|
case SolidFieldType.selectionDynamic: {// [HOLD]
|
|
@@ -375,7 +374,7 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
375
374
|
// dataSource: string; // The name of the selection provider
|
|
376
375
|
// filterSchema : json // This is a custom json object that every data source will handle accordingly. We could validate the query against the selection provider
|
|
377
376
|
// values : string[]; // The values returned by the selection provider
|
|
378
|
-
const options = { ...commonOptions, selectionDynamicProvider: fieldMetadata.selectionDynamicProvider, selectionDynamicProviderCtxt: fieldMetadata.selectionDynamicProviderCtxt, selectionValueType: fieldMetadata.selectionValueType as SelectionValueType, discoveryService: this.discoveryService };
|
|
377
|
+
const options = { ...commonOptions, selectionDynamicProvider: fieldMetadata.selectionDynamicProvider, selectionDynamicProviderCtxt: fieldMetadata.selectionDynamicProviderCtxt, selectionValueType: fieldMetadata.selectionValueType as SelectionValueType, discoveryService: this.discoveryService, isMultiSelect: fieldMetadata.isMultiSelect};
|
|
379
378
|
return new SelectionDynamicFieldCrudManager(options);
|
|
380
379
|
}
|
|
381
380
|
case SolidFieldType.uuid: {
|
|
@@ -497,16 +496,15 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
497
496
|
|
|
498
497
|
// Will iterate through every entity & all populateMedia & call getMediaDetails for each field
|
|
499
498
|
for (const entity of entities) {
|
|
500
|
-
const mediaObj: Record<string, any> = {};
|
|
501
499
|
for (const mediaFieldPath of populateMedia) {
|
|
502
|
-
|
|
500
|
+
await this.populateMediaObject(mediaFieldPath, model, entity);
|
|
503
501
|
}
|
|
504
|
-
entity['_media'] = mediaObj;
|
|
505
502
|
}
|
|
506
503
|
return entities;
|
|
507
504
|
}
|
|
508
505
|
|
|
509
|
-
|
|
506
|
+
// Adds the media with full URL to the entity / nested entity
|
|
507
|
+
private async populateMediaObject(mediaFieldPath: string, model: ModelMetadata, entity: T) {
|
|
510
508
|
if (mediaFieldPath.includes('.')) { // mediaFieldPath is a nested field
|
|
511
509
|
const pathParts = mediaFieldPath.split('.');
|
|
512
510
|
const mediaFieldMetadata = await this.getFieldMetadataRecursively(pathParts, model.fields);
|
|
@@ -515,12 +513,15 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
515
513
|
}
|
|
516
514
|
|
|
517
515
|
// We can assume that the media field entity model is already populated as part of the entity data
|
|
518
|
-
const
|
|
519
|
-
if (!
|
|
516
|
+
const mediaFieldEntities = this.getMediaFieldEntities(entity, pathParts);
|
|
517
|
+
if (!mediaFieldEntities || mediaFieldEntities.length === 0) {
|
|
520
518
|
throw new BadRequestException(`Media field path ${mediaFieldPath} is not populated in model ${this.modelName}`);
|
|
521
519
|
}
|
|
522
|
-
|
|
523
|
-
|
|
520
|
+
// Populate the media field entities with the full URL
|
|
521
|
+
for (const mediaFieldEntity of mediaFieldEntities) {
|
|
522
|
+
const mediaWithFullUrl = await this.getMediaWithFullUrl(mediaFieldEntity, mediaFieldMetadata);
|
|
523
|
+
this.appendMediaKey(mediaFieldEntity, mediaWithFullUrl);
|
|
524
|
+
}
|
|
524
525
|
}
|
|
525
526
|
else {
|
|
526
527
|
// mediaFieldPath is a single field
|
|
@@ -529,29 +530,40 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
529
530
|
throw new BadRequestException(`Media field ${mediaFieldPath} not found in model ${this.modelName}`);
|
|
530
531
|
}
|
|
531
532
|
const mediaWithFullUrl = await this.getMediaWithFullUrl(entity, mediaFieldMetadata);
|
|
532
|
-
|
|
533
|
+
this.appendMediaKey(entity, mediaWithFullUrl);
|
|
533
534
|
}
|
|
534
535
|
}
|
|
535
536
|
|
|
536
|
-
|
|
537
|
-
|
|
537
|
+
// Add the media with full URL to the entity
|
|
538
|
+
private appendMediaKey(entity: T, mediaWithFullUrl: MediaWithFullUrl[]) {
|
|
539
|
+
// if _media key already exists, append the new media to the existing array
|
|
540
|
+
if (entity['_media']) {
|
|
541
|
+
entity['_media'] = [...entity['_media'], ...mediaWithFullUrl];
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
entity['_media'] = mediaWithFullUrl;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
private getMediaFieldEntities(entity: T, mediaPathParts: string[]): T[] {
|
|
549
|
+
let entityPart = entity;
|
|
538
550
|
for (let i = 0; i < mediaPathParts.length - 1; i++) {
|
|
539
551
|
const pathPart = mediaPathParts[i];
|
|
540
|
-
if (
|
|
541
|
-
|
|
552
|
+
if (entity[pathPart]) {
|
|
553
|
+
entityPart = entity[pathPart];
|
|
542
554
|
} else {
|
|
543
555
|
throw new BadRequestException(`Media field ${pathPart} not found in entity ${JSON.stringify(entity)}`);
|
|
544
556
|
}
|
|
545
557
|
}
|
|
546
|
-
return
|
|
558
|
+
return isArray(entityPart) ? entityPart : [entityPart];
|
|
547
559
|
}
|
|
548
560
|
|
|
549
|
-
async getMediaWithFullUrl(mediaEntity: any, mediaFieldMetadata: FieldMetadata): Promise<MediaWithFullUrl>{
|
|
561
|
+
async getMediaWithFullUrl(mediaEntity: any, mediaFieldMetadata: FieldMetadata): Promise<MediaWithFullUrl[]>{
|
|
550
562
|
const storageProviderMetadata = mediaFieldMetadata.mediaStorageProvider;
|
|
551
563
|
const storageProviderType = storageProviderMetadata.type as MediaStorageProviderType;
|
|
552
564
|
const storageProvider = await getMediaStorageProvider(this.moduleRef, storageProviderType);
|
|
553
565
|
const mediaDetails = await storageProvider.retrieve(mediaEntity, mediaFieldMetadata);
|
|
554
|
-
return mediaDetails as MediaWithFullUrl;
|
|
566
|
+
return mediaDetails as MediaWithFullUrl[];
|
|
555
567
|
}
|
|
556
568
|
|
|
557
569
|
async findOne(id: number, query: any, solidRequestContext: any = {}) {
|
|
@@ -866,7 +866,8 @@ export class FieldMetadataService {
|
|
|
866
866
|
"encryptionType",
|
|
867
867
|
"decryptWhen",
|
|
868
868
|
"columnName",
|
|
869
|
-
"enableAuditTracking"
|
|
869
|
+
"enableAuditTracking",
|
|
870
|
+
"isMultiSelect"
|
|
870
871
|
];
|
|
871
872
|
|
|
872
873
|
case SolidFieldType.selectionDynamic:
|
|
@@ -889,7 +890,8 @@ export class FieldMetadataService {
|
|
|
889
890
|
"decryptWhen",
|
|
890
891
|
"columnName",
|
|
891
892
|
"isUserKey",
|
|
892
|
-
"enableAuditTracking"
|
|
893
|
+
"enableAuditTracking",
|
|
894
|
+
"isMultiSelect"
|
|
893
895
|
];
|
|
894
896
|
case SolidFieldType.computed:
|
|
895
897
|
return [
|