Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/src/entities/connection/connection.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ export class ConnectionController {
cognitoUserName: userId,
},
};
return await this.createGroupInConnectionUseCase.execute(inputData, InTransactionEnum.ON);
return await this.createGroupInConnectionUseCase.execute(inputData, InTransactionEnum.OFF);

Copilot AI Feb 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transaction mode for creating a group in a connection has been changed from InTransactionEnum.ON to InTransactionEnum.OFF. This change appears unrelated to the stated PR purpose of "refactor: don't wait ai settings" and could be accidental.

Removing transactions from group creation operations could lead to data inconsistency issues if the operation fails partway through. The createGroupInConnection use case creates a new group entity and saves it to the database, which should ideally be atomic.

If this change is intentional, it should be explained in the PR description or separated into a different commit with proper justification. If accidental, it should be reverted to InTransactionEnum.ON.

Copilot uses AI. Check for mistakes.
}

@ApiOperation({ summary: 'Find all groups in connection' })
Expand Down
150 changes: 48 additions & 102 deletions backend/src/entities/connection/use-cases/create-connection.use.case.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
import {
BadRequestException,
Inject,
Injectable,
InternalServerErrorException,
Scope,
} from "@nestjs/common";
import { getDataAccessObject } from "@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js";
import AbstractUseCase from "../../../common/abstract-use.case.js";
import { IGlobalDatabaseContext } from "../../../common/application/global-database-context.interface.js";
import { BaseType } from "../../../common/data-injection.tokens.js";
import { Messages } from "../../../exceptions/text/messages.js";
import { Encryptor } from "../../../helpers/encryption/encryptor.js";
import {
isConnectionTypeAgent,
slackPostMessage,
} from "../../../helpers/index.js";
import { SharedJobsService } from "../../shared-jobs/shared-jobs.service.js";
import { UserRoleEnum } from "../../user/enums/user-role.enum.js";
import { UserEntity } from "../../user/user.entity.js";
import { CreateConnectionDs } from "../application/data-structures/create-connection.ds.js";
import { CreatedConnectionDTO } from "../application/dto/created-connection.dto.js";
import { ConnectionEntity } from "../connection.entity.js";
import { buildConnectionEntity } from "../utils/build-connection-entity.js";
import { buildCreatedConnectionDs } from "../utils/build-created-connection.ds.js";
import { processAWSConnection } from "../utils/process-aws-connection.util.js";
import { validateCreateConnectionData } from "../utils/validate-create-connection-data.js";
import { ICreateConnection } from "./use-cases.interfaces.js";
import { BadRequestException, Inject, Injectable, InternalServerErrorException, Scope } from '@nestjs/common';
import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js';
import * as Sentry from '@sentry/node';
import AbstractUseCase from '../../../common/abstract-use.case.js';
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
import { BaseType } from '../../../common/data-injection.tokens.js';
import { Messages } from '../../../exceptions/text/messages.js';
import { Encryptor } from '../../../helpers/encryption/encryptor.js';
import { isConnectionTypeAgent, slackPostMessage } from '../../../helpers/index.js';
import { SharedJobsService } from '../../shared-jobs/shared-jobs.service.js';
import { UserRoleEnum } from '../../user/enums/user-role.enum.js';
import { UserEntity } from '../../user/user.entity.js';
import { CreateConnectionDs } from '../application/data-structures/create-connection.ds.js';
import { CreatedConnectionDTO } from '../application/dto/created-connection.dto.js';
import { ConnectionEntity } from '../connection.entity.js';
import { buildConnectionEntity } from '../utils/build-connection-entity.js';
import { buildCreatedConnectionDs } from '../utils/build-created-connection.ds.js';
import { processAWSConnection } from '../utils/process-aws-connection.util.js';
import { validateCreateConnectionData } from '../utils/validate-create-connection-data.js';
import { ICreateConnection } from './use-cases.interfaces.js';

