@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.
Files changed (68) hide show
  1. package/dist/config/iam.config.d.ts +0 -13
  2. package/dist/config/iam.config.d.ts.map +1 -1
  3. package/dist/config/iam.config.js +2 -12
  4. package/dist/config/iam.config.js.map +1 -1
  5. package/dist/config/jwt.config.d.ts +14 -0
  6. package/dist/config/jwt.config.d.ts.map +1 -0
  7. package/dist/config/jwt.config.js +15 -0
  8. package/dist/config/jwt.config.js.map +1 -0
  9. package/dist/decorators/active-user.decorator.d.ts +1 -1
  10. package/dist/guards/access-token.guard.d.ts +1 -1
  11. package/dist/guards/access-token.guard.d.ts.map +1 -1
  12. package/dist/guards/access-token.guard.js +2 -2
  13. package/dist/guards/access-token.guard.js.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/interfaces.d.ts +16 -1
  19. package/dist/interfaces.d.ts.map +1 -1
  20. package/dist/interfaces.js.map +1 -1
  21. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.d.ts.map +1 -1
  22. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js +7 -0
  23. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js.map +1 -1
  24. package/dist/services/authentication.service.d.ts +8 -7
  25. package/dist/services/authentication.service.d.ts.map +1 -1
  26. package/dist/services/authentication.service.js +12 -11
  27. package/dist/services/authentication.service.js.map +1 -1
  28. package/dist/services/crud.service.d.ts +1 -0
  29. package/dist/services/crud.service.d.ts.map +1 -1
  30. package/dist/services/crud.service.js +14 -12
  31. package/dist/services/crud.service.js.map +1 -1
  32. package/dist/services/dashboard-question.service.d.ts.map +1 -1
  33. package/dist/services/dashboard-question.service.js +23 -2
  34. package/dist/services/dashboard-question.service.js.map +1 -1
  35. package/dist/services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service.d.ts +16 -0
  36. package/dist/services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service.d.ts.map +1 -0
  37. package/dist/services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service.js +151 -0
  38. package/dist/services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service.js.map +1 -0
  39. package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.d.ts +1 -0
  40. package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.d.ts.map +1 -1
  41. package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.js +8 -1
  42. package/dist/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.js.map +1 -1
  43. package/dist/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.d.ts +14 -0
  44. package/dist/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.d.ts.map +1 -0
  45. package/dist/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.js +73 -0
  46. package/dist/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.js.map +1 -0
  47. package/dist/services/setting.service.d.ts.map +1 -1
  48. package/dist/services/setting.service.js +3 -2
  49. package/dist/services/setting.service.js.map +1 -1
  50. package/dist/solid-core.module.d.ts.map +1 -1
  51. package/dist/solid-core.module.js +7 -2
  52. package/dist/solid-core.module.js.map +1 -1
  53. package/dist/tsconfig.tsbuildinfo +1 -1
  54. package/package.json +1 -1
  55. package/src/config/iam.config.ts +1 -11
  56. package/src/config/jwt.config.ts +13 -0
  57. package/src/guards/access-token.guard.ts +1 -1
  58. package/src/index.ts +1 -0
  59. package/src/interfaces.ts +17 -1
  60. package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +18 -0
  61. package/src/services/authentication.service.ts +17 -17
  62. package/src/services/crud.service.ts +17 -31
  63. package/src/services/dashboard-question.service.ts +29 -2
  64. package/src/services/genai/mcp-handlers/solid-add-button-to-form-view-mcp-handler.service.ts +137 -0
  65. package/src/services/genai/mcp-handlers/solid-add-controller-handler-method-mcp-handler.service.ts +21 -7
  66. package/src/services/genai/mcp-handlers/solid-create-custom-form-view-widget-mcp-handler.service.ts +72 -0
  67. package/src/services/setting.service.ts +3 -2
  68. 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.186",
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",
@@ -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:true,
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 '../config/iam.config';
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 { SMTPEMailService } from 'src/services/mail/smtp-email.service';
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 { iamConfig, jwtConfig } from '../config/iam.config';
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 { UserService } from './user.service';
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 commonConfig from 'src/config/common.config';
49
+ import { SettingService } from './setting.service';
46
50
  import { UserActivityHistoryService } from './user-activity-history.service';
47
- import { RequestContextService } from './request-context.service';
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 insertMany(createDtos: any[], filesArray: Express.Multer.File[][] = [], solidRequestContext: any = {}): Promise<T[]> {
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
- // Process each createDto in parallel
684
- const createAndSavePromises = createDtos.map(async (createDto, index) => {
685
- const files = []; // TODO, This is set, because we are not supporting files for insertMany currently
686
- let hasMediaFields = false;
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, files);
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
- createDto = await fieldManager.transformForCreate(createDto);
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
- // Save the entity
700
- const entity = this.repo.create(createDto);
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
- expressions.push(...inputExpressions);
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
- return expressions;
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
+ }
@@ -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.tsMorph.addMethodToExistingClass(
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
  }
@@ -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: true,
179
+ forceChangePasswordOnFirstLogin: this.iamConfiguration.forceChangePasswordOnFirstLogin,
179
180
  enableUsername: true,
180
181
  enabledNotification: true,
181
182
  contactSupportEmail : null,
@@ -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, jwtConfig } from './config/iam.config';
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,