@solidstarters/solid-core 1.2.91 → 1.2.93

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 (58) hide show
  1. package/# Password field.md +8 -0
  2. package/dist/controllers/import-transaction.controller.d.ts +2 -1
  3. package/dist/controllers/import-transaction.controller.d.ts.map +1 -1
  4. package/dist/controllers/import-transaction.controller.js +16 -4
  5. package/dist/controllers/import-transaction.controller.js.map +1 -1
  6. package/dist/dtos/create-menu-item-metadata.dto.d.ts +2 -1
  7. package/dist/dtos/create-menu-item-metadata.dto.d.ts.map +1 -1
  8. package/dist/dtos/create-menu-item-metadata.dto.js +22 -3
  9. package/dist/dtos/create-menu-item-metadata.dto.js.map +1 -1
  10. package/dist/dtos/create-role-metadata.dto.d.ts.map +1 -1
  11. package/dist/dtos/create-role-metadata.dto.js +11 -0
  12. package/dist/dtos/create-role-metadata.dto.js.map +1 -1
  13. package/dist/dtos/update-menu-item-metadata.dto.d.ts +2 -1
  14. package/dist/dtos/update-menu-item-metadata.dto.d.ts.map +1 -1
  15. package/dist/dtos/update-menu-item-metadata.dto.js +24 -5
  16. package/dist/dtos/update-menu-item-metadata.dto.js.map +1 -1
  17. package/dist/dtos/update-role-metadata.dto.d.ts.map +1 -1
  18. package/dist/dtos/update-role-metadata.dto.js +12 -1
  19. package/dist/dtos/update-role-metadata.dto.js.map +1 -1
  20. package/dist/entities/menu-item-metadata.entity.d.ts +2 -1
  21. package/dist/entities/menu-item-metadata.entity.d.ts.map +1 -1
  22. package/dist/entities/menu-item-metadata.entity.js +13 -8
  23. package/dist/entities/menu-item-metadata.entity.js.map +1 -1
  24. package/dist/seeders/seed-data/solid-core-metadata.json +31 -0
  25. package/dist/services/authentication.service.d.ts.map +1 -1
  26. package/dist/services/authentication.service.js +8 -3
  27. package/dist/services/authentication.service.js.map +1 -1
  28. package/dist/services/crud.service.js +1 -1
  29. package/dist/services/crud.service.js.map +1 -1
  30. package/dist/services/import-transaction.service.d.ts +19 -6
  31. package/dist/services/import-transaction.service.d.ts.map +1 -1
  32. package/dist/services/import-transaction.service.js +214 -87
  33. package/dist/services/import-transaction.service.js.map +1 -1
  34. package/dist/services/menu-item-metadata.service.d.ts.map +1 -1
  35. package/dist/services/menu-item-metadata.service.js +1 -0
  36. package/dist/services/menu-item-metadata.service.js.map +1 -1
  37. package/dist/services/setting.service.d.ts.map +1 -1
  38. package/dist/services/setting.service.js +6 -2
  39. package/dist/services/setting.service.js.map +1 -1
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/package.json +1 -1
  42. package/src/controllers/import-transaction.controller.ts +9 -3
  43. package/src/dtos/create-menu-item-metadata.dto.ts +21 -13
  44. package/src/dtos/create-role-metadata.dto.ts +48 -45
  45. package/src/dtos/update-menu-item-metadata.dto.ts +23 -16
  46. package/src/dtos/update-role-metadata.dto.ts +49 -47
  47. package/src/entities/menu-item-metadata.entity.ts +14 -14
  48. package/src/seeders/seed-data/email-templates/forgot-password.handlebars.html +134 -154
  49. package/src/seeders/seed-data/email-templates/on-force-password-change.handlebars.html +141 -195
  50. package/src/seeders/seed-data/email-templates/otp-on-login.handlebars.html +130 -144
  51. package/src/seeders/seed-data/email-templates/otp-on-register.handlebars.html +131 -145
  52. package/src/seeders/seed-data/solid-core-metadata.json +31 -0
  53. package/src/services/authentication.service.ts +8 -3
  54. package/src/services/crud.service.ts +1 -1
  55. package/src/services/import-transaction.service.ts +313 -121
  56. package/src/services/menu-item-metadata.service.ts +2 -0
  57. package/src/services/pending_import_issues +3 -0
  58. package/src/services/setting.service.ts +6 -2
