@solidstarters/solid-core 1.2.184 → 1.2.186
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/config/iam.config.d.ts +2 -0
- package/dist/config/iam.config.d.ts.map +1 -1
- package/dist/config/iam.config.js +1 -0
- package/dist/config/iam.config.js.map +1 -1
- package/dist/dtos/selection-dynamic-query.dto.d.ts +1 -0
- package/dist/dtos/selection-dynamic-query.dto.d.ts.map +1 -1
- package/dist/dtos/selection-dynamic-query.dto.js +6 -1
- package/dist/dtos/selection-dynamic-query.dto.js.map +1 -1
- package/dist/helpers/module-metadata-helper.service.d.ts.map +1 -1
- package/dist/helpers/module-metadata-helper.service.js +8 -3
- package/dist/helpers/module-metadata-helper.service.js.map +1 -1
- package/dist/interfaces.d.ts +25 -1
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js +3 -2
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +48 -48
- package/dist/services/computed-fields/entity/uuid-externalid-entity-computed-field-provider.service.d.ts +10 -0
- package/dist/services/computed-fields/entity/uuid-externalid-entity-computed-field-provider.service.d.ts.map +1 -0
- package/dist/services/computed-fields/entity/uuid-externalid-entity-computed-field-provider.service.js +35 -0
- package/dist/services/computed-fields/entity/uuid-externalid-entity-computed-field-provider.service.js.map +1 -0
- package/dist/services/field-metadata.service.d.ts.map +1 -1
- package/dist/services/field-metadata.service.js +10 -0
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.d.ts +16 -0
- package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.d.ts.map +1 -0
- package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.js +73 -0
- package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.js.map +1 -0
- package/dist/services/genai/mcp-handlers/solid-add-custom-service-method-mcp-handler.service.d.ts +16 -0
- package/dist/services/genai/mcp-handlers/solid-add-custom-service-method-mcp-handler.service.d.ts.map +1 -0
- package/dist/services/genai/mcp-handlers/solid-add-custom-service-method-mcp-handler.service.js +74 -0
- package/dist/services/genai/mcp-handlers/solid-add-custom-service-method-mcp-handler.service.js.map +1 -0
- package/dist/services/genai/mcp-handlers/solid-add-header-button-or-row-button-to-list-view-mcp-handler.service.d.ts +16 -0
- package/dist/services/genai/mcp-handlers/solid-add-header-button-or-row-button-to-list-view-mcp-handler.service.d.ts.map +1 -0
- package/dist/services/genai/mcp-handlers/solid-add-header-button-or-row-button-to-list-view-mcp-handler.service.js +151 -0
- package/dist/services/genai/mcp-handlers/solid-add-header-button-or-row-button-to-list-view-mcp-handler.service.js.map +1 -0
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +14 -12
- package/dist/services/model-metadata.service.js.map +1 -1
- 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/solid-ts-morph.service.d.ts +15 -0
- package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
- package/dist/services/solid-ts-morph.service.js +68 -0
- package/dist/services/solid-ts-morph.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/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/config/iam.config.ts +2 -1
- package/src/dtos/selection-dynamic-query.dto.ts +4 -0
- package/src/helpers/module-metadata-helper.service.ts +10 -3
- package/src/interfaces.ts +28 -1
- package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +4 -3
- package/src/seeders/seed-data/solid-core-metadata.json +48 -48
- package/src/services/computed-fields/entity/uuid-externalid-entity-computed-field-provider.service.ts +34 -0
- package/src/services/field-metadata.service.ts +10 -0
- package/src/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.ts +67 -0
- package/src/services/genai/mcp-handlers/solid-add-custom-service-method-mcp-handler.service.ts +69 -0
- package/src/services/genai/mcp-handlers/solid-add-header-button-or-row-button-to-list-view-mcp-handler.service.ts +141 -0
- package/src/services/model-metadata.service.ts +25 -23
- package/src/services/setting.service.ts +4 -2
- package/src/services/solid-ts-morph.service.ts +112 -0
- package/src/solid-core.module.ts +10 -1
- package/src/controllers/user.controller.ts.bkp +0 -78
package/src/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Injectable, Logger } from "@nestjs/common";
|
|
2
|
+
import { AiInteraction } from "src/entities/ai-interaction.entity";
|
|
3
|
+
import { IMcpToolResponseHandler, McpComputedProviderResponse, PlanStep } from "../../../interfaces";
|
|
4
|
+
import { SolidTsMorphService } from "src/services/solid-ts-morph.service";
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class SolidAddControllerHandlerMcpHandler implements IMcpToolResponseHandler {
|
|
8
|
+
private readonly logger = new Logger(SolidAddControllerHandlerMcpHandler.name);
|
|
9
|
+
|
|
10
|
+
constructor(private readonly tsMorph: SolidTsMorphService) { }
|
|
11
|
+
|
|
12
|
+
async apply(aiInteraction: AiInteraction) {
|
|
13
|
+
const raw = this.safeParse(aiInteraction.message);
|
|
14
|
+
const payload: McpComputedProviderResponse | undefined = (raw?.data?.plan ? raw.data : raw) as McpComputedProviderResponse;
|
|
15
|
+
|
|
16
|
+
if (!payload || !Array.isArray(payload.plan)) {
|
|
17
|
+
throw new Error("SolidAddControllerHandlerMethodMcpHandler: invalid MCP response; missing plan[]");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Batch all plan steps in a single txn so nodemon restarts only once.
|
|
21
|
+
this.tsMorph.begin();
|
|
22
|
+
try {
|
|
23
|
+
for (const step of payload.plan as PlanStep[]) {
|
|
24
|
+
switch (step.type) {
|
|
25
|
+
case "createNewFile": {
|
|
26
|
+
const overwrite = step.overwrite ?? false;
|
|
27
|
+
this.tsMorph.createNewFile(step.path, step.content, overwrite);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
case "addMethodToExistingClass": {
|
|
31
|
+
this.tsMorph.addMethodToExistingClass(
|
|
32
|
+
step.path,
|
|
33
|
+
step.className,
|
|
34
|
+
step.methodName,
|
|
35
|
+
step.content,
|
|
36
|
+
);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
default:
|
|
40
|
+
throw new Error(`Unsupported plan step type: ${(step as any).type}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const result = await this.tsMorph.commit();
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
seedingRequired: true,
|
|
48
|
+
serverRebooting: true,
|
|
49
|
+
appliedSteps: payload.plan.length,
|
|
50
|
+
wroteFiles: result.wrote,
|
|
51
|
+
};
|
|
52
|
+
} catch (err) {
|
|
53
|
+
this.logger.error(`Apply failed; rolling back. ${String(err)}`);
|
|
54
|
+
this.tsMorph.rollback();
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private safeParse(str: string): any {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(str);
|
|
62
|
+
} catch {
|
|
63
|
+
const unescaped = str.replace(/\\'/g, "'");
|
|
64
|
+
return JSON.parse(unescaped);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/services/genai/mcp-handlers/solid-add-custom-service-method-mcp-handler.service.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Injectable, Logger } from "@nestjs/common";
|
|
2
|
+
import { AiInteraction } from "src/entities/ai-interaction.entity";
|
|
3
|
+
import { IMcpToolResponseHandler, McpComputedProviderResponse, PlanStep } from "../../../interfaces";
|
|
4
|
+
import { SolidTsMorphService } from "src/services/solid-ts-morph.service";
|
|
5
|
+
|
|
6
|
+
const RESTART_TOUCH_FILE = process.env.MCP_RESTART_TOUCH_FILE || "tmp/restart.touch";
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class SolidAddCustomServiceMethodMcpHandler implements IMcpToolResponseHandler {
|
|
10
|
+
private readonly logger = new Logger(SolidAddCustomServiceMethodMcpHandler.name);
|
|
11
|
+
|
|
12
|
+
constructor(private readonly tsMorph: SolidTsMorphService) { }
|
|
13
|
+
|
|
14
|
+
async apply(aiInteraction: AiInteraction) {
|
|
15
|
+
const raw = this.safeParse(aiInteraction.message);
|
|
16
|
+
const payload: McpComputedProviderResponse | undefined = (raw?.data?.plan ? raw.data : raw) as McpComputedProviderResponse;
|
|
17
|
+
|
|
18
|
+
if (!payload || !Array.isArray(payload.plan)) {
|
|
19
|
+
throw new Error("SolidAddCustomServiceMethodMcpHandler: invalid MCP response; missing plan[]");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Batch all plan steps in a single txn so nodemon restarts only once.
|
|
23
|
+
this.tsMorph.begin();
|
|
24
|
+
try {
|
|
25
|
+
for (const step of payload.plan as PlanStep[]) {
|
|
26
|
+
switch (step.type) {
|
|
27
|
+
case "createNewFile": {
|
|
28
|
+
const overwrite = step.overwrite ?? false;
|
|
29
|
+
this.tsMorph.createNewFile(step.path, step.content, overwrite);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case "addMethodToExistingClass": {
|
|
33
|
+
this.tsMorph.addMethodToExistingClass(
|
|
34
|
+
step.path,
|
|
35
|
+
step.className,
|
|
36
|
+
step.methodName,
|
|
37
|
+
step.content,
|
|
38
|
+
);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Unsupported plan step type: ${(step as any).type}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = await this.tsMorph.commit();
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
seedingRequired: false,
|
|
50
|
+
serverRebooting: true,
|
|
51
|
+
appliedSteps: payload.plan.length,
|
|
52
|
+
wroteFiles: result.wrote,
|
|
53
|
+
};
|
|
54
|
+
} catch (err) {
|
|
55
|
+
this.logger.error(`Apply failed; rolling back. ${String(err)}`);
|
|
56
|
+
this.tsMorph.rollback();
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private safeParse(str: string): any {
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(str);
|
|
64
|
+
} catch {
|
|
65
|
+
const unescaped = str.replace(/\\'/g, "'");
|
|
66
|
+
return JSON.parse(unescaped);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Injectable, Logger } from "@nestjs/common";
|
|
2
|
+
import { AiInteraction } from "src/entities/ai-interaction.entity";
|
|
3
|
+
import { IMcpToolResponseHandler, McpComputedProviderResponse, PlanStep } from "../../../interfaces";
|
|
4
|
+
import { SolidTsMorphService } from "src/services/solid-ts-morph.service";
|
|
5
|
+
import { ModuleMetadataHelperService } from "src/helpers/module-metadata-helper.service";
|
|
6
|
+
import * as fs from "fs/promises";
|
|
7
|
+
|
|
8
|
+
const RESTART_TOUCH_FILE = process.env.MCP_RESTART_TOUCH_FILE || "tmp/restart.touch";
|
|
9
|
+
|
|
10
|
+
@Injectable()
|
|
11
|
+
export class SolidAddHeaderButtonOrRowButtonToListViewMcpHandler implements IMcpToolResponseHandler {
|
|
12
|
+
private readonly logger = new Logger(SolidAddHeaderButtonOrRowButtonToListViewMcpHandler.name);
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly tsMorph: SolidTsMorphService,
|
|
16
|
+
private readonly moduleMetadataHelperService: ModuleMetadataHelperService,
|
|
17
|
+
|
|
18
|
+
) { }
|
|
19
|
+
|
|
20
|
+
async apply(aiInteraction: AiInteraction) {
|
|
21
|
+
const raw = this.safeParse(aiInteraction.message);
|
|
22
|
+
const payload: McpComputedProviderResponse | undefined = (raw?.data?.plan ? raw.data : raw) as McpComputedProviderResponse;
|
|
23
|
+
|
|
24
|
+
if (!payload || !Array.isArray(payload.plan)) {
|
|
25
|
+
throw new Error("SolidAddHeaderButtonOrRowButtonToListViewMcpHandler: invalid MCP response; missing plan[]");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Batch all plan steps in a single txn so nodemon restarts only once.
|
|
29
|
+
this.tsMorph.begin();
|
|
30
|
+
try {
|
|
31
|
+
for (const step of payload.plan as PlanStep[]) {
|
|
32
|
+
switch (step.type) {
|
|
33
|
+
case "createNewFile": {
|
|
34
|
+
const overwrite = step.overwrite ?? false;
|
|
35
|
+
this.tsMorph.createNewFile(step.path, step.content, overwrite);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case "registerSolidXExtensionComponent": {
|
|
39
|
+
this.tsMorph.registerSolidUiExtension(
|
|
40
|
+
step.path,
|
|
41
|
+
step.content,
|
|
42
|
+
);
|
|
43
|
+
this.tsMorph.addImport(
|
|
44
|
+
step.path,
|
|
45
|
+
step.importExtensionComponent,
|
|
46
|
+
);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case "addListViewButton": {
|
|
50
|
+
|
|
51
|
+
const filePath =
|
|
52
|
+
await this.moduleMetadataHelperService.getModuleMetadataFilePath(
|
|
53
|
+
step.moduleName
|
|
54
|
+
);
|
|
55
|
+
try {
|
|
56
|
+
await fs.access(filePath);
|
|
57
|
+
} catch {
|
|
58
|
+
this.logger.error(`Metadata file not found: ${filePath}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const metaData =
|
|
62
|
+
await this.moduleMetadataHelperService.getModuleMetadataConfiguration(
|
|
63
|
+
filePath
|
|
64
|
+
);
|
|
65
|
+
// Remove, update or insert logic
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// Find the existing view of type "list" for the given module & model
|
|
69
|
+
const existingViewIndex = metaData.views.findIndex(
|
|
70
|
+
(view: any) =>
|
|
71
|
+
view.type === "list" &&
|
|
72
|
+
view.moduleUserKey === step.moduleName &&
|
|
73
|
+
view.modelUserKey === step.modelName
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (existingViewIndex !== -1) {
|
|
77
|
+
const view = metaData.views[existingViewIndex];
|
|
78
|
+
|
|
79
|
+
// Ensure layout & attrs exist
|
|
80
|
+
view.layout = view.layout || {};
|
|
81
|
+
view.layout.attrs = view.layout.attrs || {};
|
|
82
|
+
|
|
83
|
+
// Initialize rowButtons or headerButtons if not present
|
|
84
|
+
if (!Array.isArray(view.layout.attrs[step.buttonType])) {
|
|
85
|
+
view.layout.attrs[step.buttonType] = [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let buttonContent = step.content;
|
|
89
|
+
|
|
90
|
+
// Parse only if it’s a string
|
|
91
|
+
if (typeof buttonContent === "string") {
|
|
92
|
+
try {
|
|
93
|
+
buttonContent = JSON.parse(buttonContent);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
this.logger.error("❌ Failed to parse step.content JSON:", err);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
// Push the new button content
|
|
102
|
+
view.layout.attrs[step.buttonType].push(buttonContent);
|
|
103
|
+
|
|
104
|
+
console.log(`✅ Added ${step.buttonType} to view: ${view.name}`);
|
|
105
|
+
} else {
|
|
106
|
+
console.warn(`⚠️ No matching list view found for module=${step.moduleName} and model=${step.modelName}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const updatedContent = JSON.stringify(metaData, null, 2);
|
|
110
|
+
await fs.writeFile(filePath, updatedContent);
|
|
111
|
+
this.logger.log(`Updated list view in ${filePath}`);
|
|
112
|
+
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
default:
|
|
116
|
+
throw new Error(`Unsupported plan step type: ${(step as any).type}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = await this.tsMorph.commit();
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
seedingRequired: true,
|
|
124
|
+
serverRebooting: false,
|
|
125
|
+
};
|
|
126
|
+
} catch (err) {
|
|
127
|
+
this.logger.error(`Apply failed; rolling back. ${String(err)}`);
|
|
128
|
+
this.tsMorph.rollback();
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private safeParse(str: string): any {
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(str);
|
|
136
|
+
} catch {
|
|
137
|
+
const unescaped = str.replace(/\\'/g, "'");
|
|
138
|
+
return JSON.parse(unescaped);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -657,28 +657,30 @@ export class ModelMetadataService {
|
|
|
657
657
|
// <moduleName>-metadata.json | Remove references to this model in the model metadata, menu, action & view sections. | Automatic
|
|
658
658
|
const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(modelEntity.module.name);
|
|
659
659
|
const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
660
|
+
if (metaData) {
|
|
661
|
+
const existingModelIndex = metaData.moduleMetadata.models.findIndex(
|
|
662
|
+
(existingModel: any) => existingModel.singularName === modelEntity.singularName
|
|
663
|
+
);
|
|
663
664
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
665
|
+
// Remove the model to be deleted from the metadata
|
|
666
|
+
if (existingModelIndex !== -1) {
|
|
667
|
+
metaData.moduleMetadata.models.splice(existingModelIndex, 1);
|
|
668
|
+
}
|
|
668
669
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
670
|
+
// Remove references to this model in the menu, action & view sections.
|
|
671
|
+
metaData.moduleMetadata.menus = metaData.moduleMetadata.menus.filter(
|
|
672
|
+
(menu: any) => menu.modelUserKey !== modelEntity.singularName
|
|
673
|
+
);
|
|
674
|
+
metaData.moduleMetadata.actions = metaData.moduleMetadata.actions.filter(
|
|
675
|
+
(action: any) => action.modelUserKey !== modelEntity.singularName
|
|
676
|
+
);
|
|
677
|
+
metaData.moduleMetadata.views = metaData.moduleMetadata.views.filter(
|
|
678
|
+
(view: any) => view.modelUserKey !== modelEntity.singularName
|
|
679
|
+
);
|
|
679
680
|
|
|
680
|
-
|
|
681
|
-
|
|
681
|
+
const updatedContent = JSON.stringify(metaData, null, 2);
|
|
682
|
+
await fs.writeFile(filePath, updatedContent);
|
|
683
|
+
}
|
|
682
684
|
|
|
683
685
|
// <moduleName>.module.ts | Remove all references and imports of the above files. | Manual (X)
|
|
684
686
|
// const moduleFilePath = path.resolve(modulePath, `${dasherize(modelEntity.module.name)}.module.ts`);
|
|
@@ -901,12 +903,12 @@ export class ModelMetadataService {
|
|
|
901
903
|
children: [
|
|
902
904
|
{
|
|
903
905
|
type: "column",
|
|
904
|
-
attrs: { name: "group-1", label: "", className: "col-6" },
|
|
906
|
+
attrs: { name: "group-1", label: "", className: "col-12 sm:col-12 md:col-6 lg:col-6" },
|
|
905
907
|
children: column1Fields
|
|
906
908
|
},
|
|
907
909
|
{
|
|
908
910
|
type: "column",
|
|
909
|
-
attrs: { name: "group-2", label: "", className: "col-6" },
|
|
911
|
+
attrs: { name: "group-2", label: "", className: "col-12 sm:col-12 md:col-6 lg:col-6" },
|
|
910
912
|
children: column2Fields
|
|
911
913
|
}
|
|
912
914
|
]
|
|
@@ -1014,12 +1016,12 @@ export class ModelMetadataService {
|
|
|
1014
1016
|
children: [
|
|
1015
1017
|
{
|
|
1016
1018
|
type: "column",
|
|
1017
|
-
attrs: { name: "group-1", label: "", className: "col-6" },
|
|
1019
|
+
attrs: { name: "group-1", label: "", className: "col-12 sm:col-12 md:col-6 lg:col-6" },
|
|
1018
1020
|
children: firstHalf
|
|
1019
1021
|
},
|
|
1020
1022
|
{
|
|
1021
1023
|
type: "column",
|
|
1022
|
-
attrs: { name: "group-2", label: "", className: "col-6" },
|
|
1024
|
+
attrs: { name: "group-2", label: "", className: "col-12 sm:col-12 md:col-6 lg:col-6" },
|
|
1023
1025
|
children: secondHalf
|
|
1024
1026
|
}
|
|
1025
1027
|
]
|
|
@@ -76,7 +76,8 @@ export class SettingService extends CRUDService<Setting> {
|
|
|
76
76
|
solidXGenAiCodeBuilderConfig: JSON.stringify({
|
|
77
77
|
defaultProvider: "",
|
|
78
78
|
availableProviders: []
|
|
79
|
-
})
|
|
79
|
+
}),
|
|
80
|
+
showNameFieldsForRegistration:this.iamConfiguration.showNameFieldsForRegistration
|
|
80
81
|
};
|
|
81
82
|
|
|
82
83
|
const existingSettings = await this.repo.find();
|
|
@@ -187,7 +188,8 @@ export class SettingService extends CRUDService<Setting> {
|
|
|
187
188
|
defaultProvider: "",
|
|
188
189
|
availableProviders: []
|
|
189
190
|
}),
|
|
190
|
-
iamAutoGeneratedPassword:this.iamConfiguration.iamAutoGeneratedPassword
|
|
191
|
+
iamAutoGeneratedPassword:this.iamConfiguration.iamAutoGeneratedPassword,
|
|
192
|
+
showNameFieldsForRegistration:this.iamConfiguration.showNameFieldsForRegistration
|
|
191
193
|
};
|
|
192
194
|
}
|
|
193
195
|
|
|
@@ -3,6 +3,7 @@ import { Injectable, Logger } from "@nestjs/common";
|
|
|
3
3
|
import { join, dirname, normalize, isAbsolute, basename } from "node:path";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { Project, Node, ObjectLiteralExpression, ArrayLiteralExpression, QuoteKind, IndentationText } from "ts-morph";
|
|
6
|
+
import { MethodDeclarationStructure, StructureKind } from "ts-morph";
|
|
6
7
|
|
|
7
8
|
type Bucket = "providers" | "exports";
|
|
8
9
|
|
|
@@ -260,4 +261,115 @@ export class SolidTsMorphService {
|
|
|
260
261
|
this.logger.log(`Staged provider registration in: ${this.rel(abs)} (${registerIn.join(", ")})`);
|
|
261
262
|
return { staged: true };
|
|
262
263
|
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
addMethodToExistingClass(
|
|
267
|
+
filePath: string,
|
|
268
|
+
className: string,
|
|
269
|
+
methodName: string,
|
|
270
|
+
methodCode: string,
|
|
271
|
+
): { staged: boolean; overwritten: boolean; skipped: boolean } {
|
|
272
|
+
const abs = this.resolveRepoPath(filePath);
|
|
273
|
+
if (!existsSync(abs))
|
|
274
|
+
throw new Error(`addMethodToExistingClass: File not found at ${filePath}`);
|
|
275
|
+
|
|
276
|
+
const existing = this.project.getSourceFile(abs);
|
|
277
|
+
const sourceFile = existing
|
|
278
|
+
? existing
|
|
279
|
+
: this.project.createSourceFile(abs, readFileSync(abs, "utf8"), { overwrite: true });
|
|
280
|
+
|
|
281
|
+
const targetClass = sourceFile.getClass(className);
|
|
282
|
+
if (!targetClass)
|
|
283
|
+
throw new Error(`addMethodToExistingClass: Class ${className} not found in ${filePath}`);
|
|
284
|
+
|
|
285
|
+
const existingMethod = targetClass.getMethod(methodName);
|
|
286
|
+
if (existingMethod) {
|
|
287
|
+
|
|
288
|
+
this.logger.log(`Skipped adding method '${methodName}' (already exists)`);
|
|
289
|
+
return { staged: false, overwritten: false, skipped: true };
|
|
290
|
+
}
|
|
291
|
+
// Add the LLM-generated method directly
|
|
292
|
+
targetClass.addMember(methodCode);
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
this.dirtySourceFiles.add(abs);
|
|
296
|
+
this.logger.log(`Staged method '${methodName}' in class ${className} at ${this.rel(abs)}`);
|
|
297
|
+
return { staged: true, overwritten: !!existingMethod, skipped: false };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
registerSolidUiExtension(
|
|
301
|
+
filePath: string,
|
|
302
|
+
lineToAdd: string
|
|
303
|
+
): { staged: boolean; overwritten: boolean; skipped: boolean } {
|
|
304
|
+
const abs = this.resolveRepoPath(filePath);
|
|
305
|
+
if (!existsSync(abs))
|
|
306
|
+
throw new Error(`registerSolidUiExtension: File not found at ${filePath}`);
|
|
307
|
+
|
|
308
|
+
const fileContent = readFileSync(abs, "utf8");
|
|
309
|
+
|
|
310
|
+
// Check if the line already exists (avoid duplicates)
|
|
311
|
+
if (fileContent.includes(lineToAdd.trim())) {
|
|
312
|
+
this.logger.log(`Skipped adding line (already exists): ${lineToAdd}`);
|
|
313
|
+
return { staged: false, overwritten: false, skipped: true };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Append the new line at the end, ensuring newline
|
|
317
|
+
const newContent = fileContent.trimEnd() + "\n" + lineToAdd.trim() + "\n";
|
|
318
|
+
|
|
319
|
+
// Write updated content back
|
|
320
|
+
writeFileSync(abs, newContent, "utf8");
|
|
321
|
+
|
|
322
|
+
this.dirtySourceFiles.add(abs);
|
|
323
|
+
this.logger.log(`Staged new line in ${this.rel(abs)}: ${lineToAdd}`);
|
|
324
|
+
return { staged: true, overwritten: false, skipped: false };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
addImport(
|
|
328
|
+
filePath: string,
|
|
329
|
+
importLine: string
|
|
330
|
+
): { staged: boolean; overwritten: boolean; skipped: boolean } {
|
|
331
|
+
const abs = this.resolveRepoPath(filePath);
|
|
332
|
+
if (!existsSync(abs))
|
|
333
|
+
throw new Error(`addImport: File not found at ${filePath}`);
|
|
334
|
+
|
|
335
|
+
let fileContent = readFileSync(abs, "utf8");
|
|
336
|
+
|
|
337
|
+
// If import already exists — skip
|
|
338
|
+
if (fileContent.includes(importLine.trim())) {
|
|
339
|
+
this.logger.log(`Skipped adding import (already exists): ${importLine}`);
|
|
340
|
+
return { staged: false, overwritten: false, skipped: true };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Find last import statement (so we can insert after all imports)
|
|
344
|
+
const importRegex = /^import .+ from .+;$/gm;
|
|
345
|
+
let lastImportMatch: RegExpExecArray | null;
|
|
346
|
+
let lastImportIndex = -1;
|
|
347
|
+
|
|
348
|
+
while ((lastImportMatch = importRegex.exec(fileContent)) !== null) {
|
|
349
|
+
lastImportIndex = lastImportMatch.index + lastImportMatch[0].length;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Insert after last import (or at top if none exist)
|
|
353
|
+
let newContent: string;
|
|
354
|
+
if (lastImportIndex !== -1) {
|
|
355
|
+
newContent =
|
|
356
|
+
fileContent.slice(0, lastImportIndex) +
|
|
357
|
+
"\n" +
|
|
358
|
+
importLine.trim() +
|
|
359
|
+
"\n" +
|
|
360
|
+
fileContent.slice(lastImportIndex);
|
|
361
|
+
} else {
|
|
362
|
+
// No imports found — insert at top
|
|
363
|
+
newContent = importLine.trim() + "\n\n" + fileContent;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
writeFileSync(abs, newContent, "utf8");
|
|
367
|
+
|
|
368
|
+
this.dirtySourceFiles.add(abs);
|
|
369
|
+
this.logger.log(`Staged import in ${this.rel(abs)}: ${importLine}`);
|
|
370
|
+
return { staged: true, overwritten: false, skipped: false };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
|
|
263
375
|
}
|
package/src/solid-core.module.ts
CHANGED
|
@@ -102,6 +102,7 @@ import { UserSeederService } from './seeders/user-seeder.service';
|
|
|
102
102
|
import { AuthenticationService } from './services/authentication.service';
|
|
103
103
|
import { BcryptService } from './services/bcrypt.service';
|
|
104
104
|
import { UuidExternalIdComputedFieldProvider } from './services/computed-fields/uuid-external-id-computed-field-provider.service';
|
|
105
|
+
import { UuidExternalIdEntityComputedFieldProvider } from './services/computed-fields/entity/uuid-externalid-entity-computed-field-provider.service';
|
|
105
106
|
import { EmailTemplateService } from './services/email-template.service';
|
|
106
107
|
import { FileService } from './services/file.service';
|
|
107
108
|
import { HashingService } from './services/hashing.service';
|
|
@@ -296,6 +297,9 @@ import { SolidCreateComputedProviderMcpHandler } from './services/genai/mcp-hand
|
|
|
296
297
|
import { SolidTsMorphService } from './services/solid-ts-morph.service';
|
|
297
298
|
import { SolidAddVariableToDashboardMcpHandler } from './services/genai/mcp-handlers/solid-add-variable-to-dashboard-mcp-handler.service';
|
|
298
299
|
import { SolidAddQuestionToDashboardMcpHandler } from './services/genai/mcp-handlers/solid-add-question-to-dashboard-mcp-handler.service';
|
|
300
|
+
import { SolidAddCustomServiceMethodMcpHandler } from './services/genai/mcp-handlers/solid-add-custom-service-method-mcp-handler.service';
|
|
301
|
+
import { SolidAddHeaderButtonOrRowButtonToListViewMcpHandler } from './services/genai/mcp-handlers/solid-add-header-button-or-row-button-to-list-view-mcp-handler.service';
|
|
302
|
+
import { SolidAddControllerHandlerMcpHandler } from './services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service';
|
|
299
303
|
|
|
300
304
|
|
|
301
305
|
@Global()
|
|
@@ -538,6 +542,7 @@ import { SolidAddQuestionToDashboardMcpHandler } from './services/genai/mcp-hand
|
|
|
538
542
|
TinyUrlService,
|
|
539
543
|
PdfService,
|
|
540
544
|
UuidExternalIdComputedFieldProvider,
|
|
545
|
+
UuidExternalIdEntityComputedFieldProvider,
|
|
541
546
|
ListOfModelsSelectionProvider,
|
|
542
547
|
ListOfScheduledJobsSelectionProvider,
|
|
543
548
|
LocaleListSelectionProvider,
|
|
@@ -632,7 +637,7 @@ import { SolidAddQuestionToDashboardMcpHandler } from './services/genai/mcp-hand
|
|
|
632
637
|
SolidCreateModelWithFieldsMcpHandler,
|
|
633
638
|
SolidAddFieldsToModelMcpHandler,
|
|
634
639
|
SolidUpdateLayoutMcpHandler,
|
|
635
|
-
|
|
640
|
+
|
|
636
641
|
SolidCreateDashboardWithWidgetsMcpHandler,
|
|
637
642
|
SolidCreateDashboardQuestionMcpHandler,
|
|
638
643
|
SolidCreateDashboardQuestionSqlDatasetConfigMcpHandler,
|
|
@@ -640,6 +645,10 @@ import { SolidAddQuestionToDashboardMcpHandler } from './services/genai/mcp-hand
|
|
|
640
645
|
SolidCreateComputedProviderMcpHandler,
|
|
641
646
|
SolidAddVariableToDashboardMcpHandler,
|
|
642
647
|
SolidAddQuestionToDashboardMcpHandler,
|
|
648
|
+
|
|
649
|
+
SolidAddCustomServiceMethodMcpHandler,
|
|
650
|
+
SolidAddHeaderButtonOrRowButtonToListViewMcpHandler,
|
|
651
|
+
SolidAddControllerHandlerMcpHandler,
|
|
643
652
|
SolidTsMorphService,
|
|
644
653
|
|
|
645
654
|
ViewMetadataRepository,
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
|
|
2
|
-
import { ApiBearerAuth, ApiForbiddenResponse, ApiQuery, ApiTags } from '@nestjs/swagger';
|
|
3
|
-
import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
|
|
4
|
-
import { PaginationQueryDto } from 'src/dtos/pagination-query.dto';
|
|
5
|
-
import { ActiveUser } from '../decorators/active-user.decorator';
|
|
6
|
-
import { UserService } from '../services/user.service';
|
|
7
|
-
import { MutateUserRolesDto } from '../dtos/mutate-user-roles.dto';
|
|
8
|
-
import { MutateUserRolesBulkDto } from '../dtos/mutate-user-roles-list.dto';
|
|
9
|
-
import { BasicFilterDto } from 'src/dtos/basic-filters.dto';
|
|
10
|
-
|
|
11
|
-
@Controller('users')
|
|
12
|
-
@ApiTags("Iam")
|
|
13
|
-
export class UserController {
|
|
14
|
-
constructor(
|
|
15
|
-
private readonly userService: UserService
|
|
16
|
-
) { }
|
|
17
|
-
|
|
18
|
-
// /api/users
|
|
19
|
-
@ApiBearerAuth("jwt")
|
|
20
|
-
@Get()
|
|
21
|
-
@ApiBearerAuth("jwt")
|
|
22
|
-
@ApiQuery({ name: 'limit', required: false, type: Number })
|
|
23
|
-
@ApiQuery({ name: 'offset', required: false, type: Number })
|
|
24
|
-
@ApiQuery({ name: 'fields', required: false, type: Array })
|
|
25
|
-
@ApiQuery({ name: 'sort', required: false, type: Array })
|
|
26
|
-
@ApiQuery({ name: 'groupBy', required: false, type: Array })
|
|
27
|
-
@ApiQuery({ name: 'populate', required: false, type: Array })
|
|
28
|
-
@ApiQuery({ name: 'populateMedia', required: false, type: Array })
|
|
29
|
-
@ApiQuery({ name: 'filters', required: false, type: Array })
|
|
30
|
-
async findMany(
|
|
31
|
-
@Query() basicFilterDto: BasicFilterDto,
|
|
32
|
-
@ActiveUser() activeUser: ActiveUserData,
|
|
33
|
-
) {
|
|
34
|
-
return this.userService.findMany(basicFilterDto);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// /api/users/:id
|
|
38
|
-
@ApiBearerAuth("jwt")
|
|
39
|
-
@ApiForbiddenResponse({ description: 'Forbidden.' })
|
|
40
|
-
@Get(':id')
|
|
41
|
-
findOne(@Param('id', ParseIntPipe) id: number) {
|
|
42
|
-
return this.userService.findOne(id);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
@ApiBearerAuth("jwt")
|
|
46
|
-
@ApiForbiddenResponse({ description: 'Forbidden.' })
|
|
47
|
-
@Post('roles')
|
|
48
|
-
addRoleToUser(@Body() mutateUserRoles: MutateUserRolesDto) {
|
|
49
|
-
return this.userService.addRoleToUser(mutateUserRoles.username, mutateUserRoles.roleName);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
@ApiBearerAuth("jwt")
|
|
53
|
-
@ApiForbiddenResponse({ description: 'Forbidden.' })
|
|
54
|
-
@Post('roles/bulk')
|
|
55
|
-
addRolesToUser(@Body() mutateUserRolesBulk: MutateUserRolesBulkDto) {
|
|
56
|
-
return this.userService.addRolesToUser(mutateUserRolesBulk.username, mutateUserRolesBulk.roleNames);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
@ApiBearerAuth("jwt")
|
|
60
|
-
@ApiForbiddenResponse({ description: 'Forbidden.' })
|
|
61
|
-
@Delete('roles')
|
|
62
|
-
removeRoleFromUser(userEmail: string, roleName: string) {
|
|
63
|
-
return this.userService.removeRoleFromUser(userEmail, roleName);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
@ApiBearerAuth("jwt")
|
|
67
|
-
@Delete('/bulk')
|
|
68
|
-
async deleteMany(@Body() ids: number[]) {
|
|
69
|
-
return this.userService.deleteMany(ids);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@ApiBearerAuth("jwt")
|
|
74
|
-
@Delete(':id')
|
|
75
|
-
remove(@Param('id') id: number) {
|
|
76
|
-
return this.userService.remove(id);
|
|
77
|
-
}
|
|
78
|
-
}
|