@solidstarters/solid-core 1.2.88 → 1.2.89

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 (27) 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/dtos/export.dto.d.ts +5 -0
  6. package/dist/dtos/export.dto.d.ts.map +1 -0
  7. package/dist/dtos/export.dto.js +12 -0
  8. package/dist/dtos/export.dto.js.map +1 -0
  9. package/dist/entities/export-transaction.entity.js +1 -1
  10. package/dist/entities/export-transaction.entity.js.map +1 -1
  11. package/dist/seeders/seed-data/solid-core-metadata.json +1 -1
  12. package/dist/services/export-template.service.d.ts +3 -2
  13. package/dist/services/export-template.service.d.ts.map +1 -1
  14. package/dist/services/export-template.service.js +6 -6
  15. package/dist/services/export-template.service.js.map +1 -1
  16. package/dist/services/export-transaction.service.d.ts +6 -3
  17. package/dist/services/export-transaction.service.d.ts.map +1 -1
  18. package/dist/services/export-transaction.service.js +101 -22
  19. package/dist/services/export-transaction.service.js.map +1 -1
  20. package/dist/tsconfig.tsbuildinfo +1 -1
  21. package/package.json +1 -1
  22. package/src/controllers/export-template.controller.ts +20 -7
  23. package/src/dtos/export.dto.ts +5 -0
  24. package/src/entities/export-transaction.entity.ts +1 -1
  25. package/src/seeders/seed-data/solid-core-metadata.json +1 -1
  26. package/src/services/export-template.service.ts +7 -6
  27. package/src/services/export-transaction.service.ts +136 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidstarters/solid-core",
3
- "version": "1.2.88",
3
+ "version": "1.2.89",
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",
@@ -5,6 +5,7 @@ import { ExportTemplateService } from '../services/export-template.service';
5
5
  import { CreateExportTemplateDto } from '../dtos/create-export-template.dto';
6
6
  import { UpdateExportTemplateDto } from '../dtos/update-export-template.dto';
7
7
  import { Response } from 'express';
8
+ import { StartExportSyncDto } from 'src/dtos/export.dto';
8
9
 
9
10
  @ApiTags('Solid')
10
11
  @Controller('export-template') //FIXME: Change this to the model plural name
@@ -74,13 +75,19 @@ export class ExportTemplateController {
74
75
  }
75
76
 
76
77
  @ApiBearerAuth("jwt")
77
- @Post(':id/startExport/sync')
78
- async startExportSync(@Param('id') id: number, @Res() res: Response) {
79
- const exportFileInfo = await this.service.startExportSync(+id);
78
+ @Post('/startExport/sync')
79
+ async startExportSync(@Body() dto: StartExportSyncDto, @Res() res: Response) {
80
+ const { filters, ...rest } = dto;
81
+ let updateDto = { ...rest };
82
+ // Check if templateName is present → create template
83
+ if (updateDto?.templateName) {
84
+ const newTemplate = await this.service.create(updateDto, []);
85
+ updateDto = { ...updateDto, id: newTemplate.id };
86
+ }
87
+ const exportFileInfo = await this.service.startExportSync(updateDto, filters);
80
88
  if (exportFileInfo.exportStream === null) {
81
89
  throw new InternalServerErrorException("Export stream is null");
82
90
  }
83
-
84
91
  // ✅ Set response headers for streaming
85
92
  res.setHeader('Content-Disposition', `attachment; filename="${exportFileInfo.fileName}"`);
86
93
  res.setHeader('Content-Type', exportFileInfo.mimeType);
@@ -90,9 +97,15 @@ export class ExportTemplateController {
90
97
  }
91
98
 
92
99
  @ApiBearerAuth("jwt")
93
- @Post(':id/startExport/async')
94
- async startExportAsync(@Param('id') id: number) {
95
- return this.service.startExportAsync(+id);
100
+ @Post('/startExport/async')
101
+ async startExportAsync(@Body() dto: StartExportSyncDto) {
102
+ const { filters, ...rest } = dto;
103
+ let updateDto = { ...rest };
104
+ if (updateDto.templateName) {
105
+ const newTemplate = await this.service.create(updateDto, []);
106
+ updateDto = { ...updateDto, id: newTemplate.id };
107
+ }
108
+ return this.service.startExportAsync(updateDto, filters);
96
109
  }
97
110
 
98
111
  }
@@ -0,0 +1,5 @@
1
+ import { UpdateExportTemplateDto } from "./update-export-template.dto";
2
+
3
+ export class StartExportSyncDto extends UpdateExportTemplateDto {
4
+ filters?: any;
5
+ }
@@ -17,6 +17,6 @@ export class ExportTransaction extends CommonEntity {
17
17
  @Column({ type: "text", nullable: true })
18
18
  error: string;
19
19
  @Index()
20
- @ManyToOne(() => ExportTemplate, { onDelete: "CASCADE", nullable: false })
20
+ @ManyToOne(() => ExportTemplate, { onDelete: "CASCADE", nullable: true })
21
21
  exportTemplate: ExportTemplate;
22
22
  }
@@ -3564,7 +3564,7 @@
3564
3564
  "displayName": "Related Export Template",
3565
3565
  "type": "relation",
3566
3566
  "ormType": "int",
3567
- "required": true,
3567
+ "required": false,
3568
3568
  "unique": false,
3569
3569
  "index": true,
3570
3570
  "private": false,
@@ -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
+