@solidstarters/solid-core 1.2.187 → 1.2.189
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 +0 -13
- package/dist/config/iam.config.d.ts.map +1 -1
- package/dist/config/iam.config.js +2 -12
- package/dist/config/iam.config.js.map +1 -1
- package/dist/config/jwt.config.d.ts +14 -0
- package/dist/config/jwt.config.d.ts.map +1 -0
- package/dist/config/jwt.config.js +15 -0
- package/dist/config/jwt.config.js.map +1 -0
- package/dist/decorators/active-user.decorator.d.ts +1 -1
- package/dist/dtos/create-list-of-values.dto.d.ts.map +1 -1
- package/dist/dtos/create-list-of-values.dto.js +1 -0
- package/dist/dtos/create-list-of-values.dto.js.map +1 -1
- package/dist/dtos/update-list-of-values.dto.d.ts.map +1 -1
- package/dist/dtos/update-list-of-values.dto.js +1 -0
- package/dist/dtos/update-list-of-values.dto.js.map +1 -1
- package/dist/entities/list-of-values.entity.js +1 -1
- package/dist/entities/list-of-values.entity.js.map +1 -1
- package/dist/guards/access-token.guard.d.ts +1 -1
- package/dist/guards/access-token.guard.d.ts.map +1 -1
- package/dist/guards/access-token.guard.js +2 -2
- package/dist/guards/access-token.guard.js.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.js +12 -2
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.js.map +1 -1
- package/dist/interfaces.d.ts +16 -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 +7 -0
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +236 -119
- package/dist/services/authentication.service.d.ts +8 -7
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +12 -11
- package/dist/services/authentication.service.js.map +1 -1
- 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 +14 -12
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/dashboard-question.service.d.ts.map +1 -1
- package/dist/services/dashboard-question.service.js +23 -2
- package/dist/services/dashboard-question.service.js.map +1 -1
- package/dist/services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service.d.ts +16 -0
- package/dist/services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service.d.ts.map +1 -0
- package/dist/services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service.js +151 -0
- package/dist/services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service.js.map +1 -0
- package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.d.ts +1 -0
- package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.d.ts.map +1 -1
- package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.js +8 -1
- package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.js.map +1 -1
- package/dist/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.d.ts +14 -0
- package/dist/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.d.ts.map +1 -0
- package/dist/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.js +73 -0
- package/dist/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.js.map +1 -0
- package/dist/services/list-of-values-metadata.service.d.ts.map +1 -1
- package/dist/services/list-of-values-metadata.service.js.map +1 -1
- package/dist/services/model-metadata.service.js +2 -2
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/selection-providers/list-of-models-selection-provider.service.js +2 -2
- package/dist/services/selection-providers/list-of-models-selection-provider.service.js.map +1 -1
- package/dist/services/setting.service.d.ts.map +1 -1
- package/dist/services/setting.service.js +3 -2
- package/dist/services/setting.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +7 -2
- 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 +1 -11
- package/src/config/jwt.config.ts +13 -0
- package/src/dtos/create-list-of-values.dto.ts +1 -0
- package/src/dtos/update-list-of-values.dto.ts +1 -0
- package/src/entities/list-of-values.entity.ts +1 -1
- package/src/guards/access-token.guard.ts +1 -1
- package/src/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.ts +17 -6
- package/src/interfaces.ts +17 -1
- package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +18 -0
- package/src/seeders/seed-data/solid-core-metadata.json +238 -119
- package/src/services/authentication.service.ts +17 -17
- package/src/services/crud.service.ts +17 -31
- package/src/services/dashboard-question.service.ts +29 -2
- package/src/services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service.ts +137 -0
- package/src/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.ts +21 -7
- package/src/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.ts +72 -0
- package/src/services/list-of-values-metadata.service.ts +0 -3
- package/src/services/model-metadata.service.ts +2 -2
- package/src/services/selection-providers/list-of-models-selection-provider.service.ts +2 -2
- package/src/services/setting.service.ts +3 -2
- package/src/solid-core.module.ts +7 -1
|
@@ -15,10 +15,21 @@ import { JwtService } from '@nestjs/jwt';
|
|
|
15
15
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
|
16
16
|
import { isEmpty, isNotEmpty } from 'class-validator';
|
|
17
17
|
import { randomInt, randomUUID } from 'crypto';
|
|
18
|
-
import
|
|
18
|
+
import commonConfig from 'src/config/common.config';
|
|
19
|
+
import { jwtConfig } from 'src/config/jwt.config';
|
|
20
|
+
import { ERROR_MESSAGES } from 'src/constants/error-messages';
|
|
21
|
+
import { SUCCESS_MESSAGES } from 'src/constants/success-messages';
|
|
22
|
+
import { CreateUserDto } from 'src/dtos/create-user.dto';
|
|
23
|
+
import { MailFactory } from 'src/factories/mail.factory';
|
|
19
24
|
import { Msg91OTPService } from 'src/services/sms/Msg91OTPService';
|
|
20
25
|
import { DataSource, Repository } from 'typeorm';
|
|
21
|
-
import {
|
|
26
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
27
|
+
import { iamConfig } from '../config/iam.config';
|
|
28
|
+
import {
|
|
29
|
+
ForgotPasswordSendVerificationTokenOn,
|
|
30
|
+
RegistrationValidationSource,
|
|
31
|
+
TransactionalRegistrationValidationSource
|
|
32
|
+
} from "../constants";
|
|
22
33
|
import { ChangePasswordDto } from "../dtos/change-password.dto";
|
|
23
34
|
import { ConfirmForgotPasswordDto } from '../dtos/confirm-forgot-password.dto';
|
|
24
35
|
import { InitiateForgotPasswordDto } from '../dtos/initiate-forgot-password.dto';
|
|
@@ -29,26 +40,15 @@ import { RefreshTokenDto } from '../dtos/refresh-token.dto';
|
|
|
29
40
|
import { SignInDto } from '../dtos/sign-in.dto';
|
|
30
41
|
import { SignUpDto } from '../dtos/sign-up.dto';
|
|
31
42
|
import { User } from '../entities/user.entity';
|
|
43
|
+
import { EventDetails, EventType } from "../interfaces";
|
|
32
44
|
import { ActiveUserData } from '../interfaces/active-user-data.interface';
|
|
33
45
|
import { HashingService } from './hashing.service';
|
|
34
46
|
import { InvalidatedRefreshTokenError, RefreshTokenIdsStorageService } from './refresh-token-ids-storage.service';
|
|
35
|
-
import {
|
|
36
|
-
import { EventDetails, EventType, IMail } from "../interfaces";
|
|
37
|
-
import {
|
|
38
|
-
ForgotPasswordSendVerificationTokenOn,
|
|
39
|
-
RegistrationValidationSource,
|
|
40
|
-
TransactionalRegistrationValidationSource
|
|
41
|
-
} from "../constants";
|
|
42
|
-
import { SettingService } from './setting.service';
|
|
43
|
-
import { CreateUserDto } from 'src/dtos/create-user.dto';
|
|
47
|
+
import { RequestContextService } from './request-context.service';
|
|
44
48
|
import { RoleMetadataService } from './role-metadata.service';
|
|
45
|
-
import
|
|
49
|
+
import { SettingService } from './setting.service';
|
|
46
50
|
import { UserActivityHistoryService } from './user-activity-history.service';
|
|
47
|
-
import {
|
|
48
|
-
import { ERROR_MESSAGES } from 'src/constants/error-messages';
|
|
49
|
-
import { SUCCESS_MESSAGES } from 'src/constants/success-messages';
|
|
50
|
-
import { MailFactory } from 'src/factories/mail.factory';
|
|
51
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
51
|
+
import { UserService } from './user.service';
|
|
52
52
|
|
|
53
53
|
enum LoginProvider {
|
|
54
54
|
LOCAL = 'local',
|
|
@@ -655,14 +655,9 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
655
655
|
return entity;
|
|
656
656
|
}
|
|
657
657
|
|
|
658
|
-
async
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
// if (createDtos.length !== filesArray.length) {
|
|
662
|
-
// throw new BadRequestException('Mismatch between data objects and file arrays.');
|
|
663
|
-
// }
|
|
664
|
-
|
|
658
|
+
async createMany(createDtos: any[], solidRequestContext: any = {}): Promise<T[]> {
|
|
665
659
|
const loadedmodel = await this.loadModel();
|
|
660
|
+
|
|
666
661
|
// Check wheather user has create permission for model
|
|
667
662
|
if (solidRequestContext.activeUser) {
|
|
668
663
|
const hasPermission = this.crudHelperService.hasCreatePermissionOnModel(solidRequestContext.activeUser, loadedmodel.singularName);
|
|
@@ -680,37 +675,28 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
|
|
|
680
675
|
module: true,
|
|
681
676
|
});
|
|
682
677
|
|
|
683
|
-
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
let
|
|
687
|
-
|
|
688
|
-
// Process each field
|
|
678
|
+
const entitiesForSave: T[] = [];
|
|
679
|
+
for (const createDto of createDtos) {
|
|
680
|
+
// Validate and transform each createDto sequentially
|
|
681
|
+
let transformedDto = createDto;
|
|
689
682
|
for (const field of model.fields) {
|
|
690
683
|
const fieldManager: FieldCrudManager = await this.fieldCrudManager(field, this.entityManager);
|
|
691
|
-
const validationErrors = await fieldManager.validate(createDto,
|
|
684
|
+
const validationErrors = await fieldManager.validate(createDto, []); // TODO, This is set, because we are not supporting files for insertMany currently
|
|
692
685
|
if (validationErrors.length > 0) {
|
|
693
686
|
throw new BadRequestException(`Validation errors in ${field.name} are invalid: ${validationErrors.map(e => e.error).join(', ')}`);
|
|
694
687
|
}
|
|
695
|
-
|
|
696
|
-
hasMediaFields = hasMediaFields || field.type === 'mediaSingle' || field.type === 'mediaMultiple';
|
|
688
|
+
transformedDto = await fieldManager.transformForCreate(createDto);
|
|
697
689
|
}
|
|
690
|
+
const entity = this.repo.create(transformedDto);
|
|
691
|
+
entitiesForSave.push(entity as T);
|
|
692
|
+
}
|
|
693
|
+
// Save all entities in a single batch
|
|
694
|
+
const savedEntities = await this.repo.save(entitiesForSave) as T[];
|
|
695
|
+
return savedEntities;
|
|
696
|
+
}
|
|
698
697
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const savedEntity = await this.repo.save(entity) as T;
|
|
702
|
-
|
|
703
|
-
//Commented since currently Files are not supported for insertmany
|
|
704
|
-
// if (hasMediaFields) {
|
|
705
|
-
// await this.saveMedia(model, files, savedEntity);
|
|
706
|
-
// }
|
|
707
|
-
|
|
708
|
-
return savedEntity;
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
// Await all promises in parallel
|
|
712
|
-
const savedEntities = await Promise.all(createAndSavePromises);
|
|
713
|
-
|
|
698
|
+
async insertMany(createDtos: any[], filesArray: Express.Multer.File[][] = [], solidRequestContext: any = {}): Promise<T[]> {
|
|
699
|
+
const savedEntities = await this.createMany(createDtos, solidRequestContext);
|
|
714
700
|
return savedEntities;
|
|
715
701
|
}
|
|
716
702
|
|
|
@@ -82,6 +82,7 @@ export class DashboardQuestionService extends CRUDService<DashboardQuestion> {
|
|
|
82
82
|
|
|
83
83
|
private getExpressions(isPreview: boolean, dashboardVariables: DashboardVariable[], inputExpressions: SqlExpression[]) {
|
|
84
84
|
const expressions: SqlExpression[] = [];
|
|
85
|
+
|
|
85
86
|
if (isPreview) {
|
|
86
87
|
// Convert the dashboard variables into objects of interface type SqlExpression using the default value, default operator and the variable name
|
|
87
88
|
const expr: SqlExpression[] = dashboardVariables.map(variable => {
|
|
@@ -94,9 +95,35 @@ export class DashboardQuestionService extends CRUDService<DashboardQuestion> {
|
|
|
94
95
|
expressions.push(...expr);
|
|
95
96
|
}
|
|
96
97
|
else {
|
|
97
|
-
|
|
98
|
+
// Loop through the dashboard variables and see if there is a matching input expression
|
|
99
|
+
// If there is, use that expression instead of the default value
|
|
100
|
+
for (const variable of dashboardVariables) {
|
|
101
|
+
const matchingInputExpression = inputExpressions.find(expr => expr.variableName === variable.variableName);
|
|
102
|
+
if (matchingInputExpression) {
|
|
103
|
+
expressions.push(matchingInputExpression);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
expressions.push({
|
|
107
|
+
variableName: variable.variableName,
|
|
108
|
+
operator: variable.defaultOperator as SqlExpressionOperator,
|
|
109
|
+
value: JSON.parse(variable.defaultValue || '[]'),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
expressions.push(...expressions);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Remove duplicate expressions based on variableName in the expressions array
|
|
117
|
+
const deduplicatedExpressions: SqlExpression[] = [];
|
|
118
|
+
const variableNames = new Set<string>();
|
|
119
|
+
for (const expr of expressions) {
|
|
120
|
+
if (!variableNames.has(expr.variableName)) {
|
|
121
|
+
deduplicatedExpressions.push(expr);
|
|
122
|
+
variableNames.add(expr.variableName);
|
|
123
|
+
}
|
|
98
124
|
}
|
|
99
|
-
|
|
125
|
+
|
|
126
|
+
return deduplicatedExpressions;
|
|
100
127
|
}
|
|
101
128
|
|
|
102
129
|
private async loadQuestion(id: number) {
|
|
@@ -0,0 +1,137 @@
|
|
|
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 SolidAddButtonToFormViewMcpHandler implements IMcpToolResponseHandler {
|
|
12
|
+
private readonly logger = new Logger(SolidAddButtonToFormViewMcpHandler.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("SolidAddButtonToFormViewMcpHandler: 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 "addFormViewButton": {
|
|
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 === "form" &&
|
|
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
|
+
let buttonContent = step.content;
|
|
88
|
+
// Parse only if it’s a string
|
|
89
|
+
if (typeof buttonContent === "string") {
|
|
90
|
+
try {
|
|
91
|
+
buttonContent = JSON.parse(buttonContent);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
this.logger.error("❌ Failed to parse step.content JSON:", err);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Push the new button content
|
|
98
|
+
view.layout.attrs[step.buttonType].push(buttonContent);
|
|
99
|
+
|
|
100
|
+
console.log(`Added ${step.buttonType} to view: ${view.name}`);
|
|
101
|
+
} else {
|
|
102
|
+
console.warn(`No matching list view found for module=${step.moduleName} and model=${step.modelName}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const updatedContent = JSON.stringify(metaData, null, 2);
|
|
106
|
+
await fs.writeFile(filePath, updatedContent);
|
|
107
|
+
this.logger.log(`Updated list view in ${filePath}`);
|
|
108
|
+
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
default:
|
|
112
|
+
throw new Error(`Unsupported plan step type: ${(step as any).type}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const result = await this.tsMorph.commit();
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
seedingRequired: true,
|
|
120
|
+
serverRebooting: false,
|
|
121
|
+
};
|
|
122
|
+
} catch (err) {
|
|
123
|
+
this.logger.error(`Apply failed; rolling back. ${String(err)}`);
|
|
124
|
+
this.tsMorph.rollback();
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private safeParse(str: string): any {
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(str);
|
|
132
|
+
} catch {
|
|
133
|
+
const unescaped = str.replace(/\\'/g, "'");
|
|
134
|
+
return JSON.parse(unescaped);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
package/src/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Injectable, Logger } from "@nestjs/common";
|
|
2
2
|
import { AiInteraction } from "src/entities/ai-interaction.entity";
|
|
3
|
-
import { IMcpToolResponseHandler, McpComputedProviderResponse, PlanStep } from "../../../interfaces";
|
|
3
|
+
import { AddMethodToExistingClassStep, IMcpToolResponseHandler, McpComputedProviderResponse, PlanStep } from "../../../interfaces";
|
|
4
4
|
import { SolidTsMorphService } from "src/services/solid-ts-morph.service";
|
|
5
5
|
|
|
6
6
|
@Injectable()
|
|
@@ -28,12 +28,7 @@ export class SolidAddControllerHandlerMcpHandler implements IMcpToolResponseHand
|
|
|
28
28
|
break;
|
|
29
29
|
}
|
|
30
30
|
case "addMethodToExistingClass": {
|
|
31
|
-
this.
|
|
32
|
-
step.path,
|
|
33
|
-
step.className,
|
|
34
|
-
step.methodName,
|
|
35
|
-
step.content,
|
|
36
|
-
);
|
|
31
|
+
this.handleAddMethodToExistingClass(step);
|
|
37
32
|
break;
|
|
38
33
|
}
|
|
39
34
|
default:
|
|
@@ -64,4 +59,23 @@ export class SolidAddControllerHandlerMcpHandler implements IMcpToolResponseHand
|
|
|
64
59
|
return JSON.parse(unescaped);
|
|
65
60
|
}
|
|
66
61
|
}
|
|
62
|
+
|
|
63
|
+
private handleAddMethodToExistingClass(step: PlanStep) {
|
|
64
|
+
// Cast step to the appropriate type if necessary
|
|
65
|
+
const addMethodStep = step as AddMethodToExistingClassStep
|
|
66
|
+
// Add the import statement to the specified file
|
|
67
|
+
if (addMethodStep.importStatements && addMethodStep.importStatements.length > 0) {
|
|
68
|
+
this.tsMorph.addImport(
|
|
69
|
+
addMethodStep.path,
|
|
70
|
+
(addMethodStep.importStatements || []).join('\n'),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
// Add the method content to the specified class in the file
|
|
74
|
+
this.tsMorph.addMethodToExistingClass(
|
|
75
|
+
addMethodStep.path,
|
|
76
|
+
addMethodStep.className,
|
|
77
|
+
addMethodStep.methodName,
|
|
78
|
+
addMethodStep.content,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
67
81
|
}
|
package/src/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
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 SolidCreateCustomFormViewWidgetMcpHandler implements IMcpToolResponseHandler {
|
|
10
|
+
private readonly logger = new Logger(SolidCreateCustomFormViewWidgetMcpHandler.name);
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly tsMorph: SolidTsMorphService,
|
|
14
|
+
|
|
15
|
+
) { }
|
|
16
|
+
|
|
17
|
+
async apply(aiInteraction: AiInteraction) {
|
|
18
|
+
const raw = this.safeParse(aiInteraction.message);
|
|
19
|
+
const payload: McpComputedProviderResponse | undefined = (raw?.data?.plan ? raw.data : raw) as McpComputedProviderResponse;
|
|
20
|
+
|
|
21
|
+
if (!payload || !Array.isArray(payload.plan)) {
|
|
22
|
+
throw new Error("SolidCreateCustomFormViewWidgetMcpHandler: invalid MCP response; missing plan[]");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Batch all plan steps in a single txn so nodemon restarts only once.
|
|
26
|
+
this.tsMorph.begin();
|
|
27
|
+
try {
|
|
28
|
+
for (const step of payload.plan as PlanStep[]) {
|
|
29
|
+
switch (step.type) {
|
|
30
|
+
case "createNewFile": {
|
|
31
|
+
const overwrite = step.overwrite ?? false;
|
|
32
|
+
this.tsMorph.createNewFile(step.path, step.content, overwrite);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case "registerSolidXExtensionComponent": {
|
|
36
|
+
this.tsMorph.registerSolidUiExtension(
|
|
37
|
+
step.path,
|
|
38
|
+
step.content,
|
|
39
|
+
);
|
|
40
|
+
this.tsMorph.addImport(
|
|
41
|
+
step.path,
|
|
42
|
+
step.importExtensionComponent,
|
|
43
|
+
);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
default:
|
|
47
|
+
throw new Error(`Unsupported plan step type: ${(step as any).type}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = await this.tsMorph.commit();
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
seedingRequired: true,
|
|
55
|
+
serverRebooting: false,
|
|
56
|
+
};
|
|
57
|
+
} catch (err) {
|
|
58
|
+
this.logger.error(`Apply failed; rolling back. ${String(err)}`);
|
|
59
|
+
this.tsMorph.rollback();
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private safeParse(str: string): any {
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(str);
|
|
67
|
+
} catch {
|
|
68
|
+
const unescaped = str.replace(/\\'/g, "'");
|
|
69
|
+
return JSON.parse(unescaped);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -111,8 +111,6 @@ export class ListOfValuesMetadataService extends CRUDService<ListOfValues> {
|
|
|
111
111
|
await this.deleteFromConfig(metaData, listofvalue, filePath);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
114
|
private async getConfigFileDetails(moduleName: string): Promise<{ filePath: string; metaData: any }> {
|
|
117
115
|
const filePath = await this.moduleMetadataHelperService.getModuleMetadataFilePath(moduleName);
|
|
118
116
|
try {
|
|
@@ -185,7 +183,6 @@ export class ListOfValuesMetadataService extends CRUDService<ListOfValues> {
|
|
|
185
183
|
await fs.writeFile(filePath, updatedContent);
|
|
186
184
|
}
|
|
187
185
|
|
|
188
|
-
|
|
189
186
|
private async deleteFromConfig(metaData: any, listofvalues: ListOfValues, filePath: string) {
|
|
190
187
|
const dto = await this.listOfValuesMapper.toDto(listofvalues);
|
|
191
188
|
|
|
@@ -1045,8 +1045,8 @@ export class ModelMetadataService {
|
|
|
1045
1045
|
let view = await viewRepo.findOne({ where: { name: `${model.singularName}-list-view` } });
|
|
1046
1046
|
|
|
1047
1047
|
const actionData = {
|
|
1048
|
-
displayName: `${model.displayName} List
|
|
1049
|
-
name: `${model.singularName}-list-
|
|
1048
|
+
displayName: `${model.displayName} List Action`,
|
|
1049
|
+
name: `${model.singularName}-list-action`,
|
|
1050
1050
|
type: "solid",
|
|
1051
1051
|
domain: "" as any,
|
|
1052
1052
|
context: "" as any,
|
|
@@ -33,7 +33,7 @@ export class ListOfModelsSelectionProvider implements ISelectionProvider<ISelect
|
|
|
33
33
|
const models = (await this.modelMetadataService.findMany(queryData)).records;
|
|
34
34
|
|
|
35
35
|
const model = models.filter(i => i.singularName === optionValue)[0];
|
|
36
|
-
return { label: model.
|
|
36
|
+
return { label: model.displayName, value: model.singularName }
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
async values(query: string, ctxt: ISelectionProviderContext): Promise<readonly ISelectionProviderValues[]> {
|
|
@@ -63,7 +63,7 @@ export class ListOfModelsSelectionProvider implements ISelectionProvider<ISelect
|
|
|
63
63
|
// });
|
|
64
64
|
|
|
65
65
|
return filteredModels.map(i => {
|
|
66
|
-
return { label: i.
|
|
66
|
+
return { label: i.displayName, value: i.singularName };
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
}
|
|
@@ -77,7 +77,8 @@ export class SettingService extends CRUDService<Setting> {
|
|
|
77
77
|
defaultProvider: "",
|
|
78
78
|
availableProviders: []
|
|
79
79
|
}),
|
|
80
|
-
showNameFieldsForRegistration:this.iamConfiguration.showNameFieldsForRegistration
|
|
80
|
+
showNameFieldsForRegistration:this.iamConfiguration.showNameFieldsForRegistration,
|
|
81
|
+
forceChangePasswordOnFirstLogin: this.iamConfiguration.forceChangePasswordOnFirstLogin
|
|
81
82
|
};
|
|
82
83
|
|
|
83
84
|
const existingSettings = await this.repo.find();
|
|
@@ -175,7 +176,7 @@ export class SettingService extends CRUDService<Setting> {
|
|
|
175
176
|
shouldQueueSms: this.commonConfiguration.shouldQueueSms,
|
|
176
177
|
enableDarkMode: true,
|
|
177
178
|
copyright: null,
|
|
178
|
-
forceChangePasswordOnFirstLogin:
|
|
179
|
+
forceChangePasswordOnFirstLogin: this.iamConfiguration.forceChangePasswordOnFirstLogin,
|
|
179
180
|
enableUsername: true,
|
|
180
181
|
enabledNotification: true,
|
|
181
182
|
contactSupportEmail : null,
|
package/src/solid-core.module.ts
CHANGED
|
@@ -57,7 +57,8 @@ import { HttpModule } from '@nestjs/axios';
|
|
|
57
57
|
import { JwtModule } from '@nestjs/jwt';
|
|
58
58
|
import { SeedCommand } from './commands/seed.command';
|
|
59
59
|
import commonConfig from './config/common.config';
|
|
60
|
-
import { iamConfig
|
|
60
|
+
import { iamConfig } from './config/iam.config';
|
|
61
|
+
import { jwtConfig } from './config/jwt.config';
|
|
61
62
|
import { AuthenticationController } from './controllers/authentication.controller';
|
|
62
63
|
import { EmailTemplateController } from './controllers/email-template.controller';
|
|
63
64
|
import { GoogleAuthenticationController } from './controllers/google-authentication.controller';
|
|
@@ -300,6 +301,8 @@ import { SolidAddQuestionToDashboardMcpHandler } from './services/genai/mcp-hand
|
|
|
300
301
|
import { SolidAddCustomServiceMethodMcpHandler } from './services/genai/mcp-handlers/solid-add-custom-service-method-mcp-handler.service';
|
|
301
302
|
import { SolidAddHeaderButtonOrRowButtonToListViewMcpHandler } from './services/genai/mcp-handlers/solid-add-header-button-or-row-button-to-list-view-mcp-handler.service';
|
|
302
303
|
import { SolidAddControllerHandlerMcpHandler } from './services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service';
|
|
304
|
+
import { SolidAddButtonToFormViewMcpHandler } from './services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service';
|
|
305
|
+
import { SolidCreateCustomFormViewWidgetMcpHandler } from './services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service';
|
|
303
306
|
|
|
304
307
|
|
|
305
308
|
@Global()
|
|
@@ -649,6 +652,9 @@ import { SolidAddControllerHandlerMcpHandler } from './services/genai/mcp-handle
|
|
|
649
652
|
SolidAddCustomServiceMethodMcpHandler,
|
|
650
653
|
SolidAddHeaderButtonOrRowButtonToListViewMcpHandler,
|
|
651
654
|
SolidAddControllerHandlerMcpHandler,
|
|
655
|
+
SolidAddButtonToFormViewMcpHandler,
|
|
656
|
+
SolidCreateCustomFormViewWidgetMcpHandler,
|
|
657
|
+
|
|
652
658
|
SolidTsMorphService,
|
|
653
659
|
|
|
654
660
|
ViewMetadataRepository,
|