@Injectable({ scope: Scope.REQUEST })
export class CreateConnectionUseCase
Expand All @@ -39,41 +31,28 @@ export class CreateConnectionUseCase
) {
super();
}
protected async implementation(
createConnectionData: CreateConnectionDs,
): Promise<CreatedConnectionDTO> {
protected async implementation(createConnectionData: CreateConnectionDs): Promise<CreatedConnectionDTO> {
const {
creation_info: { authorId, masterPwd },
} = createConnectionData;
const connectionAuthor: UserEntity =
await this._dbContext.userRepository.findOneUserById(authorId);
const connectionAuthor: UserEntity = await this._dbContext.userRepository.findOneUserById(authorId);

if (!connectionAuthor) {
throw new InternalServerErrorException(Messages.USER_NOT_FOUND);
}

if (
connectionAuthor.role !== UserRoleEnum.ADMIN &&
connectionAuthor.role !== UserRoleEnum.DB_ADMIN
) {
throw new BadRequestException(
Messages.CANT_CREATE_CONNECTION_USER_NON_COMPANY_ADMIN,
);
if (connectionAuthor.role !== UserRoleEnum.ADMIN && connectionAuthor.role !== UserRoleEnum.DB_ADMIN) {
throw new BadRequestException(Messages.CANT_CREATE_CONNECTION_USER_NON_COMPANY_ADMIN);
}

await slackPostMessage(
Messages.USER_TRY_CREATE_CONNECTION(
connectionAuthor.email,
createConnectionData.connection_parameters.type,
),
Messages.USER_TRY_CREATE_CONNECTION(connectionAuthor.email, createConnectionData.connection_parameters.type),
);
await validateCreateConnectionData(createConnectionData);

createConnectionData = await processAWSConnection(createConnectionData);
let isConnectionTestedSuccessfully: boolean = false;
if (
!isConnectionTypeAgent(createConnectionData.connection_parameters.type)
) {
if (!isConnectionTypeAgent(createConnectionData.connection_parameters.type)) {
const connectionParamsCopy = {
...createConnectionData.connection_parameters,
};
Expand All @@ -84,10 +63,7 @@ export class CreateConnectionUseCase
} catch (e) {
const text: string = e.message.toLowerCase();
isConnectionTestedSuccessfully = false;
if (
text.includes("ssl required") ||
text.includes("ssl connection required")
) {
if (text.includes('ssl required') || text.includes('ssl connection required')) {
createConnectionData.connection_parameters.ssl = true;
connectionParamsCopy.ssl = true;
try {
Expand All @@ -104,79 +80,49 @@ export class CreateConnectionUseCase
}
let connectionCopy: ConnectionEntity = null;
try {
const createdConnection: ConnectionEntity = await buildConnectionEntity(
createConnectionData,
connectionAuthor,
);
const createdConnection: ConnectionEntity = await buildConnectionEntity(createConnectionData, connectionAuthor);
const savedConnection: ConnectionEntity =
await this._dbContext.connectionRepository.saveNewConnection(
createdConnection,
);
await this._dbContext.connectionRepository.saveNewConnection(createdConnection);

connectionCopy = { ...savedConnection } as ConnectionEntity;
if (
savedConnection.masterEncryption &&
masterPwd &&
!isConnectionTypeAgent(savedConnection.type)
) {
connectionCopy = Encryptor.decryptConnectionCredentials(
connectionCopy,
masterPwd,
);
if (savedConnection.masterEncryption && masterPwd && !isConnectionTypeAgent(savedConnection.type)) {
connectionCopy = Encryptor.decryptConnectionCredentials(connectionCopy, masterPwd);
}

let token: string;
if (isConnectionTypeAgent(savedConnection.type)) {
token =
await this._dbContext.agentRepository.createNewAgentForConnectionAndReturnToken(
savedConnection,
);
token = await this._dbContext.agentRepository.createNewAgentForConnectionAndReturnToken(savedConnection);
}
const createdAdminGroup =
await this._dbContext.groupRepository.createdAdminGroupInConnection(
savedConnection,
connectionAuthor,
);
await this._dbContext.permissionRepository.createdDefaultAdminPermissionsInGroup(
createdAdminGroup,
const createdAdminGroup = await this._dbContext.groupRepository.createdAdminGroupInConnection(
savedConnection,
connectionAuthor,
);
await this._dbContext.permissionRepository.createdDefaultAdminPermissionsInGroup(createdAdminGroup);
delete createdAdminGroup.connection;
await this._dbContext.userRepository.saveUserEntity(connectionAuthor);
createdConnection.groups = [createdAdminGroup];
const foundUserCompany =
await this._dbContext.companyInfoRepository.findOneCompanyInfoByUserIdWithConnections(
connectionAuthor.id,
);
const foundUserCompany = await this._dbContext.companyInfoRepository.findOneCompanyInfoByUserIdWithConnections(
connectionAuthor.id,
);
if (foundUserCompany) {
const connection = await this._dbContext.connectionRepository.findOne({
where: { id: savedConnection.id },
});
connection.company = foundUserCompany;
await this._dbContext.connectionRepository.saveUpdatedConnection(
connection,
);
await this._dbContext.connectionRepository.saveUpdatedConnection(connection);
}
await slackPostMessage(
Messages.USER_CREATED_CONNECTION(
connectionAuthor.email,
createConnectionData.connection_parameters.type,
),
);
const connectionRO = buildCreatedConnectionDs(
savedConnection,
token,
masterPwd,
Messages.USER_CREATED_CONNECTION(connectionAuthor.email, createConnectionData.connection_parameters.type),
);
const connectionRO = buildCreatedConnectionDs(savedConnection, token, masterPwd);
return connectionRO;
} finally {
if (
isConnectionTestedSuccessfully &&
!isConnectionTypeAgent(connectionCopy.type)
) {
// await this.sharedJobsService.scanDatabaseAndCreateWidgets(connectionCopy);
await this.sharedJobsService.scanDatabaseAndCreateSettingsAndWidgetsWithAI(
connectionCopy,
);
if (isConnectionTestedSuccessfully && !isConnectionTypeAgent(connectionCopy.type)) {

Copilot AI Feb 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fire-and-forget logic in the finally block could throw an error if connectionCopy remains null. If an exception occurs in the try block before line 87 (where connectionCopy is assigned), the finally block will execute with connectionCopy still being null, causing connectionCopy.type to throw a TypeError.

Consider adding a null check for connectionCopy before accessing its properties.

Suggested change
if (isConnectionTestedSuccessfully && !isConnectionTypeAgent(connectionCopy.type)) {
if (connectionCopy && isConnectionTestedSuccessfully && !isConnectionTypeAgent(connectionCopy.type)) {

Copilot uses AI. Check for mistakes.
// Fire-and-forget: run AI scan in background without blocking response
this.sharedJobsService.scanDatabaseAndCreateSettingsAndWidgetsWithAI(connectionCopy).catch((error) => {
console.error('Background AI scan failed:', error);
Sentry.captureException(error);
});
}
}
}
Expand Down
Loading