@solidstarters/solid-core 1.2.88 → 1.2.90

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 (102) hide show
  1. package/dist/controllers/export-template.controller.d.ts +3 -2
  2. package/dist/controllers/export-template.controller.d.ts.map +1 -1
  3. package/dist/controllers/export-template.controller.js +23 -10
  4. package/dist/controllers/export-template.controller.js.map +1 -1
  5. package/dist/controllers/import-transaction-error-log.controller.d.ts +41 -0
  6. package/dist/controllers/import-transaction-error-log.controller.d.ts.map +1 -0
  7. package/dist/controllers/import-transaction-error-log.controller.js +179 -0
  8. package/dist/controllers/import-transaction-error-log.controller.js.map +1 -0
  9. package/dist/controllers/import-transaction.controller.d.ts +46 -0
  10. package/dist/controllers/import-transaction.controller.d.ts.map +1 -0
  11. package/dist/controllers/import-transaction.controller.js +236 -0
  12. package/dist/controllers/import-transaction.controller.js.map +1 -0
  13. package/dist/dtos/create-import-transaction-error-log.dto.d.ts +10 -0
  14. package/dist/dtos/create-import-transaction-error-log.dto.d.ts.map +1 -0
  15. package/dist/dtos/create-import-transaction-error-log.dto.js +65 -0
  16. package/dist/dtos/create-import-transaction-error-log.dto.js.map +1 -0
  17. package/dist/dtos/create-import-transaction.dto.d.ts +14 -0
  18. package/dist/dtos/create-import-transaction.dto.d.ts.map +1 -0
  19. package/dist/dtos/create-import-transaction.dto.js +90 -0
  20. package/dist/dtos/create-import-transaction.dto.js.map +1 -0
  21. package/dist/dtos/export.dto.d.ts +5 -0
  22. package/dist/dtos/export.dto.d.ts.map +1 -0
  23. package/dist/dtos/export.dto.js +12 -0
  24. package/dist/dtos/export.dto.js.map +1 -0
  25. package/dist/dtos/import-instructions.dto.d.ts +19 -0
  26. package/dist/dtos/import-instructions.dto.d.ts.map +1 -0
  27. package/dist/dtos/import-instructions.dto.js +110 -0
  28. package/dist/dtos/import-instructions.dto.js.map +1 -0
  29. package/dist/dtos/update-import-transaction-error-log.dto.d.ts +11 -0
  30. package/dist/dtos/update-import-transaction-error-log.dto.d.ts.map +1 -0
  31. package/dist/dtos/update-import-transaction-error-log.dto.js +72 -0
  32. package/dist/dtos/update-import-transaction-error-log.dto.js.map +1 -0
  33. package/dist/dtos/update-import-transaction.dto.d.ts +15 -0
  34. package/dist/dtos/update-import-transaction.dto.d.ts.map +1 -0
  35. package/dist/dtos/update-import-transaction.dto.js +91 -0
  36. package/dist/dtos/update-import-transaction.dto.js.map +1 -0
  37. package/dist/entities/export-transaction.entity.js +1 -1
  38. package/dist/entities/export-transaction.entity.js.map +1 -1
  39. package/dist/entities/import-transaction-error-log.entity.d.ts +11 -0
  40. package/dist/entities/import-transaction-error-log.entity.d.ts.map +1 -0
  41. package/dist/entities/import-transaction-error-log.entity.js +53 -0
  42. package/dist/entities/import-transaction-error-log.entity.js.map +1 -0
  43. package/dist/entities/import-transaction.entity.d.ts +11 -0
  44. package/dist/entities/import-transaction.entity.d.ts.map +1 -0
  45. package/dist/entities/import-transaction.entity.js +55 -0
  46. package/dist/entities/import-transaction.entity.js.map +1 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +2 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/seeders/seed-data/solid-core-metadata.json +317 -1
  52. package/dist/services/csv.service.d.ts +11 -1
  53. package/dist/services/csv.service.d.ts.map +1 -1
  54. package/dist/services/csv.service.js +72 -5
  55. package/dist/services/csv.service.js.map +1 -1
  56. package/dist/services/excel.service.d.ts +11 -1
  57. package/dist/services/excel.service.d.ts.map +1 -1
  58. package/dist/services/excel.service.js +68 -10
  59. package/dist/services/excel.service.js.map +1 -1
  60. package/dist/services/export-template.service.d.ts +3 -2
  61. package/dist/services/export-template.service.d.ts.map +1 -1
  62. package/dist/services/export-template.service.js +6 -6
  63. package/dist/services/export-template.service.js.map +1 -1
  64. package/dist/services/export-transaction.service.d.ts +6 -3
  65. package/dist/services/export-transaction.service.d.ts.map +1 -1
  66. package/dist/services/export-transaction.service.js +101 -22
  67. package/dist/services/export-transaction.service.js.map +1 -1
  68. package/dist/services/import-transaction-error-log.service.d.ts +22 -0
  69. package/dist/services/import-transaction-error-log.service.d.ts.map +1 -0
  70. package/dist/services/import-transaction-error-log.service.js +56 -0
  71. package/dist/services/import-transaction-error-log.service.js.map +1 -0
  72. package/dist/services/import-transaction.service.d.ts +64 -0
  73. package/dist/services/import-transaction.service.d.ts.map +1 -0
  74. package/dist/services/import-transaction.service.js +231 -0
  75. package/dist/services/import-transaction.service.js.map +1 -0
  76. package/dist/solid-core.module.d.ts.map +1 -1
  77. package/dist/solid-core.module.js +13 -1
  78. package/dist/solid-core.module.js.map +1 -1
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +1 -1
  81. package/src/controllers/export-template.controller.ts +20 -7
  82. package/src/controllers/import-transaction-error-log.controller.ts +93 -0
  83. package/src/controllers/import-transaction.controller.ts +128 -0
  84. package/src/dtos/create-import-transaction-error-log.dto.ts +34 -0
  85. package/src/dtos/create-import-transaction.dto.ts +50 -0
  86. package/src/dtos/export.dto.ts +5 -0
  87. package/src/dtos/import-instructions.dto.ts +66 -0
  88. package/src/dtos/update-import-transaction-error-log.dto.ts +39 -0
  89. package/src/dtos/update-import-transaction.dto.ts +52 -0
  90. package/src/entities/export-transaction.entity.ts +1 -1
  91. package/src/entities/import-transaction-error-log.entity.ts +22 -0
  92. package/src/entities/import-transaction.entity.ts +22 -0
  93. package/src/index.ts +2 -0
  94. package/src/seeders/seed-data/solid-core-metadata.json +319 -3
  95. package/src/services/csv.service.ts +116 -7
  96. package/src/services/excel.service.ts +109 -64
  97. package/src/services/export-template.service.ts +7 -6
  98. package/src/services/export-transaction.service.ts +136 -25
  99. package/src/services/export_issues.txt +5 -1
  100. package/src/services/import-transaction-error-log.service.ts +34 -0
  101. package/src/services/import-transaction.service.ts +281 -0
  102. package/src/solid-core.module.ts +13 -1