@@ -11,15 +11,18 @@ import { ModelMetadataService } from 'src/services/model-metadata.service';
11
11
  import { ModuleMetadataService } from 'src/services/module-metadata.service';
12
12
 
13
13
 
14
+ import { classify } from '@angular-devkit/core/src/utils/strings';
14
15
  import { HttpService } from '@nestjs/axios';
15
- import { SolidFieldType } from 'src/dtos/create-field-metadata.dto';
16
+ import { RelationFieldsCommand, RelationType, SolidFieldType } from 'src/dtos/create-field-metadata.dto';
16
17
  import { ImportInstructionsResponseDto, StandardImportInstructionsResponseDto } from 'src/dtos/import-instructions.dto';
17
18
  import { FieldMetadata } from 'src/entities/field-metadata.entity';
19
+ import { ModelMetadata } from 'src/entities/model-metadata.entity';
18
20
  import { MediaWithFullUrl } from 'src/interfaces';
19
21
  import { Readable } from 'stream';
20
22
  import { ImportTransaction } from '../entities/import-transaction.entity';
21
23
  import { CsvService } from './csv.service';
22
24
  import { ExcelService } from './excel.service';
25
+ import { SolidIntrospectService } from './solid-introspect.service';
23
26
 
24
27
  interface ImportTemplateFileInfo {
25
28
  stream: NodeJS.ReadableStream;
@@ -45,11 +48,16 @@ export interface ImportableFieldInfo {
45
48
  displayName: string;
46
49
  }
47
50
 
48
- export interface ImportReadResult {
51
+ export interface ImportPaginatedReadResult {
49
52
  headers: string[]; // Headers of the CSV file
50
53
  data: Record<string, any>[]; // Data records in the current page
51
54
  }
52
55
 
56
+ interface ImportMapping {
57
+ header: string; // The name of the field in the import file
58
+ fieldName: string; // The name of the field in the model metadata to which the imported field is mapped
59
+ }
60
+
53
61
  @Injectable()
54
62
  export class ImportTransactionService extends CRUDService<ImportTransaction> {
55
63
  constructor(
@@ -66,96 +74,56 @@ export class ImportTransactionService extends CRUDService<ImportTransaction> {
66
74
  readonly moduleRef: ModuleRef,
67
75
  readonly excelService: ExcelService,
68
76
  readonly csvService: CsvService,
69
- readonly httpService: HttpService
70
-
77
+ readonly httpService: HttpService,
78
+ readonly introspectService: SolidIntrospectService,
79
+ // readonly fieldMetadataService: FieldMetadataService,
71
80
  ) {
72
81
  super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'importTransaction', 'solid-core', moduleRef);
73
82
  }
74
83
 
75
84
  private readonly logger = new Logger(ImportTransactionService.name);
76
- saveImportMapping(arg0: number) {
77
- throw new Error('Method not implemented.');
78
- }
79
85
 
80
- async getImportMappingInfo(importTransactionId: number): Promise<ImportMappingInfo> {
81
- // Load the import transaction for the given ID
82
- const importTransaction = await this.findOne(importTransactionId, {
83
- populate: ['modelMetadata', 'modelMetadata.fields'],
84
- populateMedia: ['fileLocation'],
86
+ /**
87
+ * This method is used to return a csv / excel template for the import transaction
88
+ * It will contain the display names of the fields in the header row
89
+ * @param modelMetadataId
90
+ */
91
+ async getImportTemplate(modelMetadataId: number, format: ImportFormat = ImportFormat.CSV): Promise<ImportTemplateFileInfo> {
92
+ // Load the model metadata for the given ID
93
+ const modelMetadata = await this.modelMetadataService.findOne(modelMetadataId, {
94
+ populate: ['fields'],
85
95
  });
86
- if (!importTransaction) {
87
- throw new Error(`Import transaction with ID ${importTransactionId} not found.`);
88
- }
89
-
90
- // Get all the importable fields from the model metadata
91
- const importableFields: ImportableFieldInfo[] = this.fieldsAllowedForImport(importTransaction.modelMetadata.fields).map(field => ({
92
- name: field.name,
93
- displayName: field.displayName,
94
- }));
95
-
96
- // Get the import file stream for the import transaction
97
- const importFileMediaObject = importTransaction['_media']['fileLocation'][0] as MediaWithFullUrl; // Since there can be only one fileLocation, we can safely access the first element
98
- if (!importFileMediaObject) {
99
- throw new Error(`Import file for transaction ID ${importTransactionId} not found.`);
100
- }
101
- const importFileStream = await this.getImportFileStream(importFileMediaObject);
102
-
103
- // Get a sample of records from the import file
104
- const sampleRecord = await this.getFileRecordsSample(importFileStream, importFileMediaObject.mimeType);
105
-
106
- // Convert sampleRecord to the format required for SampleImportedRecordInfo
107
- const wrappedRecords: SampleImportedRecordInfo[] = sampleRecord.data.map((record: Record<string, any>) => {
108
- return Object.entries(record).map(([key, value]) => ({
109
- cellHeader: key,
110
- cellValue: value,
111
- defaultMappedFieldName: importableFields.find(field => field.displayName === key)?.name || '',
112
- }));
113
- }).flat();
114
-
115
- // for await (const page of this.csvService.readCsvInPagesFromStream(importFileStream)) {
116
- // // await dbService.bulkInsert(page);
117
- // }
118
-
119
- return {
120
- sampleImportedRecordInfo: wrappedRecords, // This will hold the sample data from the file
121
- importableFields: importableFields, // This will hold the fields that can be imported
122
- };
123
- }
124
-
125
- private async getFileRecordsSample(importFileStream: Readable, mimeType: string): Promise<ImportReadResult> {
126
- // Depending upon the mime type of the file, read the file in pages
127
- // For CSV files, use the csvService to read the file in pages
128
- // For Excel files, use the excelService to read the file in pages
129
- if (mimeType === 'text/csv') {
130
- const generator = this.csvService.readCsvInPagesFromStream(importFileStream, { pageSize: 1 });
131
- const firstRecord = await generator.next(); // Get the first record to extract headers and sample data
132
- return firstRecord.value;
133
- } else if (mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
134
- const generator = this.excelService.readExcelInPagesFromStream(importFileStream, { pageSize: 1 });
135
- const firstRecord = await generator.next(); // Get the first record to extract headers and sample data
136
- return firstRecord.value;
137
- }
138
- else { // If the file is neither CSV nor Excel, throw an error
139
- throw new Error(`Unsupported file type: ${mimeType}`);
96
+ if (!modelMetadata) {
97
+ throw new Error(`Model metadata with ID ${modelMetadataId} not found.`);
140
98
  }
141
- }
99
+ // Create a header row with the display names of the fields, excluding the media fields,computed fields
100
+ const headers = this.fieldsAllowedForImport(modelMetadata.fields)
101
+ .map(field => field.displayName);
142
102
 
143
- private async getImportFileStream(importFileMediaObject: MediaWithFullUrl): Promise<Readable> {
144
- const fileUrl = importFileMediaObject['_full_url'];
145
- const mimeType = importFileMediaObject['mimeType'];
146
- if (!fileUrl) {
147
- throw new Error(`File URL ${fileUrl} not found.`);
103
+ // Depending on the format, generate the template
104
+ if (format === ImportFormat.CSV) {
105
+ const stream = await this.csvService.createCsvStream(null, 0, headers); // Create a CSV stream with the header row
106
+ const fileName = `${modelMetadata.singularName}-import-template.csv`;
107
+ const mimeType = 'text/csv';
108
+ return {
109
+ stream,
110
+ fileName,
111
+ mimeType,
112
+ };
113
+ } else if (format === ImportFormat.EXCEL) {
114
+ const stream = await this.excelService.createExcelStream(null, 0, headers); // Create an Excel stream with the header row
115
+ const fileName = `${modelMetadata.singularName}-import-template.xlsx`;
116
+ // Set the MIME type for Excel files
117
+ const mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
118
+ return {
119
+ stream,
120
+ fileName,
121
+ mimeType,
122
+ };
123
+ } else {
124
+ throw new Error(`Unsupported import format: ${format}`);
148
125
  }
149
- // From the file URL, convert the file URL to a readable stream using nestjs http service and axios
150
- const fileUrlResponse = await this.httpService.axiosRef.get(fileUrl, {
151
- responseType: 'stream',
152
- });
153
126
 
154
- if (!fileUrlResponse || !fileUrlResponse.data) {
155
- throw new Error(`Failed to read file from URL: ${fileUrl}`);
156
- }
157
- // fileUrlResponse.data is a Node.js Readable stream
158
- return fileUrlResponse.data;
159
127
  }
160
128
 
161
129
  async getImportInstructions(modelMetadataId: number): Promise<ImportInstructionsResponseDto> {
@@ -220,47 +188,76 @@ export class ImportTransactionService extends CRUDService<ImportTransaction> {
220
188
  };
221
189
  }
222
190
 
223
- /**
224
- * This method is used to return a csv / excel template for the import transaction
225
- * It will contain the display names of the fields in the header row
226
- * @param modelMetadataId
227
- */
228
- async getImportTemplate(modelMetadataId: number, format: ImportFormat = ImportFormat.CSV): Promise<ImportTemplateFileInfo> {
229
- // Load the model metadata for the given ID
230
- const modelMetadata = await this.modelMetadataService.findOne(modelMetadataId, {
231
- populate: ['fields'],
232
- });
233
- if (!modelMetadata) {
234
- throw new Error(`Model metadata with ID ${modelMetadataId} not found.`);
235
- }
236
- // Create a header row with the display names of the fields, excluding the media fields,computed fields
237
- const headers = this.fieldsAllowedForImport(modelMetadata.fields)
238
- .map(field => field.displayName);
191
+ async getImportMappingInfo(importTransactionId: number): Promise<ImportMappingInfo> {
192
+ // Load the import transaction for the given ID
193
+ const importTransaction = await this.loadImportTransaction(importTransactionId);
239
194
 
240
- // Depending on the format, generate the template
241
- if (format === ImportFormat.CSV) {
242
- const stream = await this.csvService.createCsvStream(null, 0, headers); // Create a CSV stream with the header row
243
- const fileName = `${modelMetadata.singularName}-import-template.csv`;
244
- const mimeType = 'text/csv';
245
- return {
246
- stream,
247
- fileName,
248
- mimeType,
249
- };
250
- } else if (format === ImportFormat.EXCEL) {
251
- const stream = await this.excelService.createExcelStream(null, 0, headers); // Create an Excel stream with the header row
252
- const fileName = `${modelMetadata.singularName}-import-template.xlsx`;
253
- // Set the MIME type for Excel files
254
- const mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
255
- return {
256
- stream,
257
- fileName,
258
- mimeType,
259
- };
260
- } else {
261
- throw new Error(`Unsupported import format: ${format}`);
262
- }
195
+ // Get all the importable fields from the model metadata
196
+ const importableFields: ImportableFieldInfo[] = this.fieldsAllowedForImport(importTransaction.modelMetadata.fields).map(field => ({
197
+ name: field.name,
198
+ displayName: field.displayName,
199
+ }));
200
+
201
+ // Get the import file media object from the import transaction
202
+ const importFileMediaObject = this.getImportFileObject(importTransaction);
203
+
204
+ // Get the import file stream for the import transaction
205
+ const importFileStream = await this.getImportFileStream(importFileMediaObject);
206
+
207
+ // Get a sample of records from the import file
208
+ const sampleRecord = await this.getFileRecordsSample(importFileStream, importFileMediaObject.mimeType);
209
+
210
+ // Convert sampleRecord to the format required for SampleImportedRecordInfo
211
+ const wrappedRecords: SampleImportedRecordInfo[] = sampleRecord.data.map((record: Record<string, any>) => {
212
+ return Object.entries(record).map(([key, value]) => ({
213
+ cellHeader: key,
214
+ cellValue: value,
215
+ defaultMappedFieldName: importableFields.find(field => field.displayName === key)?.name || '',
216
+ }));
217
+ }).flat();
218
+
219
+ // for await (const page of this.csvService.readCsvInPagesFromStream(importFileStream)) {
220
+ // // await dbService.bulkInsert(page);
221
+ // }
222
+
223
+ return {
224
+ sampleImportedRecordInfo: wrappedRecords, // This will hold the sample data from the file
225
+ importableFields: importableFields, // This will hold the fields that can be imported
226
+ };
227
+ }
228
+
229
+ async startImportSync(importTransactionId: number): Promise<Array<number>> {
230
+ // Load the import transaction for the given ID
231
+ const importTransaction = await this.loadImportTransaction(importTransactionId);
232
+
233
+ // Get the import file media object from the import transaction
234
+ const importFileMediaObject = this.getImportFileObject(importTransaction);
235
+
236
+ // Get the import file stream for the import transaction
237
+ const importFileStream = await this.getImportFileStream(importFileMediaObject);
238
+
239
+ const ids = await this.writeFileRecordsToDb(
240
+ importFileStream,
241
+ importFileMediaObject.mimeType,
242
+ JSON.parse(importTransaction.mapping) as ImportMapping[], // Parse the mapping from the import transaction
243
+ importTransaction.modelMetadata,
244
+ );
245
+ return ids; // Return the IDs of the created records
246
+ }
263
247
 
248
+ startImportAsync(importTransactionId: number): Promise<void> {
249
+ throw new Error('Method not implemented.');
250
+ }
251
+
252
+ private async loadImportTransaction(importTransactionId: number) {
253
+ const importTransaction = await this.findOne(importTransactionId, {
254
+ populate: ['modelMetadata', 'modelMetadata.fields'],
255
+ populateMedia: ['fileLocation'],
256
+ });
257
+ if (!importTransaction) {
258
+ throw new Error(`Import transaction with ID ${importTransactionId} not found.`);
259
+ }
260
+ return importTransaction;
264
261
  }
265
262
 
266
263
  private fieldsAllowedForImport(fields: FieldMetadata[]): FieldMetadata[] {
@@ -276,6 +273,201 @@ export class ImportTransactionService extends CRUDService<ImportTransaction> {
276
273
  );
277
274
  }
278
275
 
276
+ private async getFileRecordsSample(importFileStream: Readable, mimeType: string): Promise<ImportPaginatedReadResult> {
277
+ // Depending upon the mime type of the file, read the file in pages
278
+ // For CSV files, use the csvService to read the file in pages
279
+ // For Excel files, use the excelService to read the file in pages
280
+ if (mimeType === 'text/csv') {
281
+ const generator = this.csvService.readCsvInPagesFromStream(importFileStream, { pageSize: 1 });
282
+ const firstRecord = await generator.next(); // Get the first record to extract headers and sample data
283
+ return firstRecord.value;
284
+ } else if (mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
285
+ const generator = this.excelService.readExcelInPagesFromStream(importFileStream, { pageSize: 1 });
286
+ const firstRecord = await generator.next(); // Get the first record to extract headers and sample data
287
+ return firstRecord.value;
288
+ }
289
+ else { // If the file is neither CSV nor Excel, throw an error
290
+ throw new Error(`Unsupported file type: ${mimeType}`);
291
+ }
292
+ }
293
+
294
+ private getImportFileObject(importTransaction: ImportTransaction): MediaWithFullUrl {
295
+ const importFileMediaObject = importTransaction['_media']['fileLocation'][0] as MediaWithFullUrl; // Since there can be only one fileLocation, we can safely access the first element
296
+ if (!importFileMediaObject) {
297
+ throw new Error(`Import file for transaction ID ${importTransaction.id} not found.`);
298
+ }
299
+ return importFileMediaObject;
300
+ }
279
301
 
302
+ private async getImportFileStream(importFileMediaObject: MediaWithFullUrl): Promise<Readable> {
303
+ const fileUrl = importFileMediaObject['_full_url'];
304
+ const mimeType = importFileMediaObject['mimeType'];
305
+ if (!fileUrl) {
306
+ throw new Error(`File URL ${fileUrl} not found.`);
307
+ }
308
+ // From the file URL, convert the file URL to a readable stream using nestjs http service and axios
309
+ const fileUrlResponse = await this.httpService.axiosRef.get(fileUrl, {
310
+ responseType: 'stream',
311
+ });
280
312
 
281
- }
313
+ if (!fileUrlResponse || !fileUrlResponse.data) {
314
+ throw new Error(`Failed to read file from URL: ${fileUrl}`);
315
+ }
316
+ // fileUrlResponse.data is a Node.js Readable stream
317
+ return fileUrlResponse.data;
318
+ }
319
+
320
+ private async writeFileRecordsToDb(
321
+ importFileStream: Readable,
322
+ mimeType: string,
323
+ mapping: ImportMapping[],
324
+ modelMetadataWithFields: ModelMetadata,
325
+ ): Promise<Array<number>> {
326
+ // Get the model service for the model metadata name
327
+ const modelService = this.getModelService(modelMetadataWithFields.singularName);
328
+
329
+ // Depending upon the mime type of the file, read the file in pages
330
+ // For CSV files, use the csvService to read the file in pages
331
+ // For Excel files, use the excelService to read the file in pages
332
+ if (mimeType === 'text/csv') {
333
+ // Read the csv file in pages
334
+ for await (const page of this.csvService.readCsvInPagesFromStream(importFileStream)) {
335
+ // Convert the paginated result to DTOs
336
+ const dtos = await this.convertPaginatedResultToDtos(page, modelMetadataWithFields, mapping);
337
+ // Use the model service to create the records in the database
338
+ const createdRecords = await modelService.insertMany(dtos, [], {});
339
+ // Set the solidRequestContext to null, as this is a background job;
340
+ // Return the IDs of the created records
341
+ return createdRecords.map(record => record.id);
342
+ }
343
+ }
344
+ else if (mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
345
+ // Read the excel file in pages
346
+ for await (const page of this.excelService.readExcelInPagesFromStream(importFileStream)) {
347
+ // Convert the paginated result to DTOs
348
+ const dtos = await this.convertPaginatedResultToDtos(page, modelMetadataWithFields, mapping);
349
+ // Use the model service to create the records in the database
350
+ const createdRecords = await modelService.insertMany(dtos, [], {});
351
+ // Set the solidRequestContext to null, as this is a background job;
352
+ // Return the IDs of the created records
353
+ return createdRecords.map(record => record.id);
354
+ }
355
+ } else { // If the file is neither CSV nor Excel, throw an error
356
+ throw new Error(`Unsupported file type: ${mimeType}`);
357
+ }
358
+ }
359
+
360
+ private getModelService(modelSingularName: string): CRUDService<any> {
361
+ // Get the model service for the model metadata name
362
+ const modelServiceWrapper = this.introspectService.getProvider(`${classify(modelSingularName)}Service`);
363
+ const modelService = modelServiceWrapper.instance as CRUDService<any>;
364
+ if (!modelService) {
365
+ throw new Error(`Model service for ${modelSingularName} not found.`);
366
+ }
367
+ return modelService;
368
+ }
369
+
370
+ // This method will
371
+ private async convertPaginatedResultToDtos(importPaginatedResult: ImportPaginatedReadResult, modelMetadataWithFields: ModelMetadata, mapping: ImportMapping[]) {
372
+ const dtos = [];
373
+ // Iterate through the data records in the importPaginatedResult
374
+ for (const record of importPaginatedResult.data) {
375
+ // For every key in the record, get the corresponding field from the mapping, if the field is not found in mapping, skip the field
376
+ const dto = await this.convertImportedRecordToDto(record, mapping, modelMetadataWithFields);
377
+ dtos.push(dto);
378
+ }
379
+ return dtos;
380
+ }
381
+
382
+ private async convertImportedRecordToDto(record: Record<string, any>, mapping: ImportMapping[], modelMetadataWithFields: ModelMetadata) {
383
+ // Create a new record object
384
+ const dtoRecord: Record<string, any> = {};
385
+
386
+ // Using the saved mapping, populate the dtoRecord w.r.t the record and fields
387
+ for (const key in record) {
388
+ const mappedField = mapping.find(m => m.header === key);
389
+ if (mappedField) {
390
+ // If the field is found in the mapping, get the field metadata from the model metadata
391
+ const fieldMetadata = modelMetadataWithFields.fields.find(f => f.name === mappedField.fieldName);
392
+ // const userKeyField = modelMetadataWithFields.fields.find(f => f.isUserKey === true); // Assuming userKey is a field in the model metadata
393
+ if (fieldMetadata) {
394
+ // If the field is found in the model metadata, set the value in the dtoRecord
395
+ await this.populateDto(dtoRecord, fieldMetadata, record, key);
396
+ } else {
397
+ this.logger.warn(`Field ${mappedField.fieldName} not found in model metadata ${modelMetadataWithFields.singularName}`);
398
+ }
399
+ }
400
+ }
401
+ return dtoRecord;
402
+ }
403
+
404
+ private async populateDto(dtoRecord: Record<string, any>, fieldMetadata: FieldMetadata, record: Record<string, any>, key: string): Promise<Record<string, any>> {
405
+ const fieldType = fieldMetadata.type;
406
+ // const userKeyFieldName = userKeyField?.name || 'id'; // Default to 'id' if not found
407
+
408
+ switch (fieldType) {
409
+ case SolidFieldType.relation: {
410
+ return await this.populateDtoForRelations(fieldMetadata, record, key, dtoRecord);
411
+ }
412
+ case SolidFieldType.date: return this.populateDtoForDate(record, key, fieldMetadata, dtoRecord);
413
+ case SolidFieldType.datetime: return this.populateDtoForDate(record, key, fieldMetadata, dtoRecord);
414
+ default:
415
+ dtoRecord[fieldMetadata.name] = record[key];
416
+ return dtoRecord;
417
+ }
418
+ }
419
+
420
+ private populateDtoForDate(record: Record<string, any>, key: string, fieldMetadata: FieldMetadata, dtoRecord: Record<string, any>) {
421
+ {
422
+ const dateValue = new Date(record[key]);
423
+ if (isNaN(dateValue.getTime())) {
424
+ throw new Error(`Invalid date value for field ${fieldMetadata.name}: ${record[key]}`);
425
+ }
426
+ dtoRecord[fieldMetadata.name] = dateValue;
427
+ return dtoRecord;
428
+ }
429
+ }
430
+
431
+ private async populateDtoForRelations(fieldMetadata: FieldMetadata, record: Record<string, any>, key: string, dtoRecord: Record<string, any>) {
432
+ if (!fieldMetadata.relationCoModelSingularName) {
433
+ throw new Error(`Relation coModelSingularName is not defined for relation field ${fieldMetadata.name}`);
434
+ }
435
+
436
+ const relatedRecordsIds = await this.getRelatedEntityIdsFromUserKeys(fieldMetadata, record, key);
437
+
438
+ if (fieldMetadata.relationType === RelationType.manyTomany || fieldMetadata.relationType === RelationType.oneToMany) {
439
+ dtoRecord[`${fieldMetadata.name}Ids`] = relatedRecordsIds;
440
+ dtoRecord[`${fieldMetadata.name}Command`] = RelationFieldsCommand.set; // Reset the relation field association with the related records IDs
441
+ }
442
+ else if (fieldMetadata.relationType === RelationType.manyToOne) {
443
+ dtoRecord[`${fieldMetadata.name}Id`] = relatedRecordsIds.pop(); // For many-to-one relations, we need only one ID
444
+ }
445
+ return dtoRecord;
446
+ }
447
+
448
+ private async getRelatedEntityIdsFromUserKeys(fieldMetadata: FieldMetadata, record: Record<string, any>, key: string): Promise<Array<number>> {
449
+ const coModelService = this.getModelService(fieldMetadata.relationCoModelSingularName);
450
+ const coModelWithUserKeyField = await this.modelMetadataService.findOneBySingularName(fieldMetadata.relationCoModelSingularName, ['userKeyField']);
451
+ const coModelUserKeyFieldName = coModelWithUserKeyField?.userKeyField?.name || 'id'; // Default to 'id' if not found
452
+
453
+ // For many-to-many or one-to-many relations, we expect the record cell to contains a comma-separated list of userKeys
454
+ const relationUserKeys = record[key] ? String(record[key]).split(',').map((userKey: string) => userKey.trim()) : [];
455
+
456
+ // Set the relation basic filter dto filters with the userkeys and call the find method of the model service to get the related records
457
+ const relationFilterDto = {
458
+ filters: {
459
+ [coModelUserKeyFieldName]: {
460
+ $in: relationUserKeys, // Use the userKeyFieldName to filter by userKeys
461
+ },
462
+ },
463
+ };
464
+
465
+ // From the userKeys, we will get the IDs of the related records using the userKeyFieldName and throw an error if any of the userKeys is not found
466
+ const relatedRecordsResult = await coModelService.find(relationFilterDto);
467
+ if (!relatedRecordsResult || !relatedRecordsResult.records || relatedRecordsResult.records.length === 0 || relatedRecordsResult.records.length !== relationUserKeys.length) {
468
+ throw new Error(`Missing related records found for userKeys: ${relationUserKeys.join(', ')} in model ${fieldMetadata.relationCoModelSingularName}`);
469
+ }
470
+ const relatedRecordsIds = relatedRecordsResult.records.map(record => record.id);
471
+ return relatedRecordsIds;
472
+ }
473
+ }
@@ -178,6 +178,8 @@ export class MenuItemMetadataService extends CRUDService<MenuItemMetadata> {
178
178
  title: rootItem.displayName || rootItem.name,
179
179
  path: path,
180
180
  key: rootItem.name.toLowerCase().replace(/\s+/g, '-'),
181
+ icon : rootItem.iconName,
182
+ // iconVariant : rootItem.iconVariant
181
183
  }
182
184
  if (children.length > 0) {
183
185
  data["children"] = this.buildMenuTree(children, allMenuItems, activeUser);
@@ -0,0 +1,3 @@
1
+ 1. fileLocation -> file
2
+ 2. Change the import response to return proper shape.
3
+ 3. Change the urls to reflect proper semantics.
@@ -58,7 +58,9 @@ export class SettingService extends CRUDService<Setting> {
58
58
  appPrivacyPolicy: "",
59
59
  defaultRole: this.iamConfiguration.defaultRole,
60
60
  shouldQueueEmails: this.commonConfiguration.shouldQueueEmails,
61
- shouldQueueSms: this.commonConfiguration.shouldQueueSms
61
+ shouldQueueSms: this.commonConfiguration.shouldQueueSms,
62
+ enableDarkMode: true,
63
+ copyright : ""
62
64
  };
63
65
 
64
66
  const existingSettings = await this.repo.find();
@@ -138,7 +140,9 @@ export class SettingService extends CRUDService<Setting> {
138
140
  appPrivacyPolicy: "",
139
141
  defaultRole: this.iamConfiguration.defaultRole,
140
142
  shouldQueueEmails: this.commonConfiguration.shouldQueueEmails,
141
- shouldQueueSms: this.commonConfiguration.shouldQueueSms
143
+ shouldQueueSms: this.commonConfiguration.shouldQueueSms,
144
+ enableDarkMode: true,
145
+ copyright : ""
142
146
  };
143
147
  }
144
148