@solidstarters/solid-core 1.2.92 → 1.2.95
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/# Password field.md +8 -0
- package/dist/controllers/import-transaction.controller.d.ts +2 -1
- package/dist/controllers/import-transaction.controller.d.ts.map +1 -1
- package/dist/controllers/import-transaction.controller.js +16 -4
- package/dist/controllers/import-transaction.controller.js.map +1 -1
- package/dist/controllers/locale.controller.d.ts +41 -0
- package/dist/controllers/locale.controller.d.ts.map +1 -0
- package/dist/controllers/locale.controller.js +179 -0
- package/dist/controllers/locale.controller.js.map +1 -0
- package/dist/controllers/view-metadata.controller.d.ts +1 -0
- package/dist/controllers/view-metadata.controller.d.ts.map +1 -1
- package/dist/dtos/basic-filters.dto.d.ts +2 -0
- package/dist/dtos/basic-filters.dto.d.ts.map +1 -1
- package/dist/dtos/basic-filters.dto.js +11 -1
- package/dist/dtos/basic-filters.dto.js.map +1 -1
- package/dist/dtos/create-locale.dto.d.ts +6 -0
- package/dist/dtos/create-locale.dto.d.ts.map +1 -0
- package/dist/dtos/create-locale.dto.js +43 -0
- package/dist/dtos/create-locale.dto.js.map +1 -0
- package/dist/dtos/create-menu-item-metadata.dto.d.ts +2 -1
- package/dist/dtos/create-menu-item-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/create-menu-item-metadata.dto.js +22 -3
- package/dist/dtos/create-menu-item-metadata.dto.js.map +1 -1
- package/dist/dtos/create-model-metadata.dto.d.ts +1 -0
- package/dist/dtos/create-model-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/create-model-metadata.dto.js +7 -2
- package/dist/dtos/create-model-metadata.dto.js.map +1 -1
- package/dist/dtos/create-role-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/create-role-metadata.dto.js +11 -0
- package/dist/dtos/create-role-metadata.dto.js.map +1 -1
- package/dist/dtos/update-locale.dto.d.ts +7 -0
- package/dist/dtos/update-locale.dto.d.ts.map +1 -0
- package/dist/dtos/update-locale.dto.js +48 -0
- package/dist/dtos/update-locale.dto.js.map +1 -0
- package/dist/dtos/update-menu-item-metadata.dto.d.ts +2 -1
- package/dist/dtos/update-menu-item-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/update-menu-item-metadata.dto.js +24 -5
- package/dist/dtos/update-menu-item-metadata.dto.js.map +1 -1
- package/dist/dtos/update-role-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/update-role-metadata.dto.js +12 -1
- package/dist/dtos/update-role-metadata.dto.js.map +1 -1
- package/dist/entities/common.entity.d.ts +3 -0
- package/dist/entities/common.entity.d.ts.map +1 -1
- package/dist/entities/common.entity.js +13 -1
- package/dist/entities/common.entity.js.map +1 -1
- package/dist/entities/locale.entity.d.ts +7 -0
- package/dist/entities/locale.entity.d.ts.map +1 -0
- package/dist/entities/locale.entity.js +43 -0
- package/dist/entities/locale.entity.js.map +1 -0
- package/dist/entities/menu-item-metadata.entity.d.ts +2 -1
- package/dist/entities/menu-item-metadata.entity.d.ts.map +1 -1
- package/dist/entities/menu-item-metadata.entity.js +13 -8
- package/dist/entities/menu-item-metadata.entity.js.map +1 -1
- package/dist/entities/model-metadata.entity.d.ts +1 -0
- package/dist/entities/model-metadata.entity.d.ts.map +1 -1
- package/dist/entities/model-metadata.entity.js +5 -1
- package/dist/entities/model-metadata.entity.js.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js.map +1 -1
- package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js.map +1 -1
- package/dist/helpers/module-metadata-helper.service.d.ts.map +1 -1
- package/dist/helpers/module-metadata-helper.service.js.map +1 -1
- package/dist/helpers/solid-registry.d.ts +2 -0
- package/dist/helpers/solid-registry.d.ts.map +1 -1
- package/dist/helpers/solid-registry.js +5 -0
- package/dist/helpers/solid-registry.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +234 -0
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +8 -3
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/crud-helper.service.d.ts +1 -1
- package/dist/services/crud-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +17 -2
- package/dist/services/crud-helper.service.js.map +1 -1
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +19 -6
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/import-transaction.service.d.ts +19 -6
- package/dist/services/import-transaction.service.d.ts.map +1 -1
- package/dist/services/import-transaction.service.js +214 -87
- package/dist/services/import-transaction.service.js.map +1 -1
- package/dist/services/locale.service.d.ts +26 -0
- package/dist/services/locale.service.d.ts.map +1 -0
- package/dist/services/locale.service.js +64 -0
- package/dist/services/locale.service.js.map +1 -0
- package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
- package/dist/services/menu-item-metadata.service.d.ts.map +1 -1
- package/dist/services/menu-item-metadata.service.js +1 -0
- package/dist/services/menu-item-metadata.service.js.map +1 -1
- package/dist/services/selection-providers/locale-list-selection-provider.service.d.ts +9 -0
- package/dist/services/selection-providers/locale-list-selection-provider.service.d.ts.map +1 -0
- package/dist/services/selection-providers/locale-list-selection-provider.service.js +87 -0
- package/dist/services/selection-providers/locale-list-selection-provider.service.js.map +1 -0
- package/dist/services/setting.service.d.ts.map +1 -1
- package/dist/services/setting.service.js +4 -2
- package/dist/services/setting.service.js.map +1 -1
- package/dist/services/view-metadata.service.d.ts +3 -0
- package/dist/services/view-metadata.service.d.ts.map +1 -1
- package/dist/services/view-metadata.service.js +73 -7
- package/dist/services/view-metadata.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +8 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/model.subscriber.d.ts.map +1 -1
- package/dist/subscribers/model.subscriber.js +24 -0
- package/dist/subscribers/model.subscriber.js.map +1 -1
- package/dist/transformers/datetime-transformer.d.ts +4 -0
- package/dist/transformers/datetime-transformer.d.ts.map +1 -0
- package/dist/transformers/datetime-transformer.js +11 -0
- package/dist/transformers/datetime-transformer.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/src/controllers/import-transaction.controller.ts +9 -3
- package/src/controllers/locale.controller.ts +94 -0
- package/src/dtos/basic-filters.dto.ts +13 -1
- package/src/dtos/create-locale.dto.ts +17 -0
- package/src/dtos/create-menu-item-metadata.dto.ts +21 -13
- package/src/dtos/create-model-metadata.dto.ts +5 -1
- package/src/dtos/create-role-metadata.dto.ts +48 -45
- package/src/dtos/update-locale.dto.ts +23 -0
- package/src/dtos/update-menu-item-metadata.dto.ts +23 -16
- package/src/dtos/update-role-metadata.dto.ts +49 -47
- package/src/entities/common.entity.ts +11 -1
- package/src/entities/locale.entity.ts +14 -0
- package/src/entities/menu-item-metadata.entity.ts +14 -14
- package/src/entities/model-metadata.entity.ts +3 -0
- package/src/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.ts +2 -1
- package/src/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.ts +1 -0
- package/src/helpers/module-metadata-helper.service.ts +0 -1
- package/src/helpers/solid-registry.ts +11 -2
- package/src/index.ts +6 -3
- package/src/seeders/seed-data/email-templates/forgot-password.handlebars.html +134 -154
- package/src/seeders/seed-data/email-templates/on-force-password-change.handlebars.html +141 -195
- package/src/seeders/seed-data/email-templates/otp-on-login.handlebars.html +130 -144
- package/src/seeders/seed-data/email-templates/otp-on-register.handlebars.html +131 -145
- package/src/seeders/seed-data/solid-core-metadata.json +234 -0
- package/src/services/authentication.service.ts +8 -3
- package/src/services/crud-helper.service.ts +30 -12
- package/src/services/crud.service.ts +51 -35
- package/src/services/import-transaction.service.ts +313 -121
- package/src/services/locale.service.ts +37 -0
- package/src/services/mediaStorageProviders/file-storage-provider.ts +1 -1
- package/src/services/menu-item-metadata.service.ts +2 -0
- package/src/services/pending_import_issues +3 -0
- package/src/services/selection-providers/locale-list-selection-provider.service.ts +58 -0
- package/src/services/setting.service.ts +4 -2
- package/src/services/view-metadata.service.ts +179 -14
- package/src/solid-core.module.ts +8 -0
- package/src/subscribers/model.subscriber.ts +24 -0
- package/src/transformers/datetime-transformer.ts +12 -0
|
@@ -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
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 (!
|
|
87
|
-
throw new Error(`
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
225
|
-
|
|
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
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
import { ModelMetadata } from 'src/entities/model-metadata.entity';
|
|
13
|
+
import { RequestContextService } from './request-context.service';
|
|
14
|
+
import { Locale } from 'src/entities/locale.entity';
|
|
15
|
+
@Injectable()
|
|
16
|
+
export class LocaleService extends CRUDService<Locale>{
|
|
17
|
+
constructor(
|
|
18
|
+
readonly modelMetadataService: ModelMetadataService,
|
|
19
|
+
readonly moduleMetadataService: ModuleMetadataService,
|
|
20
|
+
readonly configService: ConfigService,
|
|
21
|
+
readonly fileService: FileService,
|
|
22
|
+
readonly discoveryService: DiscoveryService,
|
|
23
|
+
readonly crudHelperService: CrudHelperService,
|
|
24
|
+
@InjectEntityManager()
|
|
25
|
+
readonly entityManager: EntityManager,
|
|
26
|
+
@InjectRepository(Locale, 'default')
|
|
27
|
+
readonly repo: Repository<Locale>,
|
|
28
|
+
@InjectRepository(Locale, 'default')
|
|
29
|
+
readonly moduleRef: ModuleRef,
|
|
30
|
+
@InjectRepository(ModelMetadata)
|
|
31
|
+
private readonly modelMetadataRepo: Repository<ModelMetadata>,
|
|
32
|
+
readonly requestContextService: RequestContextService
|
|
33
|
+
) {
|
|
34
|
+
super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService,entityManager, repo, 'locale', 'solid-core', moduleRef);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
@@ -45,7 +45,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
45
45
|
await this.fileService.deleteFile(file.path);
|
|
46
46
|
|
|
47
47
|
// Create an entry in the media table
|
|
48
|
-
|
|
48
|
+
const mediaEntity = await this.mediaRepository.createMedia({
|
|
49
49
|
entityId: entity.id,
|
|
50
50
|
modelMetadataId: mediaFieldMetadata.model.id,
|
|
51
51
|
relativeUri: this.getFileName(file),
|
|
@@ -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,58 @@
|
|
|
1
|
+
import { Injectable } from "@nestjs/common";
|
|
2
|
+
import { ModelMetadataService } from "src/services/model-metadata.service";
|
|
3
|
+
import { SelectionProvider } from "src/decorators/selection-provider.decorator";
|
|
4
|
+
import { ISelectionProvider, ISelectionProviderContext, ISelectionProviderValues } from "../../interfaces";
|
|
5
|
+
// import localeCodes from 'locale-codes';
|
|
6
|
+
import * as locale from 'locale-codes'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@SelectionProvider()
|
|
10
|
+
@Injectable()
|
|
11
|
+
export class LocaleListSelectionProvider implements ISelectionProvider<ISelectionProviderContext> {
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
help(): string {
|
|
17
|
+
return "# Gets all locales available to the user";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
name(): string {
|
|
21
|
+
return 'LocaleListSelectionProvider';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async value(optionValue: string, ctxt: ISelectionProviderContext): Promise<ISelectionProviderValues | any> {
|
|
25
|
+
const locales = locale.all
|
|
26
|
+
.filter(code => code.tag === optionValue)
|
|
27
|
+
.map(code => ({ label: `${code.name} (${code.tag})`, value: code.tag }));
|
|
28
|
+
|
|
29
|
+
return locales.length > 0 ? locales[0] : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async values(query: string, ctxt: ISelectionProviderContext): Promise<readonly ISelectionProviderValues[]> {
|
|
33
|
+
|
|
34
|
+
const locales = locale.all
|
|
35
|
+
.filter(code => {
|
|
36
|
+
// Look at the documentation here - https://www.npmjs.com/package/locale-codes
|
|
37
|
+
// In this if you check the section - https://www.npmjs.com/package/locale-codes#locale-list
|
|
38
|
+
|
|
39
|
+
// We disabled this because this was not returning the basic locale codes like 'en', 'fr', etc.
|
|
40
|
+
// We are only interested in the locales that have a tag with a hyphen (-) in it.
|
|
41
|
+
// if (code.tag.includes('-')) {
|
|
42
|
+
if (query) {
|
|
43
|
+
const lowerCaseQuery = query.toLowerCase();
|
|
44
|
+
return code.name.toLowerCase().includes(lowerCaseQuery) || code.tag.toLowerCase().includes(lowerCaseQuery);
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
// }
|
|
48
|
+
return false;
|
|
49
|
+
})
|
|
50
|
+
.map(code => ({
|
|
51
|
+
label: `${code.name} (${code.tag})`,
|
|
52
|
+
value: code.tag,
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
return locales;
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -59,7 +59,8 @@ export class SettingService extends CRUDService<Setting> {
|
|
|
59
59
|
defaultRole: this.iamConfiguration.defaultRole,
|
|
60
60
|
shouldQueueEmails: this.commonConfiguration.shouldQueueEmails,
|
|
61
61
|
shouldQueueSms: this.commonConfiguration.shouldQueueSms,
|
|
62
|
-
enableDarkMode: true
|
|
62
|
+
enableDarkMode: true,
|
|
63
|
+
copyright : ""
|
|
63
64
|
};
|
|
64
65
|
|
|
65
66
|
const existingSettings = await this.repo.find();
|
|
@@ -140,7 +141,8 @@ export class SettingService extends CRUDService<Setting> {
|
|
|
140
141
|
defaultRole: this.iamConfiguration.defaultRole,
|
|
141
142
|
shouldQueueEmails: this.commonConfiguration.shouldQueueEmails,
|
|
142
143
|
shouldQueueSms: this.commonConfiguration.shouldQueueSms,
|
|
143
|
-
enableDarkMode: true
|
|
144
|
+
enableDarkMode: true,
|
|
145
|
+
copyright : ""
|
|
144
146
|
};
|
|
145
147
|
}
|
|
146
148
|
|