@solidstarters/solid-core 1.2.137 → 1.2.139
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dtos/create-dashboard-question.dto.d.ts.map +1 -1
- package/dist/dtos/create-dashboard-question.dto.js.map +1 -1
- package/dist/dtos/create-dashboard.dto.d.ts +2 -0
- package/dist/dtos/create-dashboard.dto.d.ts.map +1 -1
- package/dist/dtos/create-dashboard.dto.js +13 -1
- package/dist/dtos/create-dashboard.dto.js.map +1 -1
- package/dist/dtos/update-dashboard-question.dto.d.ts.map +1 -1
- package/dist/dtos/update-dashboard-question.dto.js.map +1 -1
- package/dist/dtos/update-dashboard.dto.d.ts +2 -0
- package/dist/dtos/update-dashboard.dto.d.ts.map +1 -1
- package/dist/dtos/update-dashboard.dto.js +13 -1
- package/dist/dtos/update-dashboard.dto.js.map +1 -1
- package/dist/entities/dashboard-question.entity.d.ts.map +1 -1
- package/dist/entities/dashboard-question.entity.js.map +1 -1
- package/dist/entities/dashboard.entity.d.ts +2 -0
- package/dist/entities/dashboard.entity.d.ts.map +1 -1
- package/dist/entities/dashboard.entity.js +9 -1
- package/dist/entities/dashboard.entity.js.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.d.ts +1 -1
- package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.js +4 -4
- package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.js.map +1 -1
- package/dist/helpers/module.helper.d.ts.map +1 -1
- package/dist/helpers/module.helper.js +9 -2
- package/dist/helpers/module.helper.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +45 -0
- package/dist/services/crud.service.d.ts +1 -0
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +12 -4
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/dashboard.service.d.ts.map +1 -1
- package/dist/services/dashboard.service.js +7 -2
- package/dist/services/dashboard.service.js.map +1 -1
- package/dist/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.d.ts +4 -1
- package/dist/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.d.ts.map +1 -1
- package/dist/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.js +4 -2
- package/dist/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.js.map +1 -1
- package/dist/services/sql-expression-resolver.service.d.ts +3 -0
- package/dist/services/sql-expression-resolver.service.d.ts.map +1 -1
- package/dist/services/sql-expression-resolver.service.js +18 -3
- package/dist/services/sql-expression-resolver.service.js.map +1 -1
- package/dist/subscribers/dashboard.subscriber.d.ts +1 -0
- package/dist/subscribers/dashboard.subscriber.d.ts.map +1 -1
- package/dist/subscribers/dashboard.subscriber.js +17 -2
- package/dist/subscribers/dashboard.subscriber.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/dtos/create-dashboard-question.dto.ts +4 -5
- package/src/dtos/create-dashboard.dto.ts +8 -0
- package/src/dtos/update-dashboard-question.dto.ts +4 -5
- package/src/dtos/update-dashboard.dto.ts +8 -0
- package/src/entities/dashboard-question.entity.ts +2 -3
- package/src/entities/dashboard.entity.ts +4 -0
- package/src/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.ts +6 -5
- package/src/helpers/module.helper.ts +33 -20
- package/src/seeders/seed-data/solid-core-metadata.json +45 -0
- package/src/services/crud.service.ts +14 -4
- package/src/services/dashboard.service.ts +7 -2
- package/src/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.ts +5 -2
- package/src/services/sql-expression-resolver.service.ts +16 -2
- package/src/subscribers/dashboard.subscriber.ts +24 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.139",
|
|
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",
|
|
@@ -55,9 +55,8 @@ export class CreateDashboardQuestionDto {
|
|
|
55
55
|
@IsString()
|
|
56
56
|
@ApiProperty({ description: "This is the SQL query to fetch the KPI value for the question" })
|
|
57
57
|
kpiSql: string;
|
|
58
|
-
|
|
59
|
-
@
|
|
60
|
-
@
|
|
61
|
-
|
|
62
|
-
sequenceNumber: number;
|
|
58
|
+
@IsOptional()
|
|
59
|
+
@IsInt()
|
|
60
|
+
@ApiProperty()
|
|
61
|
+
sequenceNumber: number;
|
|
63
62
|
}
|
|
@@ -50,4 +50,12 @@ export class CreateDashboardDto {
|
|
|
50
50
|
@IsOptional()
|
|
51
51
|
@ApiProperty()
|
|
52
52
|
moduleUserKey: string;
|
|
53
|
+
@IsOptional()
|
|
54
|
+
@IsString()
|
|
55
|
+
@ApiProperty()
|
|
56
|
+
displayName: string;
|
|
57
|
+
@IsOptional()
|
|
58
|
+
@IsString()
|
|
59
|
+
@ApiProperty({ description: "This is a description of the dashboard configuration, providing context and details about the dashboard." })
|
|
60
|
+
description: string;
|
|
53
61
|
}
|
|
@@ -60,9 +60,8 @@ export class UpdateDashboardQuestionDto {
|
|
|
60
60
|
@IsString()
|
|
61
61
|
@ApiProperty({ description: "This is the SQL query to fetch the KPI value for the question" })
|
|
62
62
|
kpiSql: string;
|
|
63
|
-
|
|
64
|
-
@
|
|
65
|
-
@
|
|
66
|
-
|
|
67
|
-
sequenceNumber: number;
|
|
63
|
+
@IsOptional()
|
|
64
|
+
@IsInt()
|
|
65
|
+
@ApiProperty()
|
|
66
|
+
sequenceNumber: number;
|
|
68
67
|
}
|
|
@@ -54,4 +54,12 @@ export class UpdateDashboardDto {
|
|
|
54
54
|
@IsOptional()
|
|
55
55
|
@ApiProperty()
|
|
56
56
|
moduleUserKey: string;
|
|
57
|
+
@IsOptional()
|
|
58
|
+
@IsString()
|
|
59
|
+
@ApiProperty()
|
|
60
|
+
displayName: string;
|
|
61
|
+
@IsOptional()
|
|
62
|
+
@IsString()
|
|
63
|
+
@ApiProperty({ description: "This is a description of the dashboard configuration, providing context and details about the dashboard." })
|
|
64
|
+
description: string;
|
|
57
65
|
}
|
|
@@ -27,7 +27,6 @@ export class DashboardQuestion extends CommonEntity {
|
|
|
27
27
|
labelSql: string;
|
|
28
28
|
@Column({ type: "text", nullable: true })
|
|
29
29
|
kpiSql: string;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
sequenceNumber: number;
|
|
30
|
+
@Column({ type: "integer", nullable: true })
|
|
31
|
+
sequenceNumber: number;
|
|
33
32
|
}
|
|
@@ -18,4 +18,8 @@ export class Dashboard extends CommonEntity {
|
|
|
18
18
|
@ManyToOne(() => ModuleMetadata, { onDelete: "CASCADE", nullable: false })
|
|
19
19
|
@JoinColumn()
|
|
20
20
|
module: ModuleMetadata;
|
|
21
|
+
@Column({ type: "varchar", nullable: true })
|
|
22
|
+
displayName: string;
|
|
23
|
+
@Column({ type: "text", nullable: true })
|
|
24
|
+
description: string;
|
|
21
25
|
}
|
|
@@ -9,9 +9,10 @@ export interface ManyToOneRelationFieldOptions {
|
|
|
9
9
|
required: boolean | undefined | null;
|
|
10
10
|
relationCoModelSingularName: string | undefined | null;
|
|
11
11
|
fieldName: string | undefined | null;
|
|
12
|
-
modelUserKeyFieldName: string | undefined | null;
|
|
12
|
+
// modelUserKeyFieldName: string | undefined | null;
|
|
13
13
|
modelSingularName: string | undefined | null;
|
|
14
14
|
entityManager: EntityManager;
|
|
15
|
+
relationCoModelUserKeyFieldName: string | undefined | null;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
// This implementation is meant to be used for many-to-one relation field
|
|
@@ -50,8 +51,8 @@ export class ManyToOneRelationFieldCrudManager implements FieldCrudManager {
|
|
|
50
51
|
private applyUserKeyFormatValidations(fieldUserKey: string): ValidationError[] {
|
|
51
52
|
const errors: ValidationError[] = [];
|
|
52
53
|
!isString(fieldUserKey) ? errors.push({ field: this.options.fieldName, error: 'Field is not a string' }) : "no errors";
|
|
53
|
-
if (isEmpty(this.options.
|
|
54
|
-
errors.push({ field: this.options.fieldName, error: `UserKey field name is not defined in the model ${this.options.
|
|
54
|
+
if (isEmpty(this.options.relationCoModelUserKeyFieldName)) {
|
|
55
|
+
errors.push({ field: this.options.fieldName, error: `UserKey field name is not defined in the model ${this.options.relationCoModelSingularName}` });
|
|
55
56
|
}
|
|
56
57
|
return errors;
|
|
57
58
|
}
|
|
@@ -72,9 +73,9 @@ export class ManyToOneRelationFieldCrudManager implements FieldCrudManager {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
else {
|
|
75
|
-
dto[this.options.fieldName] = await this.options.entityManager.getRepository(entityTarget).findOneBy({ [this.options.
|
|
76
|
+
dto[this.options.fieldName] = await this.options.entityManager.getRepository(entityTarget).findOneBy({ [this.options.relationCoModelUserKeyFieldName]: fieldUserKeyValue });
|
|
76
77
|
if (this.options.required && isEmpty(dto[this.options.fieldName])) {
|
|
77
|
-
throw new Error(`ManyToOneRelationFieldCrudManager: Record with userKey: ${this.options.
|
|
78
|
+
throw new Error(`ManyToOneRelationFieldCrudManager: Record with userKey: ${this.options.relationCoModelUserKeyFieldName}: ${fieldUserKeyValue} not found in ${this.options.relationCoModelSingularName}`);
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -3,23 +3,36 @@ import * as path from 'path'; // To handle file paths
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
export const getDynamicModuleNames = (): string[] => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
6
|
+
const dynamicModulesToExclude = process.env.SOLID_DYNAMIC_MODULES_TO_EXCLUDE?.split(',') || [];
|
|
7
|
+
|
|
8
|
+
// Adjust if 'src' is in a different location
|
|
9
|
+
const srcPath = path.join(process.cwd(), 'src');
|
|
10
|
+
const coreModuleNames = getCoreModuleNames();
|
|
11
|
+
const allExcludedModules = [...new Set([...coreModuleNames, ...dynamicModulesToExclude])];
|
|
12
|
+
|
|
13
|
+
const directories = fs.readdirSync(srcPath, { withFileTypes: true });
|
|
14
|
+
// const enabledModules = directories
|
|
15
|
+
// .filter(d => d.isDirectory() && !allExcludedModules.includes(d.name))
|
|
16
|
+
// .map(d => d.name);
|
|
17
|
+
|
|
18
|
+
const enabledModules = directories
|
|
19
|
+
.filter(dirent => {
|
|
20
|
+
const isValidDirectory = dirent.isDirectory() && !allExcludedModules.includes(dirent.name);
|
|
21
|
+
|
|
22
|
+
if (!isValidDirectory) return false;
|
|
23
|
+
|
|
24
|
+
const fullPath = path.join(srcPath, dirent.name);
|
|
25
|
+
const files = fs.readdirSync(fullPath);
|
|
26
|
+
// skip if empty directory
|
|
27
|
+
return files.length > 0;
|
|
28
|
+
})
|
|
29
|
+
.map(dirent => dirent.name);
|
|
30
|
+
|
|
31
|
+
console.log(`Enabled dynamic modules:`, enabledModules);
|
|
32
|
+
return enabledModules;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const getCoreModuleNames = (): string[] => {
|
|
36
|
+
// return ['iam', 'common', 'queues', 'app-builder'];
|
|
37
|
+
return ['solid-core'];
|
|
38
|
+
}
|
|
@@ -4308,6 +4308,33 @@
|
|
|
4308
4308
|
"isSystem": true,
|
|
4309
4309
|
"isUserKey": true
|
|
4310
4310
|
},
|
|
4311
|
+
{
|
|
4312
|
+
"name": "displayName",
|
|
4313
|
+
"displayName": "Display Name",
|
|
4314
|
+
"type": "shortText",
|
|
4315
|
+
"ormType": "varchar",
|
|
4316
|
+
"length": 256,
|
|
4317
|
+
"required": false,
|
|
4318
|
+
"unique": false,
|
|
4319
|
+
"index": false,
|
|
4320
|
+
"private": false,
|
|
4321
|
+
"encrypt": false,
|
|
4322
|
+
"isSystem": true,
|
|
4323
|
+
"isUserKey": false
|
|
4324
|
+
},
|
|
4325
|
+
{
|
|
4326
|
+
"name": "description",
|
|
4327
|
+
"displayName": "Description",
|
|
4328
|
+
"type": "longText",
|
|
4329
|
+
"ormType": "text",
|
|
4330
|
+
"required": false,
|
|
4331
|
+
"unique": false,
|
|
4332
|
+
"index": false,
|
|
4333
|
+
"private": false,
|
|
4334
|
+
"encrypt": false,
|
|
4335
|
+
"isSystem": true,
|
|
4336
|
+
"description": "This is a description of the dashboard configuration, providing context and details about the dashboard."
|
|
4337
|
+
},
|
|
4311
4338
|
{
|
|
4312
4339
|
"name": "layoutJson",
|
|
4313
4340
|
"displayName": "Layout Json",
|
|
@@ -11075,6 +11102,12 @@
|
|
|
11075
11102
|
"name": "name"
|
|
11076
11103
|
}
|
|
11077
11104
|
},
|
|
11105
|
+
{
|
|
11106
|
+
"type": "field",
|
|
11107
|
+
"attrs": {
|
|
11108
|
+
"name": "displayName"
|
|
11109
|
+
}
|
|
11110
|
+
},
|
|
11078
11111
|
{
|
|
11079
11112
|
"type": "field",
|
|
11080
11113
|
"attrs": {
|
|
@@ -11138,6 +11171,18 @@
|
|
|
11138
11171
|
"name": "name"
|
|
11139
11172
|
}
|
|
11140
11173
|
},
|
|
11174
|
+
{
|
|
11175
|
+
"type": "field",
|
|
11176
|
+
"attrs": {
|
|
11177
|
+
"name": "displayName"
|
|
11178
|
+
}
|
|
11179
|
+
},
|
|
11180
|
+
{
|
|
11181
|
+
"type": "field",
|
|
11182
|
+
"attrs": {
|
|
11183
|
+
"name": "description"
|
|
11184
|
+
}
|
|
11185
|
+
},
|
|
11141
11186
|
{
|
|
11142
11187
|
"type": "field",
|
|
11143
11188
|
"attrs": {
|
|
@@ -111,7 +111,7 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
private async validateAndTransformDto(field: FieldMetadata, dto: any, files: Express.Multer.File[], hasMediaFields: boolean, isPartialUpdate: boolean = false, isUpdate: boolean = false) {
|
|
114
|
-
const fieldManager: FieldCrudManager = this.fieldCrudManager(field, this.entityManager, isPartialUpdate, isUpdate);
|
|
114
|
+
const fieldManager: FieldCrudManager = await this.fieldCrudManager(field, this.entityManager, isPartialUpdate, isUpdate);
|
|
115
115
|
const validationErrors = fieldManager.validate(dto, files);
|
|
116
116
|
const errors = (validationErrors instanceof Promise) ? await validationErrors : validationErrors;
|
|
117
117
|
if (errors.length > 0) {
|
|
@@ -259,7 +259,7 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
private fieldCrudManager(fieldMetadata: FieldMetadata, entityManager: EntityManager, isPartialUpdate: boolean = false, isUpdate: boolean = false)
|
|
262
|
+
private async fieldCrudManager(fieldMetadata: FieldMetadata, entityManager: EntityManager, isPartialUpdate: boolean = false, isUpdate: boolean = false) {
|
|
263
263
|
const commonOptions = { required: fieldMetadata.required && !isPartialUpdate, fieldName: fieldMetadata.name, isUpdate };
|
|
264
264
|
switch (fieldMetadata.type) {
|
|
265
265
|
case SolidFieldType.shortText: {
|
|
@@ -326,11 +326,13 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
326
326
|
case SolidFieldType.relation: {
|
|
327
327
|
// Identify if the field is for the inverse side or not
|
|
328
328
|
if (fieldMetadata.relationType === RelationType.manyToOne) {
|
|
329
|
+
const relationCoModelUserKeyFieldName = await this.getUserKeyFieldNameForModel(fieldMetadata.relationCoModelSingularName);
|
|
329
330
|
const manyToOneOptions: ManyToOneRelationFieldOptions = {
|
|
330
331
|
...commonOptions,
|
|
331
332
|
relationCoModelSingularName: fieldMetadata.relationCoModelSingularName,
|
|
332
|
-
modelUserKeyFieldName: fieldMetadata.model.userKeyField?.name,
|
|
333
|
+
// modelUserKeyFieldName: fieldMetadata.model.userKeyField?.name,
|
|
333
334
|
modelSingularName: fieldMetadata.model.singularName,
|
|
335
|
+
relationCoModelUserKeyFieldName: relationCoModelUserKeyFieldName,
|
|
334
336
|
entityManager,
|
|
335
337
|
}
|
|
336
338
|
return new ManyToOneRelationFieldCrudManager(manyToOneOptions);
|
|
@@ -677,7 +679,7 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
677
679
|
|
|
678
680
|
// Process each field
|
|
679
681
|
for (const field of model.fields) {
|
|
680
|
-
const fieldManager: FieldCrudManager = this.fieldCrudManager(field, this.entityManager);
|
|
682
|
+
const fieldManager: FieldCrudManager = await this.fieldCrudManager(field, this.entityManager);
|
|
681
683
|
const validationErrors = await fieldManager.validate(createDto, files);
|
|
682
684
|
if (validationErrors.length > 0) {
|
|
683
685
|
throw new BadRequestException(`Validation errors in ${field.name} are invalid: ${validationErrors.map(e => e.error).join(', ')}`);
|
|
@@ -871,5 +873,13 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
871
873
|
|
|
872
874
|
return this.getFieldMetadataRecursively(remainingParts, relationCoModel.fields);
|
|
873
875
|
}
|
|
876
|
+
|
|
877
|
+
async getUserKeyFieldNameForModel(modelSingularName: string): Promise<string> {
|
|
878
|
+
const model = await this.modelMetadataService.findOneBySingularName(modelSingularName, ['userKeyField']);
|
|
879
|
+
if (!model) {
|
|
880
|
+
throw new BadRequestException(`Model ${modelSingularName} not found`);
|
|
881
|
+
}
|
|
882
|
+
return model.userKeyField?.name || '';
|
|
883
|
+
}
|
|
874
884
|
}
|
|
875
885
|
|
|
@@ -129,10 +129,15 @@ export class DashboardService extends CRUDService<Dashboard> {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
private async writeToConfig(metaData: any, dashboard: Dashboard, filePath: string) {
|
|
132
|
-
if (metaData.dashboards) {
|
|
132
|
+
if (metaData.dashboards && Array.isArray(metaData.dashboards)) {
|
|
133
133
|
const dashboardIndex = metaData.dashboards?.findIndex((dashboardFromFile: { name: string; }) => dashboardFromFile.name === dashboard.name);
|
|
134
134
|
const dto = await this.dashboardMapper.toDto(dashboard);
|
|
135
|
-
|
|
135
|
+
if (dashboardIndex !== -1) {
|
|
136
|
+
metaData.dashboards[dashboardIndex] = dto;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
metaData.dashboards.push(dto);
|
|
140
|
+
}
|
|
136
141
|
}
|
|
137
142
|
else {
|
|
138
143
|
const dashboards = [];
|
|
@@ -36,7 +36,7 @@ export class SolidCreateModuleMcpToolResponseHandler implements IMcpToolResponse
|
|
|
36
36
|
// This creates the module-metadata.json file....
|
|
37
37
|
const moduleObj = await this.moduleMetadataService.create(createDto);
|
|
38
38
|
|
|
39
|
-
const seeder = this.solidRegistry.getSeeders().filter((seeder) => seeder.name === 'ModuleMetadataSeederService').map((seeder) => seeder.instance).pop();
|
|
39
|
+
// const seeder = this.solidRegistry.getSeeders().filter((seeder) => seeder.name === 'ModuleMetadataSeederService').map((seeder) => seeder.instance).pop();
|
|
40
40
|
|
|
41
41
|
// Now we need to run solid seed & then solid refresh-model --name <module-name>
|
|
42
42
|
await this.moduleMetadataService.generateCode({ moduleId: moduleObj.id });
|
|
@@ -46,7 +46,10 @@ export class SolidCreateModuleMcpToolResponseHandler implements IMcpToolResponse
|
|
|
46
46
|
// await seeder.seed();
|
|
47
47
|
|
|
48
48
|
// TODO: decide on some shape to return hre...
|
|
49
|
-
return {
|
|
49
|
+
return {
|
|
50
|
+
seedingRequired: true,
|
|
51
|
+
serverRebooting: true,
|
|
52
|
+
}
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Injectable } from "@nestjs/common";
|
|
2
2
|
import { SqlExpression, SqlExpressionOperator } from "./question-data-providers/chartjs-sql-data-provider.service";
|
|
3
|
+
import { RequestContextService } from "./request-context.service";
|
|
3
4
|
|
|
4
5
|
export interface SqlReplacementResult {
|
|
5
6
|
rawSql: string;
|
|
@@ -8,10 +9,25 @@ export interface SqlReplacementResult {
|
|
|
8
9
|
|
|
9
10
|
@Injectable()
|
|
10
11
|
export class SqlExpressionResolverService {
|
|
12
|
+
constructor(private readonly requestContextService: RequestContextService) { }
|
|
11
13
|
resolveSqlWithExpressions(sql: string, expressions: SqlExpression[]): SqlReplacementResult {
|
|
12
14
|
const variableToColumnMap: Record<string, string> = {};
|
|
13
15
|
const variablePattern = /{{\s*(\w+)\s*\[\s*([\w.]+)\s*\]\s*}}/g;
|
|
14
16
|
|
|
17
|
+
let paramIndex = 1;
|
|
18
|
+
const parameters: any[] = [];
|
|
19
|
+
|
|
20
|
+
// Handle sql expression tokens like {{$activeUserId}} in the SQL string
|
|
21
|
+
if (sql.includes('{{$activeUserId}}')) {
|
|
22
|
+
const activeUser = this.requestContextService.getActiveUser();
|
|
23
|
+
if (activeUser && activeUser.sub) {
|
|
24
|
+
// Replace custom placeholder with parameter placeholder ($1)
|
|
25
|
+
sql = sql.replace(/\{\{\$activeUserId\}\}/g, `$${paramIndex++}`);
|
|
26
|
+
// Add the active user ID to parameters
|
|
27
|
+
parameters.push(activeUser.sub);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
// --- Pass 1: extract variable -> column mappings ---
|
|
16
32
|
let simplifiedSql = sql.replace(variablePattern, (_, variableName, columnName) => {
|
|
17
33
|
variableToColumnMap[variableName] = columnName;
|
|
@@ -19,8 +35,6 @@ export class SqlExpressionResolverService {
|
|
|
19
35
|
});
|
|
20
36
|
|
|
21
37
|
// --- Pass 2: Replace each variable with positional fragment ---
|
|
22
|
-
let paramIndex = 1;
|
|
23
|
-
const parameters: any[] = [];
|
|
24
38
|
|
|
25
39
|
for (const expr of expressions) {
|
|
26
40
|
const column = variableToColumnMap[expr.variableName];
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Injectable, Logger } from '@nestjs/common';
|
|
2
2
|
import { InjectDataSource } from "@nestjs/typeorm";
|
|
3
|
-
import * as fs from 'fs/promises'; // Use the Promise-based version of fs for async/await
|
|
4
3
|
import { Dashboard } from 'src/entities/dashboard.entity';
|
|
5
4
|
import { ModuleMetadataHelperService } from "src/helpers/module-metadata-helper.service";
|
|
6
|
-
import { DashboardMapper } from 'src/mappers/dashboard-mapper';
|
|
7
5
|
import { DashboardService } from 'src/services/dashboard.service';
|
|
8
|
-
import { DataSource, EntitySubscriberInterface, InsertEvent, UpdateEvent } from "typeorm";
|
|
6
|
+
import { DataSource, EntityManager, EntitySubscriberInterface, InsertEvent, UpdateEvent } from "typeorm";
|
|
9
7
|
|
|
10
8
|
@Injectable()
|
|
11
9
|
export class DashboardSubscriber implements EntitySubscriberInterface<Dashboard> {
|
|
@@ -28,7 +26,7 @@ export class DashboardSubscriber implements EntitySubscriberInterface<Dashboard>
|
|
|
28
26
|
this.logger.debug('No dashboard entity found in the DashboardSubscriber afterInsert method');
|
|
29
27
|
return;
|
|
30
28
|
}
|
|
31
|
-
await this.
|
|
29
|
+
await this.saveDashboardToConfig(event.entity, event.queryRunner.manager);
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
async afterUpdate(event: UpdateEvent<Dashboard>) {
|
|
@@ -37,7 +35,28 @@ export class DashboardSubscriber implements EntitySubscriberInterface<Dashboard>
|
|
|
37
35
|
return;
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
await this.
|
|
38
|
+
await this.saveDashboardToConfig(event.databaseEntity, event.queryRunner.manager);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private async saveDashboardToConfig(dashboard: Dashboard, entityManager: EntityManager): Promise<void> {
|
|
42
|
+
if (!dashboard || !dashboard.id) {
|
|
43
|
+
this.logger.debug('Dashboard or dashboard id is undefined');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Load the dashboard with module relation populated
|
|
48
|
+
const populatedDashboard = await entityManager.findOne(Dashboard, {
|
|
49
|
+
where: { id: dashboard.id },
|
|
50
|
+
relations: ['module','dashboardVariables', 'questions', 'questions.questionSqlDatasetConfigs'],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!populatedDashboard) {
|
|
54
|
+
this.logger.error(`Dashboard not found for id ${dashboard.id}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Call the saveDashboardToConfig method from the DashboardService
|
|
59
|
+
await this.dashboardService.saveDashboardToConfig(populatedDashboard);
|
|
41
60
|
}
|
|
42
61
|
|
|
43
62
|
}
|