@solidstarters/solid-core 1.2.20 → 1.2.21
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 +3 -1
- 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/module-metadata-helper.service.d.ts +5 -0
- package/dist/helpers/module-metadata-helper.service.d.ts.map +1 -0
- package/dist/helpers/module-metadata-helper.service.js +61 -0
- package/dist/helpers/module-metadata-helper.service.js.map +1 -0
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +26 -35
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/field-metadata.service.d.ts +17 -6
- package/dist/services/field-metadata.service.d.ts.map +1 -1
- package/dist/services/field-metadata.service.js +220 -34
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +5 -1
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +46 -31
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/module-metadata.service.d.ts +3 -3
- package/dist/services/module-metadata.service.d.ts.map +1 -1
- package/dist/services/module-metadata.service.js +8 -14
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +2 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/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/module-metadata-helper.service.ts +25 -0
- package/src/services/crud.service.ts +14 -22
- package/src/services/field-metadata.service.ts +200 -10
- package/src/services/model-metadata.service.ts +57 -40
- package/src/services/module-metadata.service.ts +5 -14
- package/src/solid-core.module.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.21",
|
|
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",
|
|
@@ -142,6 +142,7 @@ export enum MediaType {
|
|
|
142
142
|
export enum RelationType {
|
|
143
143
|
manyToOne = 'many-to-one',
|
|
144
144
|
manyTomany = 'many-to-many',
|
|
145
|
+
oneToMany = 'one-to-many',
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
export enum CascadeType {
|
|
@@ -320,6 +321,10 @@ export class CreateFieldMetadataDto {
|
|
|
320
321
|
@IsOptional()
|
|
321
322
|
relationModelFieldName: string;
|
|
322
323
|
|
|
324
|
+
@ApiProperty({description: 'Only for type=relation, many-to-many. This field is used to set the owner of the many-to-many relation'})
|
|
325
|
+
@IsOptional()
|
|
326
|
+
isRelationManyToManyOwner: boolean;
|
|
327
|
+
|
|
323
328
|
@ApiProperty({
|
|
324
329
|
description:
|
|
325
330
|
'Dynamic provider for selection. Only for type=selectionDynamic',
|
|
@@ -99,6 +99,9 @@ export class FieldMetadata extends CommonEntity {
|
|
|
99
99
|
@Column({ name: 'relation_model_field_name', nullable: true })
|
|
100
100
|
relationModelFieldName: string;
|
|
101
101
|
|
|
102
|
+
@Column({ name: 'is_relation_many_to_many_owner', nullable: true })
|
|
103
|
+
isRelationManyToManyOwner: boolean;
|
|
104
|
+
|
|
102
105
|
@Column({ name: 'selection_dynamic_provider', nullable: true })
|
|
103
106
|
selectionDynamicProvider: string;
|
|
104
107
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Injectable, ModuleMetadata } from "@nestjs/common";
|
|
2
|
+
import * as fs from 'fs/promises'; // Use the Promise-based version of fs for async/await
|
|
3
|
+
import * as path from 'path'; // To handle file paths
|
|
4
|
+
import { ModuleMetadataConfiguration } from "src/interfaces";
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class ModuleMetadataHelperService {
|
|
8
|
+
// async getModuleMetadataConfig(moduleName: string): Promise<ModuleMetadata> {
|
|
9
|
+
// const filePath = this.getModuleMetadataFilePath(moduleName);
|
|
10
|
+
// const metadata = await this.getModuleMetadata(filePath);
|
|
11
|
+
// return metadata;
|
|
12
|
+
// }
|
|
13
|
+
|
|
14
|
+
async getModuleMetadataConfiguration(configFilePath: string): Promise<any> {
|
|
15
|
+
const fileContent = await fs.readFile(configFilePath, 'utf8');
|
|
16
|
+
return JSON.parse(fileContent);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getModuleMetadataFilePath(moduleName: string): string {
|
|
20
|
+
const folderPath = path.resolve(process.cwd(), 'module-metadata', moduleName);
|
|
21
|
+
const filePath = path.join(folderPath, `${moduleName}-metadata.json`);
|
|
22
|
+
return filePath;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
@@ -66,8 +66,8 @@ export class CRUDService<T> { //Add two generic value i.e Person,CreatePersonDto
|
|
|
66
66
|
let hasMediaFields = false;
|
|
67
67
|
|
|
68
68
|
const model = await this.loadModel();
|
|
69
|
-
const inverseRelationFields = await this.loadInverseRelationFields();
|
|
70
|
-
const fieldsToProcess = [...model.fields
|
|
69
|
+
// const inverseRelationFields = await this.loadInverseRelationFields();
|
|
70
|
+
const fieldsToProcess = [...model.fields];
|
|
71
71
|
|
|
72
72
|
// 2. Loop through the fields with a switch statement
|
|
73
73
|
// 3. Handle the fields based on field type
|
|
@@ -185,8 +185,7 @@ export class CRUDService<T> { //Add two generic value i.e Person,CreatePersonDto
|
|
|
185
185
|
let hasMediaFields = false;
|
|
186
186
|
|
|
187
187
|
const model = await this.loadModel();
|
|
188
|
-
const
|
|
189
|
-
const fieldsToProcess = [...model.fields, ...inverseRelationFields];
|
|
188
|
+
const fieldsToProcess = [...model.fields];
|
|
190
189
|
|
|
191
190
|
// 2. Loop through the fields with a switch statement
|
|
192
191
|
// 3. Handle the fields based on field type
|
|
@@ -305,9 +304,7 @@ export class CRUDService<T> { //Add two generic value i.e Person,CreatePersonDto
|
|
|
305
304
|
}
|
|
306
305
|
case SolidFieldType.relation: {
|
|
307
306
|
// Identify if the field is for the inverse side or not
|
|
308
|
-
const inverseSide = (fieldMetadata.model.singularName !== this.modelName) ? true : false;
|
|
309
307
|
if (fieldMetadata.relationType === RelationType.manyToOne) {
|
|
310
|
-
if (!inverseSide) {
|
|
311
308
|
const manyToOneOptions: ManyToOneRelationFieldOptions = {
|
|
312
309
|
...commonOptions,
|
|
313
310
|
relationModelSingularName: fieldMetadata.relationModelSingularName,
|
|
@@ -316,23 +313,20 @@ export class CRUDService<T> { //Add two generic value i.e Person,CreatePersonDto
|
|
|
316
313
|
entityManager,
|
|
317
314
|
}
|
|
318
315
|
return new ManyToOneRelationFieldCrudManager(manyToOneOptions);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const inverseFieldMetadata = fieldMetadata; //Setting an alias for clarity purpose
|
|
316
|
+
}
|
|
317
|
+
else if (fieldMetadata.relationType === RelationType.oneToMany) {
|
|
322
318
|
const oneToManyOptions: OneToManyRelationFieldOptions = {
|
|
323
319
|
...commonOptions,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
modelSingularName: inverseFieldMetadata.relationModelSingularName,
|
|
320
|
+
relationModelSingularName: fieldMetadata.relationModelSingularName,
|
|
321
|
+
modelSingularName: fieldMetadata.model.singularName,
|
|
327
322
|
entityManager,
|
|
328
|
-
inverseFieldName:
|
|
329
|
-
inverseRelationModelFieldName:
|
|
323
|
+
inverseFieldName: fieldMetadata.relationModelFieldName,
|
|
324
|
+
inverseRelationModelFieldName: fieldMetadata.name,
|
|
330
325
|
}
|
|
331
326
|
return new OneToManyRelationFieldCrudManager(oneToManyOptions);
|
|
332
|
-
}
|
|
333
327
|
}
|
|
334
328
|
else if (fieldMetadata.relationType === RelationType.manyTomany) {
|
|
335
|
-
if (
|
|
329
|
+
if (fieldMetadata.isRelationManyToManyOwner) {
|
|
336
330
|
const manyToManyOptions: ManyToManyRelationFieldOptions = {
|
|
337
331
|
...commonOptions,
|
|
338
332
|
relationModelSingularName: fieldMetadata.relationModelSingularName,
|
|
@@ -344,16 +338,14 @@ export class CRUDService<T> { //Add two generic value i.e Person,CreatePersonDto
|
|
|
344
338
|
return new ManyToManyRelationFieldCrudManager(manyToManyOptions);
|
|
345
339
|
}
|
|
346
340
|
else {
|
|
347
|
-
const inverseFieldMetadata = fieldMetadata; //Setting an alias for clarity purpose
|
|
348
341
|
const inverseManyToManyOptions: ManyToManyRelationFieldOptions = {
|
|
349
342
|
...commonOptions,
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
modelSingularName: inverseFieldMetadata.relationModelSingularName,
|
|
343
|
+
relationModelSingularName: fieldMetadata.relationModelSingularName,
|
|
344
|
+
modelSingularName: fieldMetadata.model.singularName,
|
|
353
345
|
isInverseSide: true,
|
|
354
346
|
entityManager,
|
|
355
|
-
fieldName:
|
|
356
|
-
relationModelFieldName:
|
|
347
|
+
fieldName: fieldMetadata.relationModelFieldName,
|
|
348
|
+
relationModelFieldName: fieldMetadata.name,
|
|
357
349
|
}
|
|
358
350
|
return new ManyToManyRelationFieldCrudManager(inverseManyToManyOptions);
|
|
359
351
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
2
|
-
import { FieldMetadata } from '../entities/field-metadata.entity';
|
|
3
|
-
import { DataSource, Repository, SelectQueryBuilder } from 'typeorm';
|
|
1
|
+
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
|
4
2
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import { SolidFieldType } from '../dtos/create-field-metadata.dto'
|
|
3
|
+
import * as fs from 'fs/promises'; // Use the Promise-based version of fs for async/await
|
|
4
|
+
import { ModuleMetadataHelperService } from 'src/helpers/module-metadata-helper.service';
|
|
8
5
|
import { SolidRegistry } from 'src/helpers/solid-registry';
|
|
6
|
+
import { DataSource, Repository, SelectQueryBuilder } from 'typeorm';
|
|
9
7
|
import { BasicFilterDto } from '../dtos/basic-filters.dto';
|
|
10
|
-
import {
|
|
11
|
-
import { UpdateFieldMetaDataDto } from '../dtos/update-field-metadata.dto';
|
|
8
|
+
import { CascadeType, ComputedFieldValueType, CreateFieldMetadataDto, DecryptWhenType, EncryptionType, MediaType, PSQLType, RelationType, SelectionValueType, SolidFieldType } from '../dtos/create-field-metadata.dto';
|
|
12
9
|
import { SelectionDynamicQueryDto } from '../dtos/selection-dynamic-query.dto';
|
|
10
|
+
import { UpdateFieldMetaDataDto } from '../dtos/update-field-metadata.dto';
|
|
11
|
+
import { FieldMetadata } from '../entities/field-metadata.entity';
|
|
12
|
+
import { ModelMetadata } from '../entities/model-metadata.entity';
|
|
13
13
|
import { ISelectionProviderValues } from '../interfaces';
|
|
14
|
+
import { CrudHelperService } from './crud-helper.service';
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
@Injectable()
|
|
16
18
|
export class FieldMetadataService {
|
|
@@ -20,9 +22,146 @@ export class FieldMetadataService {
|
|
|
20
22
|
@InjectDataSource()
|
|
21
23
|
private readonly dataSource: DataSource,
|
|
22
24
|
private readonly solidRegistry: SolidRegistry,
|
|
23
|
-
private readonly crudHelperService: CrudHelperService
|
|
25
|
+
private readonly crudHelperService: CrudHelperService,
|
|
26
|
+
private readonly moduleMetadataHelperService: ModuleMetadataHelperService,
|
|
24
27
|
) { }
|
|
25
28
|
|
|
29
|
+
private logger = new Logger(FieldMetadataService.name);
|
|
30
|
+
|
|
31
|
+
async updateInverseField(field: FieldMetadata, fieldRepository: Repository<FieldMetadata>, modelRepository: Repository<ModelMetadata>) {
|
|
32
|
+
if (!field.model || !field.model.module) {
|
|
33
|
+
throw new Error('Model and module are required to update inverse field');
|
|
34
|
+
}
|
|
35
|
+
// Update the inverse field in the db
|
|
36
|
+
const savedInverseField = await this.updateInverseFieldInDb(field, fieldRepository, modelRepository);
|
|
37
|
+
// Update the inverse field in the file
|
|
38
|
+
this.updateRelationInverseFieldInFile(savedInverseField, field.relationModelSingularName, field.model.module.name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private async updateInverseFieldInDb(field: FieldMetadata, fieldRepository: Repository<FieldMetadata>, modelRepository: Repository<ModelMetadata>): Promise<FieldMetadata> {
|
|
42
|
+
const { moduleName, modelName } = this.validateForInverseField(field);
|
|
43
|
+
|
|
44
|
+
// Get the relation model reference
|
|
45
|
+
const relationModel = await this.getRelationModel(modelRepository, field, moduleName);
|
|
46
|
+
|
|
47
|
+
// const {id, createdAt, updatedAt, deletedAt, ...fieldKeys} = field;
|
|
48
|
+
switch (field.relationType) {
|
|
49
|
+
case RelationType.manyToOne: {
|
|
50
|
+
const inverseField: FieldMetadata = {
|
|
51
|
+
...field,
|
|
52
|
+
name: field.relationModelFieldName ?? `${modelName}s`,
|
|
53
|
+
displayName: `Inverse ${field.displayName}`,
|
|
54
|
+
description: `Inverse field for ${field.displayName}`,
|
|
55
|
+
type: SolidFieldType.relation,
|
|
56
|
+
isSystem: field.isSystem,
|
|
57
|
+
relationType: RelationType.oneToMany,
|
|
58
|
+
relationModelSingularName: modelName,
|
|
59
|
+
relationCreateInverse: true,
|
|
60
|
+
relationCascade: field.relationCascade,
|
|
61
|
+
relationModelModuleName: moduleName,
|
|
62
|
+
relationModelFieldName: field.name,
|
|
63
|
+
required: false,
|
|
64
|
+
unique: false,
|
|
65
|
+
index: false,
|
|
66
|
+
private: false,
|
|
67
|
+
encrypt: false,
|
|
68
|
+
model: relationModel,
|
|
69
|
+
columnName:null,
|
|
70
|
+
relationJoinTableName: null,
|
|
71
|
+
relationJoinColumnName: null,
|
|
72
|
+
joinColumnName: null,
|
|
73
|
+
id : null,
|
|
74
|
+
// createdAt: null,
|
|
75
|
+
// updatedAt: null,
|
|
76
|
+
// deletedAt: null,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Load the inverse field,
|
|
80
|
+
const savedField = await this.saveInverseField(fieldRepository, relationModel, inverseField);
|
|
81
|
+
return savedField;
|
|
82
|
+
}
|
|
83
|
+
case RelationType.manyTomany: {
|
|
84
|
+
// Logic to create a manyToMany inverse field definition
|
|
85
|
+
const inverseFieldManyToMany: FieldMetadata = {
|
|
86
|
+
...field,
|
|
87
|
+
name: field.relationModelFieldName,
|
|
88
|
+
displayName: `Inverse ${field.displayName}`,
|
|
89
|
+
description: `Inverse field for ${field.displayName}`,
|
|
90
|
+
type: SolidFieldType.relation,
|
|
91
|
+
isSystem: field.isSystem,
|
|
92
|
+
relationType: RelationType.manyTomany,
|
|
93
|
+
relationModelSingularName: modelName,
|
|
94
|
+
relationCreateInverse: true,
|
|
95
|
+
relationCascade: field.relationCascade,
|
|
96
|
+
relationModelModuleName: moduleName,
|
|
97
|
+
relationModelFieldName: field.name,
|
|
98
|
+
required: false,
|
|
99
|
+
unique: false,
|
|
100
|
+
index: false,
|
|
101
|
+
private: false,
|
|
102
|
+
encrypt: false,
|
|
103
|
+
model: relationModel,
|
|
104
|
+
columnName:null,
|
|
105
|
+
relationJoinTableName: null,
|
|
106
|
+
relationJoinColumnName: null,
|
|
107
|
+
joinColumnName: null,
|
|
108
|
+
isRelationManyToManyOwner: false,
|
|
109
|
+
id : null,
|
|
110
|
+
// createdAt: null,
|
|
111
|
+
// updatedAt: null,
|
|
112
|
+
// deletedAt: null,
|
|
113
|
+
}
|
|
114
|
+
const savedField = await this.saveInverseField(fieldRepository, relationModel, inverseFieldManyToMany);
|
|
115
|
+
return savedField;
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
throw new Error(`Invalid relation type for field ${field.name} with relation type ${field.relationType}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private async getRelationModel(modelRepository: Repository<ModelMetadata>, field: FieldMetadata, moduleName: string) {
|
|
123
|
+
return await modelRepository.findOne({
|
|
124
|
+
where: {
|
|
125
|
+
singularName: field.relationModelSingularName,
|
|
126
|
+
module: {
|
|
127
|
+
name: field.relationModelModuleName ?? moduleName
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async saveInverseField(fieldRepository: Repository<FieldMetadata>, relationModel: ModelMetadata, inverseField: FieldMetadata): Promise<FieldMetadata> {
|
|
134
|
+
const existingInverseField = await fieldRepository.findOne({
|
|
135
|
+
where: {
|
|
136
|
+
model: relationModel,
|
|
137
|
+
name: inverseField.name
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (existingInverseField) {
|
|
142
|
+
const updatedField = fieldRepository.merge(existingInverseField, inverseField);
|
|
143
|
+
const savedField = await fieldRepository.save(updatedField);
|
|
144
|
+
return savedField;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const savedField = await fieldRepository.save(fieldRepository.create(inverseField));
|
|
148
|
+
return savedField;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private validateForInverseField(field: FieldMetadata) {
|
|
153
|
+
if (field.type !== SolidFieldType.relation) {
|
|
154
|
+
throw new Error('Only relation fields can have inverse fields');
|
|
155
|
+
}
|
|
156
|
+
const modelName = field.model.singularName;
|
|
157
|
+
const moduleName = field.model.module.name;
|
|
158
|
+
|
|
159
|
+
if (!modelName || !moduleName) {
|
|
160
|
+
throw new Error('Model name and module name are required to create inverse field');
|
|
161
|
+
}
|
|
162
|
+
return { moduleName, modelName };
|
|
163
|
+
}
|
|
164
|
+
|
|
26
165
|
async findMany(basicFilterDto: BasicFilterDto) {
|
|
27
166
|
const alias = 'fieldMetadata';
|
|
28
167
|
// Extract the required keys from the input query
|
|
@@ -586,7 +725,8 @@ export class FieldMetadataService {
|
|
|
586
725
|
"columnName",
|
|
587
726
|
"relationJoinColumnName",
|
|
588
727
|
"joinColumnName",
|
|
589
|
-
"relationJoinTableName"
|
|
728
|
+
"relationJoinTableName",
|
|
729
|
+
"isRelationManyToManyOwner",
|
|
590
730
|
];
|
|
591
731
|
|
|
592
732
|
case SolidFieldType.mediaSingle:
|
|
@@ -874,4 +1014,54 @@ export class FieldMetadataService {
|
|
|
874
1014
|
return selectionProviderInstance.value(query.optionValue, selectionDynamicProviderCtxt);
|
|
875
1015
|
}
|
|
876
1016
|
|
|
1017
|
+
private async updateRelationInverseFieldInFile(savedInverseField: FieldMetadata, inverseModelName: string, moduleName: string) {
|
|
1018
|
+
try {
|
|
1019
|
+
const filePath = this.moduleMetadataHelperService.getModuleMetadataFilePath(moduleName);
|
|
1020
|
+
const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
|
|
1021
|
+
|
|
1022
|
+
// Create the config object for the inverse field
|
|
1023
|
+
const fieldObject: Record<string, any> = await this.createFieldConfig(savedInverseField);
|
|
1024
|
+
|
|
1025
|
+
// Find the field config object in the json file
|
|
1026
|
+
const model = metaData.moduleMetadata.models.find((model: any) => model.singularName === inverseModelName);
|
|
1027
|
+
|
|
1028
|
+
// Replace the current field object with the above field object
|
|
1029
|
+
const fieldIndex = model.fields.findIndex((field: any) => field.name === savedInverseField.name);
|
|
1030
|
+
if (fieldIndex === -1) {
|
|
1031
|
+
model.fields.push(fieldObject);
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
model.fields[fieldIndex] = fieldObject;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Write the updated object back to the file
|
|
1038
|
+
const updatedContent = JSON.stringify(metaData, null, 2);
|
|
1039
|
+
await fs.writeFile(filePath, updatedContent);
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
this.logger.error('File creation failed:', error);
|
|
1042
|
+
throw new Error('File creation failed, rolling back transaction'); // Trigger rollback
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
//Moved existing reusable logic to a separate function
|
|
1047
|
+
async createFieldConfig(field: FieldMetadata) {
|
|
1048
|
+
const fieldsRequiredBasedOnType = await this.fetchCurrentFieldsBasedOnType(field.type);
|
|
1049
|
+
const fieldObject: Record<string, any> = {};
|
|
1050
|
+
|
|
1051
|
+
// Assign default or placeholder values for required fields
|
|
1052
|
+
fieldsRequiredBasedOnType.forEach((requiredField) => {
|
|
1053
|
+
fieldObject[requiredField] = field[requiredField];
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
if (field.type == "mediaSingle" || field.type == "mediaMultiple") {
|
|
1057
|
+
if (field.mediaStorageProvider) {
|
|
1058
|
+
delete fieldObject.mediaStorageProviderId
|
|
1059
|
+
fieldObject.mediaStorageProviderUserKey = field.mediaStorageProvider.name
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return fieldObject;
|
|
1064
|
+
}
|
|
877
1065
|
}
|
|
1066
|
+
|
|
1067
|
+
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
|
2
2
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
|
3
3
|
import * as fs from 'fs/promises'; // Use the Promise-based version of fs for async/await
|
|
4
|
-
import * as path from 'path'; // To handle file paths
|
|
5
4
|
import { DataSource, EntityManager, Repository, SelectQueryBuilder } from 'typeorm';
|
|
6
5
|
import { CreateModelMetadataDto } from '../dtos/create-model-metadata.dto';
|
|
7
6
|
import { ModelMetadata } from '../entities/model-metadata.entity';
|
|
8
7
|
import { ModuleMetadata } from '../entities/module-metadata.entity';
|
|
9
8
|
|
|
9
|
+
import { SolidFieldType } from 'src/dtos/create-field-metadata.dto';
|
|
10
|
+
import { ModuleMetadataHelperService } from 'src/helpers/module-metadata-helper.service';
|
|
10
11
|
import { BasicFilterDto } from '../dtos/basic-filters.dto';
|
|
11
12
|
import { UpdateModelMetaDataDto } from '../dtos/update-model-metadata.dto';
|
|
13
|
+
import { ActionMetadata } from '../entities/action-metadata.entity';
|
|
12
14
|
import { FieldMetadata } from '../entities/field-metadata.entity';
|
|
15
|
+
import { MenuItemMetadata } from '../entities/menu-item-metadata.entity';
|
|
16
|
+
import { ViewMetadata } from '../entities/view-metadata.entity';
|
|
13
17
|
import {
|
|
14
18
|
REFRESH_MODEL_COMMAND,
|
|
15
19
|
REMOVE_FIELDS_COMMAND,
|
|
@@ -19,11 +23,7 @@ import { CodeGenerationOptions } from '../interfaces';
|
|
|
19
23
|
import { CrudHelperService } from './crud-helper.service';
|
|
20
24
|
import { FieldMetadataService } from './field-metadata.service';
|
|
21
25
|
import { MediaStorageProviderMetadataService } from './media-storage-provider-metadata.service';
|
|
22
|
-
import { ViewMetadata } from '../entities/view-metadata.entity';
|
|
23
|
-
import { ActionMetadata } from '../entities/action-metadata.entity';
|
|
24
|
-
import { MenuItemMetadata } from '../entities/menu-item-metadata.entity';
|
|
25
26
|
import { RoleMetadataService } from './role-metadata.service';
|
|
26
|
-
import { PermissionMetadataSeederService } from 'src/seeders/permission-metadata-seeder.service';
|
|
27
27
|
|
|
28
28
|
@Injectable()
|
|
29
29
|
export class ModelMetadataService {
|
|
@@ -40,6 +40,7 @@ export class ModelMetadataService {
|
|
|
40
40
|
private readonly mediaStorageProviderMetadataService: MediaStorageProviderMetadataService,
|
|
41
41
|
private readonly fieldMetadataService: FieldMetadataService,
|
|
42
42
|
private readonly roleService: RoleMetadataService,
|
|
43
|
+
private readonly moduleMetadataHelperService: ModuleMetadataHelperService,
|
|
43
44
|
) { }
|
|
44
45
|
|
|
45
46
|
async findMany(basicFilterDto: BasicFilterDto) {
|
|
@@ -118,9 +119,15 @@ export class ModelMetadataService {
|
|
|
118
119
|
|
|
119
120
|
try {
|
|
120
121
|
return await this.dataSource.transaction(async (manager: EntityManager) => {
|
|
122
|
+
const modelRepository = manager.getRepository(ModelMetadata);
|
|
123
|
+
const fieldRepository = manager.getRepository(FieldMetadata);
|
|
124
|
+
|
|
121
125
|
// Step 1: Write initial data to the database
|
|
122
126
|
const model = await this.createInDB(manager, createDto);
|
|
123
|
-
await this.createInFile(model.id,
|
|
127
|
+
await this.createInFile(model.id, modelRepository);
|
|
128
|
+
|
|
129
|
+
await this.handleInverseRelationFieldsUpdates(model, fieldRepository, modelRepository);
|
|
130
|
+
|
|
124
131
|
return model
|
|
125
132
|
});
|
|
126
133
|
} catch (error) {
|
|
@@ -129,6 +136,16 @@ export class ModelMetadataService {
|
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
138
|
|
|
139
|
+
// Iterate through the fields in the createDto & get all the relation fields which have create inverse as true
|
|
140
|
+
private async handleInverseRelationFieldsUpdates(model: ModelMetadata, fieldRepository: Repository<FieldMetadata>, modelRepository: Repository<ModelMetadata>) {
|
|
141
|
+
const fields: FieldMetadata[] = await this.getRelationInverseFields(model.id, fieldRepository);
|
|
142
|
+
|
|
143
|
+
// Call a function which will iterate through each field and create an inverse field entry for the respective model i.e call updateInverseField on the related model
|
|
144
|
+
for (const field of fields) {
|
|
145
|
+
await this.fieldMetadataService.updateInverseField(field, fieldRepository, modelRepository);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
132
149
|
async update(id: number, updateModelMetaDataDto: UpdateModelMetaDataDto) {
|
|
133
150
|
//To DO start the transaction
|
|
134
151
|
// call create In db
|
|
@@ -136,9 +153,15 @@ export class ModelMetadataService {
|
|
|
136
153
|
|
|
137
154
|
try {
|
|
138
155
|
return await this.dataSource.transaction(async (manager: EntityManager) => {
|
|
156
|
+
const modelRepository = manager.getRepository(ModelMetadata);
|
|
157
|
+
const fieldRepository = manager.getRepository(FieldMetadata);
|
|
158
|
+
|
|
139
159
|
// Step 1: Write initial data to the database
|
|
140
160
|
const model = await this.updateInDb(manager, id, updateModelMetaDataDto)
|
|
141
|
-
await this.updateInFile(model.id,
|
|
161
|
+
await this.updateInFile(model.id, modelRepository);
|
|
162
|
+
|
|
163
|
+
await this.handleInverseRelationFieldsUpdates(model, fieldRepository, modelRepository);
|
|
164
|
+
|
|
142
165
|
// return model
|
|
143
166
|
});
|
|
144
167
|
} catch (error) {
|
|
@@ -304,15 +327,11 @@ export class ModelMetadataService {
|
|
|
304
327
|
where: {
|
|
305
328
|
id: modelId,
|
|
306
329
|
},
|
|
307
|
-
relations: ["fields", "module"], //FIXME: Check with jenender and change to relations to avoid confusion
|
|
330
|
+
relations: ["fields", "fields.mediaStorageProvider", "module"], //FIXME: Check with jenender and change to relations to avoid confusion
|
|
308
331
|
});
|
|
309
332
|
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
// Read the existing JSON file
|
|
314
|
-
const fileContent = await fs.readFile(filePath, 'utf8');
|
|
315
|
-
const metaData = JSON.parse(fileContent);
|
|
333
|
+
const filePath = this.moduleMetadataHelperService.getModuleMetadataFilePath(model.module.name);
|
|
334
|
+
const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
|
|
316
335
|
|
|
317
336
|
const modelMetaData = {
|
|
318
337
|
singularName: model.singularName,
|
|
@@ -331,13 +350,7 @@ export class ModelMetadataService {
|
|
|
331
350
|
const field = model.fields[i];
|
|
332
351
|
if (!field.isSystem) {
|
|
333
352
|
|
|
334
|
-
const
|
|
335
|
-
const fieldObject: Record<string, any> = {};
|
|
336
|
-
|
|
337
|
-
// Assign default or placeholder values for required fields
|
|
338
|
-
fieldsRequiredBasedOnType.forEach((requiredField) => {
|
|
339
|
-
fieldObject[requiredField] = field[requiredField];
|
|
340
|
-
});
|
|
353
|
+
const fieldObject: Record<string, any> = await this.fieldMetadataService.createFieldConfig(field);
|
|
341
354
|
modelMetaData.fields.push(fieldObject);
|
|
342
355
|
listViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}`, sortable: true, filterable: true } })
|
|
343
356
|
formViewLayoutFields.push({ type: "field", attrs: { name: `${field.name}` } })
|
|
@@ -476,7 +489,7 @@ export class ModelMetadataService {
|
|
|
476
489
|
// Existing field
|
|
477
490
|
const existingField = existingFields.find((field) => field.id === fieldMetadata.id);
|
|
478
491
|
if (existingField) {
|
|
479
|
-
if (fieldMetadata.mediaStorageProviderId) {
|
|
492
|
+
if (fieldMetadata.mediaStorageProviderId) {
|
|
480
493
|
fieldMetadata['mediaStorageProvider'] = await this.mediaStorageProviderMetadataService.findOne(fieldMetadata.mediaStorageProviderId);
|
|
481
494
|
}
|
|
482
495
|
Object.assign(existingField, fieldMetadata);
|
|
@@ -562,12 +575,8 @@ export class ModelMetadataService {
|
|
|
562
575
|
relations: ["fields", "fields.mediaStorageProvider", "module"], //FIXME: Check with jenender and change to relations to avoid confusion
|
|
563
576
|
});
|
|
564
577
|
|
|
565
|
-
const
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
// Read the existing JSON file
|
|
569
|
-
const fileContent = await fs.readFile(filePath, 'utf8');
|
|
570
|
-
const metaData = JSON.parse(fileContent);
|
|
578
|
+
const filePath = this.moduleMetadataHelperService.getModuleMetadataFilePath(model.module.name);
|
|
579
|
+
const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
|
|
571
580
|
|
|
572
581
|
const modelMetaData = {
|
|
573
582
|
singularName: model.singularName,
|
|
@@ -583,18 +592,7 @@ export class ModelMetadataService {
|
|
|
583
592
|
const field = model.fields[i];
|
|
584
593
|
if (!field.isSystem && !field.isMarkedForRemoval) {
|
|
585
594
|
|
|
586
|
-
const
|
|
587
|
-
const fieldObject: Record<string, any> = {};
|
|
588
|
-
|
|
589
|
-
// Assign default or placeholder values for required fields
|
|
590
|
-
fieldsRequiredBasedOnType.forEach((requiredField) => {
|
|
591
|
-
fieldObject[requiredField] = field[requiredField];
|
|
592
|
-
|
|
593
|
-
});
|
|
594
|
-
if (field.type == "mediaSingle" || field.type == "mediaMultiple") {
|
|
595
|
-
delete fieldObject.mediaStorageProviderId
|
|
596
|
-
fieldObject.mediaStorageProviderUserKey = field.mediaStorageProvider.name
|
|
597
|
-
}
|
|
595
|
+
const fieldObject: Record<string, any> = await this.fieldMetadataService.createFieldConfig(field);
|
|
598
596
|
modelMetaData.fields.push(fieldObject);
|
|
599
597
|
}
|
|
600
598
|
}
|
|
@@ -908,4 +906,23 @@ export class ModelMetadataService {
|
|
|
908
906
|
}
|
|
909
907
|
|
|
910
908
|
|
|
909
|
+
private async getRelationInverseFields(modelId: number, repo: Repository<FieldMetadata>): Promise<FieldMetadata[]> {
|
|
910
|
+
return await repo.find({
|
|
911
|
+
where: {
|
|
912
|
+
model : {
|
|
913
|
+
id: modelId
|
|
914
|
+
},
|
|
915
|
+
type: SolidFieldType.relation,
|
|
916
|
+
relationCreateInverse: true
|
|
917
|
+
},
|
|
918
|
+
relations: {
|
|
919
|
+
model: {
|
|
920
|
+
module: true
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
911
926
|
}
|
|
927
|
+
|
|
928
|
+
|
|
@@ -20,6 +20,7 @@ import { SolidRegistry } from '../helpers/solid-registry';
|
|
|
20
20
|
import { CodeGenerationOptions, ModuleMetadataConfiguration } from '../interfaces';
|
|
21
21
|
import { CrudHelperService } from './crud-helper.service';
|
|
22
22
|
import { ModelMetadataService } from './model-metadata.service';
|
|
23
|
+
import { ModuleMetadataHelperService } from 'src/helpers/module-metadata-helper.service';
|
|
23
24
|
|
|
24
25
|
@Injectable()
|
|
25
26
|
export class ModuleMetadataService {
|
|
@@ -38,6 +39,7 @@ export class ModuleMetadataService {
|
|
|
38
39
|
@Inject(forwardRef(() => ModelMetadataService))
|
|
39
40
|
private readonly modelMetadataService: ModelMetadataService,
|
|
40
41
|
private readonly solidRegistry: SolidRegistry,
|
|
42
|
+
private readonly moduleMetadataHelperService: ModuleMetadataHelperService,
|
|
41
43
|
) { }
|
|
42
44
|
|
|
43
45
|
async findMany(basicFilterDto: BasicFilterDto) {
|
|
@@ -154,7 +156,7 @@ export class ModuleMetadataService {
|
|
|
154
156
|
|
|
155
157
|
// Create the folder path inside 'module-metadata'
|
|
156
158
|
const folderPath = path.resolve(process.cwd(), 'module-metadata', module.name);
|
|
157
|
-
const filePath = this.getModuleMetadataFilePath(module);
|
|
159
|
+
const filePath = this.moduleMetadataHelperService.getModuleMetadataFilePath(module.name);
|
|
158
160
|
|
|
159
161
|
// Ensure the folder exists
|
|
160
162
|
await fs.mkdir(folderPath, { recursive: true });
|
|
@@ -205,12 +207,12 @@ export class ModuleMetadataService {
|
|
|
205
207
|
|
|
206
208
|
async updateInFile(module: ModuleMetadata) {
|
|
207
209
|
try {
|
|
208
|
-
const filePath = this.getModuleMetadataFilePath(module);
|
|
210
|
+
const filePath = this.moduleMetadataHelperService.getModuleMetadataFilePath(module.name);
|
|
209
211
|
|
|
210
212
|
// Read the existing JSON file
|
|
211
213
|
let metaData;
|
|
212
214
|
try {
|
|
213
|
-
metaData = await this.
|
|
215
|
+
metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
|
|
214
216
|
|
|
215
217
|
} catch (error) {
|
|
216
218
|
metaData = {
|
|
@@ -255,17 +257,6 @@ export class ModuleMetadataService {
|
|
|
255
257
|
}
|
|
256
258
|
|
|
257
259
|
|
|
258
|
-
private async getModuleMetadata(filePath: string): Promise<any> {
|
|
259
|
-
const fileContent = await fs.readFile(filePath, 'utf8');
|
|
260
|
-
return JSON.parse(fileContent);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
private getModuleMetadataFilePath(module: ModuleMetadata) {
|
|
264
|
-
const folderPath = path.resolve(process.cwd(), 'module-metadata', module.name);
|
|
265
|
-
const filePath = path.join(folderPath, `${module.name}-metadata.json`);
|
|
266
|
-
return filePath;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
260
|
async upsert(updateModuleMetadataDto: UpdateModuleMetadataDto) {
|
|
270
261
|
this.logger.log(`Module Upsert called for : ${updateModuleMetadataDto.name}`);
|
|
271
262
|
// First check if module already exists using name
|
package/src/solid-core.module.ts
CHANGED
|
@@ -135,6 +135,7 @@ import { UserService } from './services/user.service';
|
|
|
135
135
|
import { Setting } from './entities/setting.entity';
|
|
136
136
|
import { SettingService } from './services/setting.service';
|
|
137
137
|
import { SettingController } from './controllers/setting.controller';
|
|
138
|
+
import { ModuleMetadataHelperService } from './helpers/module-metadata-helper.service';
|
|
138
139
|
|
|
139
140
|
|
|
140
141
|
@Global()
|
|
@@ -223,6 +224,7 @@ import { SettingController } from './controllers/setting.controller';
|
|
|
223
224
|
useClass: PermissionsGuard,
|
|
224
225
|
},
|
|
225
226
|
ModuleMetadataService,
|
|
227
|
+
ModuleMetadataHelperService,
|
|
226
228
|
ModelMetadataService,
|
|
227
229
|
FieldMetadataService,
|
|
228
230
|
RemoveFieldsCommand,
|