@solidstarters/solid-core 1.2.186 → 1.2.188
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/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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.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/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/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/guards/access-token.guard.ts +1 -1
- package/src/index.ts +1 -0
- package/src/interfaces.ts +17 -1
- package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +18 -0
- 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/setting.service.ts +3 -2
- package/src/solid-core.module.ts +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.188",
|
|
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",
|
package/src/config/iam.config.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { registerAs } from '@nestjs/config';
|
|
2
|
-
import { Environment } from 'src';
|
|
3
2
|
|
|
4
3
|
export const iamConfig = registerAs('iam', () => {
|
|
5
4
|
return {
|
|
@@ -14,7 +13,7 @@ export const iamConfig = registerAs('iam', () => {
|
|
|
14
13
|
defaultRole: process.env.IAM_DEFAULT_ROLE ?? 'Public',
|
|
15
14
|
dummyOtp: process.env.IAM_OTP_DUMMY,
|
|
16
15
|
forgotPasswordSendVerificationTokenOn: process.env.IAM_FORGOT_PASSWORD_SEND_VERIFICATION_TOKEN_ON ?? 'email',
|
|
17
|
-
forceChangePasswordOnFirstLogin:
|
|
16
|
+
forceChangePasswordOnFirstLogin:false,
|
|
18
17
|
PASSWORD_REGEX: process.env.PASSWORD_REGEX || '^$|^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[^\\da-zA-Z]).*$',
|
|
19
18
|
PASSWORD_COMPLEXITY_DESC : process.env.PASSWORD_COMPLEXITY_DESC || 'Password must contain at least one uppercase, one lowercase, one number, and one special character.',
|
|
20
19
|
googleOauth: {
|
|
@@ -29,12 +28,3 @@ export const iamConfig = registerAs('iam', () => {
|
|
|
29
28
|
};
|
|
30
29
|
})
|
|
31
30
|
|
|
32
|
-
export const jwtConfig = registerAs('jwt', () => {
|
|
33
|
-
return {
|
|
34
|
-
secret: process.env.IAM_JWT_SECRET,
|
|
35
|
-
audience: process.env.IAM_JWT_TOKEN_AUDIENCE,
|
|
36
|
-
issuer: process.env.IAM_JWT_TOKEN_ISSUER,
|
|
37
|
-
accessTokenTtl: parseInt(process.env.IAM_JWT_ACCESS_TOKEN_TTL ?? (process.env.ENV as Environment) === Environment.Production ? '1200' : '86400', 10), // 20 minutes in prod, 1 day otherwise
|
|
38
|
-
refreshTokenTtl: parseInt(process.env.IAM_JWT_REFRESH_TOKEN_TTL ?? '604800', 10), // 7 days
|
|
39
|
-
};
|
|
40
|
-
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { registerAs } from '@nestjs/config';
|
|
2
|
+
import { Environment } from 'src/decorators/disallow-in-production.decorator';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export const jwtConfig = registerAs('jwt', () => {
|
|
6
|
+
return {
|
|
7
|
+
secret: process.env.IAM_JWT_SECRET,
|
|
8
|
+
audience: process.env.IAM_JWT_TOKEN_AUDIENCE,
|
|
9
|
+
issuer: process.env.IAM_JWT_TOKEN_ISSUER,
|
|
10
|
+
accessTokenTtl: parseInt(process.env.IAM_JWT_ACCESS_TOKEN_TTL ?? (process.env.ENV as Environment) === Environment.Production ? '1200' : '86400', 10), // 20 minutes in prod, 1 day otherwise
|
|
11
|
+
refreshTokenTtl: parseInt(process.env.IAM_JWT_REFRESH_TOKEN_TTL ?? '604800', 10), // 7 days
|
|
12
|
+
};
|
|
13
|
+
});
|
|
@@ -9,7 +9,7 @@ import { ConfigType } from '@nestjs/config';
|
|
|
9
9
|
import { JwtService } from '@nestjs/jwt';
|
|
10
10
|
import { Request } from 'express';
|
|
11
11
|
import { ActiveUserData } from '../interfaces/active-user-data.interface';
|
|
12
|
-
import { jwtConfig } from '
|
|
12
|
+
import { jwtConfig } from 'src/config/jwt.config';
|
|
13
13
|
import { REQUEST_USER_KEY } from "../constants";
|
|
14
14
|
import { PermissionMetadataService } from '../services/permission-metadata.service';
|
|
15
15
|
import { ClsService } from 'nestjs-cls';
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ export * from './decorators/solid-database-module.decorator'
|
|
|
24
24
|
export * from './decorators/solid-service.decorator'
|
|
25
25
|
export * from './decorators/mail-provider.decorator'
|
|
26
26
|
|
|
27
|
+
export * from './dtos/post-chatter-message.dto'
|
|
27
28
|
export * from './dtos/basic-filters.dto'
|
|
28
29
|
export * from './dtos/solid-request-context.dto'
|
|
29
30
|
export * from './dtos/change-password.dto'
|
package/src/interfaces.ts
CHANGED
|
@@ -281,7 +281,14 @@ export interface IErrorCodeProvider {
|
|
|
281
281
|
|
|
282
282
|
// MCP Tool Related
|
|
283
283
|
|
|
284
|
-
export type PlanStep = CreateNewFileStep | RegisterNestProviderStep | AddMethodToExistingClassStep | RegisterSolidXExtensionComponentStep | AddListViewButtonStep;
|
|
284
|
+
export type PlanStep = CreateNewFileStep | RegisterNestProviderStep | AddMethodToExistingClassStep | RegisterSolidXExtensionComponentStep | AddListViewButtonStep | AddFormViewButtonStep | AddImportStep;
|
|
285
|
+
export interface AddImportStep {
|
|
286
|
+
type: "addImport";
|
|
287
|
+
path: string; // e.g. apps/api/src/address-master/services/address-master.service.ts
|
|
288
|
+
importStatement: string; // e.g. import { Something } from 'somewhere';
|
|
289
|
+
overwrite?: boolean; // default=false
|
|
290
|
+
rationale?: string; // optional, ignored by executor
|
|
291
|
+
}
|
|
285
292
|
|
|
286
293
|
export interface CreateNewFileStep {
|
|
287
294
|
type: "createNewFile";
|
|
@@ -307,6 +314,7 @@ export interface AddMethodToExistingClassStep {
|
|
|
307
314
|
className: string // e.g. CountryService
|
|
308
315
|
methodName: string // e.g. addCountry
|
|
309
316
|
content: string // Full Method Code
|
|
317
|
+
importStatements?: string[]; // e.g. [ "import { X } from 'y';" ]
|
|
310
318
|
rationale?: string; // optional, ignored by executor
|
|
311
319
|
}
|
|
312
320
|
|
|
@@ -325,6 +333,14 @@ export interface AddListViewButtonStep {
|
|
|
325
333
|
content?: string; // Code
|
|
326
334
|
}
|
|
327
335
|
|
|
336
|
+
export interface AddFormViewButtonStep {
|
|
337
|
+
type: "addFormViewButton";
|
|
338
|
+
moduleName?: string;
|
|
339
|
+
modelName?: string;
|
|
340
|
+
buttonType?: string;
|
|
341
|
+
content?: string; // Code
|
|
342
|
+
}
|
|
343
|
+
|
|
328
344
|
export interface McpComputedProviderResponse {
|
|
329
345
|
plan: PlanStep[];
|
|
330
346
|
// provider?: any; // (intentionally ignored per your note)
|
|
@@ -142,6 +142,10 @@ export class TriggerMcpClientSubscriberDatabase extends DatabaseSubscriber<Trigg
|
|
|
142
142
|
}
|
|
143
143
|
);
|
|
144
144
|
|
|
145
|
+
const existingControllerAndTheirMethods = this.solidRegistry.getControllers();
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
145
149
|
// Build markdown sections using template interpolation
|
|
146
150
|
const modulesSection = (existingModules ?? [])
|
|
147
151
|
.map(m => [
|
|
@@ -176,6 +180,14 @@ export class TriggerMcpClientSubscriberDatabase extends DatabaseSubscriber<Trigg
|
|
|
176
180
|
].join('\n'))
|
|
177
181
|
.join('\n\n');
|
|
178
182
|
|
|
183
|
+
const controllersAndTheirMethods = (existingControllerAndTheirMethods ?? [])
|
|
184
|
+
.map(m => [
|
|
185
|
+
`### ${m.name}`,
|
|
186
|
+
`- methods: ${m.methods.length ? m.methods.map(m => `- ${m}`).join('\n'): '- No methods found'}`,
|
|
187
|
+
].join('\n'))
|
|
188
|
+
.join('\n\n');
|
|
189
|
+
|
|
190
|
+
|
|
179
191
|
|
|
180
192
|
const finalPrompt = `
|
|
181
193
|
# User Prompt:
|
|
@@ -212,6 +224,12 @@ Use the below list of computed field providers to infer which provider the user
|
|
|
212
224
|
${computationProvidersSection}
|
|
213
225
|
`.trim();
|
|
214
226
|
|
|
227
|
+
|
|
228
|
+
// ## LIST OF EXISTING CONTROLLERS AND THEIR METHODS
|
|
229
|
+
// Use the below list of controllers and their methods to infer whether the api call being made to a particular controller exists or not.
|
|
230
|
+
|
|
231
|
+
// ${controllersAndTheirMethods}
|
|
232
|
+
|
|
215
233
|
const aiResponse = await this.aiInteractionService.runMcpPrompt(finalPrompt);
|
|
216
234
|
this.triggerMcpClientSubscriberLogger.log(`aiResponse: `);
|
|
217
235
|
this.triggerMcpClientSubscriberLogger.log(JSON.stringify(aiResponse));
|
|
@@ -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
|
+
}
|
|
@@ -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,
|