@@ -3,87 +3,69 @@ import * as ExcelJS from 'exceljs';
3
3
  import { PassThrough, Readable } from 'stream';
4
4
 
5
5
 
6
+ export interface ExcelReadOptions {
7
+ pageSize?: number; // Number of records per page
8
+ hasHeaderRow?: boolean; // Whether the first row contains headers
9
+ providedHeaders?: string[]; // Custom headers if hasHeaderRow is false
10
+ }
11
+
12
+ const DEFAULT_PAGE_SIZE = 100; // Default page size if not provided
13
+
14
+ export interface ExcelReadResult {
15
+ headers: string[]; // Headers of the Excel file
16
+ data: Record<string, any>[]; // Data records in the current page
17
+ }
18
+
6
19
  @Injectable()
7
20
  export class ExcelService {
8
21
  private logger = new Logger(ExcelService.name);
9
- // Sample JSON data
10
- // const jsonData = [
11
- // { id: 1, name: 'John Doe', age: 25, email: 'john@example.com' },
12
- // { id: 2, name: 'Jane Doe', age: 28, email: 'jane@example.com' },
13
- // { id: 3, name: 'Alice Smith', age: 30, email: 'alice@example.com' }
14
- // ];
15
-
16
- // public async createExcelFromJson(data: any[], fileName: string) {
17
- // const workbook = new ExcelJS.Workbook();
18
- // const worksheet = workbook.addWorksheet('Data');
19
-
20
- // // Define Columns (Header)
21
- // worksheet.columns = Object.keys(data[0]).map((key) => ({
22
- // header: key.toUpperCase(), // Convert header names to uppercase
23
- // key: key,
24
- // width: 20, // Set column width
25
- // }));
26
-
27
- // // Add Data Rows
28
- // data.forEach((item) => {
29
- // worksheet.addRow(item);
30
- // });
31
-
32
- // // Apply basic formatting
33
- // worksheet.getRow(1).font = { bold: true }; // Make headers bold
34
-
35
- // // Save file
36
- // await workbook.xlsx.writeFile(fileName);
37
- // this.logger.log(`✅ Excel file "${fileName}" created successfully!`);
38
- // // console.log(`✅ Excel file "${fileName}" created successfully!`);
39
- // }
40
-
41
- // public async createExcelStreamFromJson(data: any[]): Promise<Readable> {
42
- // const passThrough = new PassThrough(); // Stream to pipe data
43
- // const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: passThrough });
44
- // const worksheet = workbook.addWorksheet('Data');
45
- // worksheet.columns = Object.keys(data[0]).map((key) => ({
46
- // header: key.toUpperCase(),
47
- // key: key,
48
- // width: 20,
49
- // }));
50
-
51
- // data.forEach((item) => {
52
- // worksheet.addRow(item);
53
- // });
54
-
55
- // worksheet.getRow(1).font = { bold: true };
56
-
57
- // await workbook.commit();
58
- // return passThrough;
59
- // }
60
-
61
22
 
62
23
  public async createExcelStream(
63
24
  getDataRecords: (chunkIndex: number, chunkSize: number) => Promise<any[]>,
64
- chunkSize: number
25
+ chunkSize: number = 100,
26
+ headers: string[] = []
65
27
  ): Promise<Readable> {
28
+ // Validations
29
+ // If neither headers nor data records function is provided, throw an error
30
+ if (headers.length === 0 && typeof getDataRecords !== 'function') {
31
+ throw new Error('Either headers or data records function must be provided.');
32
+ }
33
+
34
+ // If data records function is provided, chunkSize must be greater than 0
35
+ if (getDataRecords && chunkSize <= 0) {
36
+ throw new Error('Chunk size must be greater than 0 when data records function is provided.');
37
+ }
38
+
66
39
  const passThrough = new PassThrough(); // Create streaming pipe
67
40
  try {
68
41
  const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: passThrough });
69
42
  const worksheet = workbook.addWorksheet('Data');
70
43
 
71
- let chunkIndex = 0;
72
- let isHeaderWritten = false;
44
+ // If headers are provided, use them;
45
+ if (headers.length > 0) {
46
+ worksheet.columns = headers.map((header) => ({
47
+ header: header, // Convert header names to uppercase
48
+ key: header,
49
+ width: 20, // Set column width
50
+ }));
51
+ }
52
+
53
+ // ✅ If no data loader provided, write only headers and finish
54
+ if (typeof getDataRecords !== 'function') {
55
+ // worksheet.addRow(
56
+ // headers.reduce((acc, header) => ({ ...acc, [header]: '' }), {})
57
+ // ).commit(); // Write a dummy record with headers
58
+
59
+ await workbook.commit();
60
+ return passThrough;
61
+ }
73
62
 
63
+ // Write the data records in chunks
64
+ let chunkIndex = 0;
74
65
  while (true) {
75
66
  const records = await getDataRecords(chunkIndex, chunkSize); // Fetch chunked data
76
67
  if (records.length === 0) break; // Stop if no more records
77
68
 
78
- if (!isHeaderWritten) {
79
- worksheet.columns = Object.keys(records[0]).map((key) => ({
80
- header: key.toUpperCase(),
81
- key: key,
82
- width: 20,
83
- }));
84
- isHeaderWritten = true;
85
- }
86
-
87
69
  records.forEach((item) => {
88
70
  worksheet.addRow(item).commit(); // Commit each row immediately
89
71
  });
@@ -102,4 +84,67 @@ export class ExcelService {
102
84
  return passThrough; // Return streaming response
103
85
  }
104
86
 
87
+ public async *readExcelInPagesFromStream(
88
+ stream: Readable,
89
+ options?: ExcelReadOptions
90
+ ): AsyncGenerator<ExcelReadResult> {
91
+ const { pageSize = DEFAULT_PAGE_SIZE, hasHeaderRow = true, providedHeaders = [] } = options || {};
92
+ const workbookReader = new ExcelJS.stream.xlsx.WorkbookReader(stream, {});
93
+
94
+ let headers: string[] = [];
95
+ let page: Record<string, any>[] = [];
96
+ let isFirstRow = true;
97
+ let hasYieldedData = false;
98
+
99
+ for await (const worksheet of workbookReader) {
100
+ for await (const row of worksheet) {
101
+ const values = Array.isArray(row.values) ? row.values.slice(1) : [];
102
+
103
+ if (isFirstRow) {
104
+ isFirstRow = false;
105
+
106
+ if (hasHeaderRow) {
107
+ headers = values.map(v => v?.toString().trim() || '');
108
+ continue;
109
+ } else if (providedHeaders.length) {
110
+ headers = providedHeaders;
111
+ } else {
112
+ headers = values.map((_, idx) => `${idx}`);
113
+ }
114
+ }
115
+
116
+ while (values.length < headers.length) values.push(null);
117
+ if (values.length > headers.length) values.length = headers.length;
118
+
119
+ const record = headers.reduce((acc, key, i) => {
120
+ acc[key] = values[i] ?? null;
121
+ return acc;
122
+ }, {} as Record<string, any>);
123
+
124
+ if (Object.values(record).every(v => v === null || v === '')) continue;
125
+
126
+ page.push(record);
127
+
128
+ if (page.length === pageSize) {
129
+ yield { headers, data: page };
130
+ hasYieldedData = true;
131
+ page = [];
132
+ }
133
+ }
134
+
135
+ // Optional: break if only processing first worksheet
136
+ // break;
137
+ }
138
+
139
+ if (page.length > 0) {
140
+ yield { headers, data: page };
141
+ hasYieldedData = true;
142
+ }
143
+
144
+ // ✅ Yield headers with empty data if only headers were found
145
+ if (!hasYieldedData && headers.length > 0) {
146
+ yield { headers, data: [] };
147
+ }
148
+ }
149
+
105
150
  }
@@ -18,35 +18,36 @@ import { ExportTransaction } from 'src/entities/export-transaction.entity';
18
18
  import { Readable } from 'stream';
19
19
  import { ExportTemplate } from '../entities/export-template.entity';
20
20
  import { ExportTransactionFileInfo, ExportTransactionService } from './export-transaction.service';
21
+ import { UpdateExportTemplateDto } from 'src/dtos/update-export-template.dto';
21
22
 
22
23
  @Injectable()
23
24
  export class ExportTemplateService extends CRUDService<ExportTemplate>{
24
- async startExportSync(id: number): Promise<ExportTransactionFileInfo> {
25
+ async startExportSync(updateDto: UpdateExportTemplateDto, filters:any): Promise<ExportTransactionFileInfo> {
25
26
  // Create the export transaction entry, with status 'started'
26
27
  const exportTransaction: CreateExportTransactionDto = await this.exportTransactionService.toDto({
27
28
  datetime: new Date(),
28
29
  status: 'started',
29
- exportTemplateId: id,
30
+ exportTemplateId: updateDto?.id ? updateDto?.id : null,
30
31
  });
31
32
  const exportTransactionEntity = await this.exportTransactionService.create(exportTransaction);
32
33
 
33
34
  // Trigger the export process
34
- const exportFileInfo = await this.exportTransactionService.triggerExportSync(exportTransactionEntity.id);
35
+ const exportFileInfo = await this.exportTransactionService.triggerExportSync(exportTransactionEntity.id, exportTransactionEntity, updateDto, filters);
35
36
  // It should return the export transaction id
36
37
  return exportFileInfo;
37
38
  }
38
39
 
39
- async startExportAsync(id: number): Promise<ExportTransaction>{
40
+ async startExportAsync(updateDto: UpdateExportTemplateDto, filters:any): Promise<ExportTransaction>{
40
41
  // Create the export transaction entry, with status 'started'
41
42
  const exportTransaction: CreateExportTransactionDto = await this.exportTransactionService.toDto({
42
43
  datetime: new Date(),
43
44
  status: 'started',
44
- exportTemplateId: id,
45
+ exportTemplateId: updateDto?.id ? updateDto?.id : null,
45
46
  });
46
47
  const exportTransactionEntity = await this.exportTransactionService.create(exportTransaction);
47
48
 
48
49
  // Trigger the export process
49
- this.exportTransactionService.triggerExportAsync(exportTransactionEntity.id);
50
+ this.exportTransactionService.triggerExportAsync(exportTransactionEntity.id, exportTransactionEntity, updateDto, filters);
50
51
 
51
52
  // It should return the export transaction id, so client can use this to check the status
52
53
  return exportTransactionEntity;
@@ -26,6 +26,8 @@ import { CsvService } from './csv.service';
26
26
  import { ExcelService } from './excel.service';
27
27
  import { getMediaStorageProvider } from './mediaStorageProviders';
28
28
  import { SolidIntrospectService } from './solid-introspect.service';
29
+ import { ModelMetadata } from 'src/entities/model-metadata.entity';
30
+ import { UpdateExportTemplateDto } from 'src/dtos/update-export-template.dto';
29
31
 
30
32
  const EXPORT_CHUNK_SIZE = 100;
31
33
  enum ExportStatus {
@@ -67,19 +69,30 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
67
69
  // readonly fieldMetadataService: FieldMetadataService,
68
70
  @InjectRepository(FieldMetadata, 'default')
69
71
  readonly fieldRepo: Repository<FieldMetadata>,
72
+ @InjectRepository(ModelMetadata, 'default')
73
+ readonly ModelMetadataRepo : Repository<ModelMetadata>,
70
74
  readonly moduleRef: ModuleRef
71
75
  ) {
72
76
  super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'exportTransaction', 'solid-core',moduleRef);
73
77
  }
74
78
 
75
79
  // Return the export stream
76
- async triggerExportSync(id: number): Promise<ExportTransactionFileInfo> {
80
+ async triggerExportSync(id: number, exportTransactionEntity: any, updateDto: UpdateExportTemplateDto , filters: any): Promise<ExportTransactionFileInfo> {
77
81
  try {
78
- const loadedExportTransaction = await this.loadExportTransaction(id);
79
- const { exportStream, templateName, uuid, exportTransaction } = await this.getExportStreamDetails(loadedExportTransaction);
82
+ // const loadedExportTransaction = await this.loadExportTransaction(id);
83
+ // from updateDto, get modelId and get modelMetadata
84
+ const modeldata = await this.ModelMetadataRepo.findOne({
85
+ where: { id: updateDto?.modelMetadataId},
86
+ relations: { fields: true},
87
+ })
88
+ const modelName = modeldata?.singularName;
89
+ const modelTemplateName = modelName;
90
+ const fields = JSON.parse(updateDto?.fields);
91
+ const templateFormat = updateDto?.templateFormat;
92
+ const { exportStream, templateName, uuid, exportTransaction } = await this.getExportStreamDetails(modelName, modelTemplateName, fields, modeldata, templateFormat, id, exportTransactionEntity, filters);
80
93
  this.updateExportTransaction(id, ExportStatus.COMPLETED);
81
- const fileName = this.getFileName(templateName, uuid, loadedExportTransaction.exportTemplate.templateFormat);
82
- const mimeType = this.getMimeType(loadedExportTransaction.exportTemplate.templateFormat);
94
+ const fileName = this.getFileName(templateName, uuid, templateFormat);
95
+ const mimeType = this.getMimeType(templateFormat);
83
96
  return { exportStream, fileName, mimeType, exportTransaction };
84
97
  } catch (error) {
85
98
  this.updateExportTransaction(id, ExportStatus.FAILED, error.message);
@@ -88,13 +101,22 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
88
101
  }
89
102
 
90
103
  // Store the export stream using the appropriate storage provider
91
- async triggerExportAsync(id: number): Promise<void> {
104
+ async triggerExportAsync(id: number, exportTransactionEntity: any, updateDto: UpdateExportTemplateDto, filters:any): Promise<void> {
92
105
  try {
93
- const loadedExportTransaction = await this.loadExportTransaction(id)
94
- const { exportStream, templateName, uuid, exportTransaction } = await this.getExportStreamDetails(loadedExportTransaction);
95
- const fileFormat = loadedExportTransaction.exportTemplate.templateFormat;
106
+ // const loadedExportTransaction = await this.loadExportTransaction(id)
107
+ // from updateDto, get modelId and get modelMetadata
108
+ const modeldata = await this.ModelMetadataRepo.findOne({
109
+ where: { id: updateDto?.modelMetadataId},
110
+ relations: { fields: true},
111
+ })
112
+ const modelName = modeldata?.singularName;
113
+ const modelTemplateName = modelName;
114
+ const fields = JSON.parse(updateDto?.fields);
115
+ const templateFormat = updateDto?.templateFormat;
116
+ const { exportStream, templateName, uuid, exportTransaction } = await this.getExportStreamDetails(modelName, modelTemplateName, fields, modeldata, templateFormat, id, exportTransactionEntity, filters);
117
+ // const fileFormat = loadedExportTransaction.exportTemplate.templateFormat;
96
118
  // Store the file using the appropriate storage provider
97
- await this.storeExportStream(exportStream, exportTransaction, this.getFileName(templateName, uuid, fileFormat));
119
+ await this.storeExportStream(exportStream, exportTransaction, this.getFileName(templateName, uuid, templateFormat));
98
120
  this.updateExportTransaction(id, ExportStatus.COMPLETED);
99
121
  } catch (error) {
100
122
  this.updateExportTransaction(id, ExportStatus.FAILED, error.message);
@@ -106,7 +128,7 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
106
128
  private async loadExportTransaction(id: number) {
107
129
  return await this.repo.findOne({
108
130
  where: { id: id },
109
- relations: { exportTemplate: { modelMetadata: true } },
131
+ relations: { exportTemplate: { modelMetadata: {fields: true} }},
110
132
  }
111
133
  );
112
134
  }
@@ -115,22 +137,26 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
115
137
  await this.repo.update(id, { status, error });
116
138
  }
117
139
 
118
- private async getExportStreamDetails(exportTransaction: ExportTransaction) {
140
+ private async getExportStreamDetails(modelName: string, templateName: string, fields:any, modelData:any, templateFormat:string, id:number, exportTransaction: any, filters: any) {
119
141
  // Get the columns which need to be exported & the model id
120
- const fields = JSON.parse(exportTransaction.exportTemplate.fields);
121
-
122
- // Get the appropriate service for the model by trying to fetch a model service matching a particular name
123
- const modelName = exportTransaction.exportTemplate.modelMetadata.singularName;
124
- const modelService = this.introspectService.getProvider(`${classify(modelName)}Service`);
125
- const templateName = exportTransaction.exportTemplate.templateName;
126
- const uuid = exportTransaction.exportTransactionId; //TODO can be renamed to exportTransactionUUID
142
+ // const fields = JSON.parse(exportTransaction.exportTemplate.fields);
127
143
 
144
+ // // Get the appropriate service for the model by trying to fetch a model service matching a particular name
145
+ // const modelName = exportTransaction.exportTemplate.modelMetadata.singularName;
146
+ const modelService = this.introspectService.getProvider(`${classify(modelName)}Service`);
147
+ // const templateName = exportTransaction.exportTemplate.templateName;
148
+ const uuid = String(id); //TODO can be renamed to exportTransactionUUID
149
+ // const modelData = exportTransaction.exportTemplate.modelMetadata;
128
150
 
129
151
  // Get the data records function
130
- const dataRecordsFunc = await this.getDataRecordsFunc(fields, modelService);
152
+ //const dataRecordsFunc = await this.getDataRecordsFunc(fields, modelService,modelData, filters);
153
+ const dataRecordsFunc = await this.getDataRecordsFunc(fields, modelService, modelData, filters);
131
154
 
132
155
  // Get the export passthru stream (since it is a passthru stream, nothing is stored in memory & it is streamed directly when the stream is read)
133
- let exportStream = await this.getExportStream(exportTransaction.exportTemplate.templateFormat, dataRecordsFunc);
156
+ // let exportStream = await this.getExportStream(exportTransaction.exportTemplate.templateFormat, dataRecordsFunc);
157
+ // return { exportStream, templateName, uuid, exportTransaction };
158
+
159
+ let exportStream = await this.getExportStream(templateFormat, dataRecordsFunc);
134
160
  return { exportStream, templateName, uuid, exportTransaction };
135
161
  }
136
162
 
@@ -181,20 +207,78 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
181
207
  return (fileFormat === ExportFormat.EXCEL) ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'text/csv';
182
208
  }
183
209
 
184
- private async getDataRecordsFunc(fields: any, modelService: InstanceWrapper<any>): Promise<(chunkIndex: number, chunkSize: number) => Promise<any[]>> {
210
+ private async getDataRecordsFunc(fields: any, modelService: InstanceWrapper<any>, modelMetadata: any, filters:any): Promise<(chunkIndex: number, chunkSize: number) => Promise<any[]>> {
185
211
  // Return a function which will take the chunkIndex & chunkSize and return the data
212
+ // Get the relation fields to populate
213
+ const relatedFieldNames = modelMetadata?.fields
214
+ .filter((field: { relationType: any; }) => field.relationType !== null)
215
+ .map((field: { name: any; }) => field.name);
216
+
217
+ //Get the model metadata of relation field with userKey details
218
+ const relatedModelsUserKeyMap = new Map<string, string>();
219
+ for (const field of modelMetadata?.fields || []) {
220
+ if (field.relationType && field.relationCoModelSingularName && fields.includes(field.name)) {
221
+ const relatedModelMetadata = await this.ModelMetadataRepo.findOne({
222
+ where: { singularName: field.relationCoModelSingularName },
223
+ relations: ['userKeyField'],
224
+ });
225
+
226
+ if (relatedModelMetadata?.userKeyField?.name) {
227
+ relatedModelsUserKeyMap.set(field.name, relatedModelMetadata.userKeyField.name);
228
+ }
229
+ }
230
+ }
231
+
186
232
  return async (chunkIndex: number, chunkSize: number) => {
187
233
  const offset = chunkIndex * chunkSize;
188
234
  const recordFilterDto: BasicFilterDto = {
189
- fields,
190
235
  limit: chunkSize,
191
236
  offset,
237
+ populate: relatedFieldNames
192
238
  };
239
+ const cleanedFilters = cleanNullsFromObject(filters);
240
+
241
+ if (cleanedFilters && Object.keys(cleanedFilters).length > 0) {
242
+ recordFilterDto.filters = cleanedFilters;
243
+ }
244
+
245
+ //Get the non relation fields which are in fields array passed to this function
246
+ const nonRelationalFieldSet = new Set(
247
+ modelMetadata?.fields
248
+ .filter((field: { name: any; relationType: any; }) => fields.includes(field.name) && field.relationType === null)
249
+ .map((field: { name: any; }) => field.name)
250
+ );
193
251
  const data = await modelService.instance.find(recordFilterDto);
194
252
  const records = data.records ?? [];
195
- return records;
196
- }
253
+ const cleanedRecords = records.map((record: Record<string, any>) => {
254
+ const newRecord: Record<string, any> = {};
255
+
256
+ // Include non-relational fields
257
+ for (const key of nonRelationalFieldSet as Set<string>) {
258
+ newRecord[key] = record[key];
259
+ }
260
+
261
+ // Include userKey from each related field
262
+ for (const [relatedFieldName, userKeyFieldName] of relatedModelsUserKeyMap.entries()) {
263
+ const relatedData = record[relatedFieldName];
264
+
265
+ if (Array.isArray(relatedData)) {
266
+ // For many-to-many or one-to-many
267
+ const values = relatedData.map(item => item?.[userKeyFieldName]).filter(Boolean);
268
+ newRecord[relatedFieldName] = values.join(', ');
269
+ } else if (relatedData && typeof relatedData === 'object') {
270
+ // For many-to-one or one-to-one
271
+ newRecord[relatedFieldName] = relatedData?.[userKeyFieldName] ?? null;
272
+ } else {
273
+ newRecord[relatedFieldName] = null;
274
+ }
275
+ }
276
+
277
+ return newRecord;
278
+ });
279
+ return cleanedRecords
197
280
  }
281
+ }
198
282
 
199
283
  async toDto(data: Partial<CreateExportTransactionDto>): Promise<CreateExportTransactionDto> {
200
284
  const dto = new CreateExportTransactionDto(data);
@@ -206,3 +290,30 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
206
290
  return dto;
207
291
  }
208
292
  }
293
+
294
+ function cleanNullsFromObject(obj: any): any {
295
+ if (Array.isArray(obj)) {
296
+ return obj
297
+ .filter(item => item !== null && item !== undefined)
298
+ .map(cleanNullsFromObject);
299
+ } else if (typeof obj === 'object' && obj !== null) {
300
+ const newObj: any = {};
301
+ for (const key in obj) {
302
+ const value = obj[key];
303
+ if (value !== null && value !== undefined) {
304
+ const cleanedValue = cleanNullsFromObject(value);
305
+ // Only assign non-empty objects/arrays or non-null primitives
306
+ if (
307
+ (typeof cleanedValue === 'object' && Object.keys(cleanedValue).length > 0) ||
308
+ (Array.isArray(cleanedValue) && cleanedValue.length > 0) ||
309
+ typeof cleanedValue !== 'object'
310
+ ) {
311
+ newObj[key] = cleanedValue;
312
+ }
313
+ }
314
+ }
315
+ return newObj;
316
+ }
317
+ return obj;
318
+ }
319
+
@@ -3,4 +3,8 @@
3
3
  2. queues handling -> if queues is enabled by default, i.e triggerExport(exportTransactionEntity.id).
4
4
  - startExport should either return the data or return the transaction id
5
5
  3. How to handle scenarios wherein, nested related exist.(do i need to only get the userkey)
6
- - show the userKey
6
+ - show the userKey
7
+ 4. Exclude the media fields from the fields shown in the template creation
8
+ 5. Allow provision to export the data without creating a template
9
+ 6. Also for template creation i.e do we want the option to keep the templates at a user level instead of at the application level.
10
+ 7. Do we want to save the filter against the template.
@@ -0,0 +1,34 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
3
+ import { DiscoveryService, ModuleRef } from "@nestjs/core";
4
+ import { EntityManager, Repository } from 'typeorm';
5
+
6
+ import { CRUDService } from 'src/services/crud.service';
7
+ import { ModelMetadataService } from 'src/services/model-metadata.service';
8
+ import { ModuleMetadataService } from 'src/services/module-metadata.service';
9
+ import { ConfigService } from '@nestjs/config';
10
+ import { FileService } from 'src/services/file.service';
11
+ import { CrudHelperService } from 'src/services/crud-helper.service';
12
+
13
+
14
+ import { ImportTransactionErrorLog } from '../entities/import-transaction-error-log.entity';
15
+
16
+ @Injectable()
17
+ export class ImportTransactionErrorLogService extends CRUDService<ImportTransactionErrorLog>{
18
+ constructor(
19
+ readonly modelMetadataService: ModelMetadataService,
20
+ readonly moduleMetadataService: ModuleMetadataService,
21
+ readonly configService: ConfigService,
22
+ readonly fileService: FileService,
23
+ readonly discoveryService: DiscoveryService,
24
+ readonly crudHelperService: CrudHelperService,
25
+ @InjectEntityManager()
26
+ readonly entityManager: EntityManager,
27
+ @InjectRepository(ImportTransactionErrorLog, 'default')
28
+ readonly repo: Repository<ImportTransactionErrorLog>,
29
+ readonly moduleRef: ModuleRef
30
+
31
+ ) {
32
+ super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService,entityManager, repo, 'importTransactionErrorLog', 'solid-core', moduleRef);
33
+ }
34
+ }