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: 2 additions & 0 deletions backend/src/common/data-injection.tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ export enum UseCaseType {
FREEZE_CONNECTIONS_IN_COMPANY = 'FREEZE_CONNECTIONS_IN_COMPANY',
UNFREEZE_CONNECTIONS_IN_COMPANY = 'UNFREEZE_CONNECTIONS_IN_COMPANY',
SAAS_REGISTER_USER_WITH_SAML = 'SAAS_REGISTER_USER_WITH_SAML',
SAAS_CREATE_CONNECTION_FOR_HOSTED_DB = 'SAAS_CREATE_CONNECTION_FOR_HOSTED_DB',
SAAS_DELETE_CONNECTION_FOR_HOSTED_DB = 'SAAS_DELETE_CONNECTION_FOR_HOSTED_DB',

INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsString, IsUUID, Max, Min } from 'class-validator';

export class CreateConnectionForHostedDbDto {
@ApiProperty({
description: 'Company ID',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@IsString()
@IsNotEmpty()
@IsUUID()
companyId: string;

@ApiProperty({
description: 'User ID',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@IsString()
@IsNotEmpty()
@IsUUID()
userId: string;

@ApiProperty({
description: 'Database name',
example: 'my_database',
})
@IsString()
@IsNotEmpty()
databaseName: string;

@ApiProperty({
description: 'Database hostname',
example: 'localhost',
})
@IsString()
@IsNotEmpty()
hostname: string;

@ApiProperty({
description: 'Database port',
example: 5432,
})
@IsNotEmpty()
@IsNumber()
@Max(65535)
@Min(1)
port: number;

@ApiProperty({
description: 'Database username',
example: 'db_user',
})
@IsString()
@IsNotEmpty()
username: string;

@ApiProperty({
description: 'Database password',
example: 'secure_password',
})
@IsString()
@IsNotEmpty()
password: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';

export class DeleteConnectionForHostedDbDto {
@ApiProperty({
description: 'Company ID',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@IsNotEmpty()
@IsString()
@IsUUID()
companyId: string;

@ApiProperty({
description: 'Hosted db entity ID',
example: '123e4567-e89b-12d3-a456-426614174000',
})
@IsNotEmpty()
@IsString()
@IsUUID()
hostedDatabaseId: string;

@ApiProperty({
description: 'Database name',
example: 'my_database',
})
databaseName: string;
Comment on lines +22 to +27
}
35 changes: 35 additions & 0 deletions backend/src/microservices/saas-microservice/saas.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { SaasSAMLUserRegisterDS } from './data-structures/saas-saml-user-registe
import { SaasRegisterUserWithGoogleDS } from './data-structures/sass-register-user-with-google.js';
import {
ICompanyRegistration,
ICreateConnectionForHostedDb,
IDeleteConnectionForHostedDb,
IFreezeConnectionsInCompany,
IGetUserInfo,
ILoginUserWithGitHub,
Expand All @@ -44,6 +46,9 @@ import {
ISuspendUsers,
ISuspendUsersOverLimit,
} from './use-cases/saas-use-cases.interface.js';
import { CreatedConnectionDTO } from '../../entities/connection/application/dto/created-connection.dto.js';
import { CreateConnectionForHostedDbDto } from './data-structures/create-connecttion-for-selfhosted-db.dto.js';
import { DeleteConnectionForHostedDbDto } from './data-structures/delete-connection-for-hosted-db.dto.js';
Comment on lines +49 to +51

@UseInterceptors(SentryInterceptor)
@SkipThrottle()
Expand Down Expand Up @@ -82,6 +87,10 @@ export class SaasController {
private readonly freezeConnectionsInCompanyUseCase: IFreezeConnectionsInCompany,
@Inject(UseCaseType.UNFREEZE_CONNECTIONS_IN_COMPANY)
private readonly unfreezeConnectionsInCompanyUseCase: IFreezeConnectionsInCompany,
@Inject(UseCaseType.SAAS_CREATE_CONNECTION_FOR_HOSTED_DB)
private readonly createConnectionForHostedDbUseCase: ICreateConnectionForHostedDb,
@Inject(UseCaseType.SAAS_DELETE_CONNECTION_FOR_HOSTED_DB)
private readonly deleteConnectionForHostedDbUseCase: IDeleteConnectionForHostedDb,
) {}

@ApiOperation({ summary: 'Company registered webhook' })
Expand Down Expand Up @@ -274,4 +283,30 @@ export class SaasController {
samlAttributes,
});
}

@ApiOperation({ summary: 'Created connection of hosted database' })
@ApiBody({ type: CreateConnectionForHostedDbDto })
@ApiResponse({
status: 201,
type: CreatedConnectionDTO,
})
@Post('/connection/hosted')
async createConnectionForHostedDb(
@Body() connectionData: CreateConnectionForHostedDbDto,
): Promise<CreatedConnectionDTO> {
return await this.createConnectionForHostedDbUseCase.execute(connectionData);
}

@ApiOperation({ summary: 'Delete connection of hosted database' })
@ApiBody({ type: DeleteConnectionForHostedDbDto })
@ApiResponse({
status: 201,
type: CreatedConnectionDTO,
})
@Post('connection/hosted/delete')
async deleteConnectionForHostedDb(
@Body() deleteConnectionData: DeleteConnectionForHostedDbDto,
): Promise<CreatedConnectionDTO> {
return await this.deleteConnectionForHostedDbUseCase.execute(deleteConnectionData);
}
}
12 changes: 12 additions & 0 deletions backend/src/microservices/saas-microservice/saas.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { SaaSRegisterUserWIthSamlUseCase } from './use-cases/register-user-with-
import { SaasUsualRegisterUseCase } from './use-cases/saas-usual-register-user.use.case.js';
import { SuspendUsersUseCase } from './use-cases/suspend-users.use.case.js';
import { SuspendUsersOverLimitUseCase } from './use-cases/suspend-users-over-limit.use.case.js';
import { CreateConnectionForHostedDbUseCase } from './use-cases/create-connection-for-hosted-db.use.case.js';
import { DeleteConnectionForHostedDbUseCase } from './use-cases/delete-connection-for-hosted-db.use.case.js';
import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connections-in-company-use.case.js';

@Module({
Expand Down Expand Up @@ -85,6 +87,14 @@ import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connec
provide: UseCaseType.SAAS_SUSPEND_USERS_OVER_LIMIT,
useClass: SuspendUsersOverLimitUseCase,
},
{
provide: UseCaseType.SAAS_CREATE_CONNECTION_FOR_HOSTED_DB,
useClass: CreateConnectionForHostedDbUseCase,
},
{
provide: UseCaseType.SAAS_DELETE_CONNECTION_FOR_HOSTED_DB,
useClass: DeleteConnectionForHostedDbUseCase,
},
SignInAuditService,
],
controllers: [SaasController],
Expand All @@ -108,6 +118,8 @@ export class SaasModule {
{ path: 'saas/company/freeze-connections', method: RequestMethod.PUT },
{ path: 'saas/company/unfreeze-connections', method: RequestMethod.PUT },
{ path: 'saas/user/saml/login', method: RequestMethod.POST },
{ path: 'saas/connection/hosted', method: RequestMethod.POST },
{ path: 'saas/connection/hosted/delete', method: RequestMethod.POST },
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { 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 { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.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 { slackPostMessage } from '../../../helpers/index.js';
import { AccessLevelEnum } from '../../../enums/index.js';
import { generateCedarPolicyForGroup } from '../../../entities/cedar-authorization/cedar-policy-generator.js';
import { CreatedConnectionDTO } from '../../../entities/connection/application/dto/created-connection.dto.js';
import { ConnectionEntity } from '../../../entities/connection/connection.entity.js';
import { readSslCertificate } from '../../../entities/connection/ssl-certificate/read-certificate.js';
import { buildCreatedConnectionDs } from '../../../entities/connection/utils/build-created-connection.ds.js';
import { CreateConnectionForHostedDbDto } from '../data-structures/create-connecttion-for-selfhosted-db.dto.js';
import { ICreateConnectionForHostedDb } from './saas-use-cases.interface.js';

@Injectable({ scope: Scope.REQUEST })
export class CreateConnectionForHostedDbUseCase
extends AbstractUseCase<CreateConnectionForHostedDbDto, CreatedConnectionDTO>
implements ICreateConnectionForHostedDb
{
constructor(
@Inject(BaseType.GLOBAL_DB_CONTEXT)
protected _dbContext: IGlobalDatabaseContext,
) {
super();
}

protected async implementation(inputData: CreateConnectionForHostedDbDto): Promise<CreatedConnectionDTO> {
const { companyId, userId, databaseName, hostname, port, username, password } = inputData;

const connectionAuthor = await this._dbContext.userRepository.findOneUserById(userId);
if (!connectionAuthor) {
throw new InternalServerErrorException(Messages.USER_NOT_FOUND);
}

await slackPostMessage(
Messages.USER_TRY_CREATE_CONNECTION(connectionAuthor.email, ConnectionTypesEnum.postgres),
);

const cert = await readSslCertificate();

const connectionParams = {
type: ConnectionTypesEnum.postgres,
host: hostname,
port: port,
username: username,
password: password,
database: databaseName,
schema: null,
sid: null,
ssh: false,
privateSSHKey: null,
sshHost: null,
sshPort: null,
sshUsername: null,
ssl: true,
cert: cert,
azure_encryption: false,
authSource: null,
dataCenter: null,
};

const dao = getDataAccessObject(connectionParams);
await dao.testConnect();

const connection = new ConnectionEntity();
connection.type = ConnectionTypesEnum.postgres;
connection.host = hostname;
connection.port = port;
connection.username = username;
connection.password = password;
connection.database = databaseName;
connection.ssl = true;
connection.cert = cert;
connection.ssh = false;
connection.azure_encryption = false;
connection.masterEncryption = false;
connection.author = connectionAuthor;

const savedConnection = await this._dbContext.connectionRepository.saveNewConnection(connection);

const createdAdminGroup = await this._dbContext.groupRepository.createdAdminGroupInConnection(
savedConnection,
connectionAuthor,
);
await this._dbContext.permissionRepository.createdDefaultAdminPermissionsInGroup(createdAdminGroup);
createdAdminGroup.cedarPolicy = generateCedarPolicyForGroup(
savedConnection.id,
true,
{
connection: { connectionId: savedConnection.id, accessLevel: AccessLevelEnum.edit },
group: { groupId: createdAdminGroup.id, accessLevel: AccessLevelEnum.edit },
tables: [],
},
);
await this._dbContext.groupRepository.saveNewOrUpdatedGroup(createdAdminGroup);
delete createdAdminGroup.connection;
await this._dbContext.userRepository.saveUserEntity(connectionAuthor);
savedConnection.groups = [createdAdminGroup];

const foundCompany = await this._dbContext.companyInfoRepository.findCompanyInfoByCompanyIdWithoutConnections(companyId);
if (foundCompany) {
const connectionToUpdate = await this._dbContext.connectionRepository.findOne({
where: { id: savedConnection.id },
});
connectionToUpdate.company = foundCompany;
await this._dbContext.connectionRepository.saveUpdatedConnection(connectionToUpdate);
}
Comment on lines +103 to +110

await slackPostMessage(
Messages.USER_CREATED_CONNECTION(connectionAuthor.email, ConnectionTypesEnum.postgres),
);

const connectionRO = buildCreatedConnectionDs(savedConnection, null, null);
return connectionRO;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
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 { CreatedConnectionDTO } from '../../../entities/connection/application/dto/created-connection.dto.js';
import { buildCreatedConnectionDs } from '../../../entities/connection/utils/build-created-connection.ds.js';
import { DeleteConnectionForHostedDbDto } from '../data-structures/delete-connection-for-hosted-db.dto.js';
import { IDeleteConnectionForHostedDb } from './saas-use-cases.interface.js';

@Injectable({ scope: Scope.REQUEST })
export class DeleteConnectionForHostedDbUseCase
extends AbstractUseCase<DeleteConnectionForHostedDbDto, CreatedConnectionDTO>
implements IDeleteConnectionForHostedDb
{
constructor(
@Inject(BaseType.GLOBAL_DB_CONTEXT)
protected _dbContext: IGlobalDatabaseContext,
) {
super();
}

protected async implementation(inputData: DeleteConnectionForHostedDbDto): Promise<CreatedConnectionDTO> {
const { companyId, hostedDatabaseId } = inputData;

const connectionToDelete = await this._dbContext.connectionRepository.findAndDecryptConnection(
hostedDatabaseId,
null,
);
if (!connectionToDelete) {
throw new NotFoundException(Messages.CONNECTION_NOT_FOUND);
}

const foundCompany = await this._dbContext.companyInfoRepository.findCompanyInfoByCompanyIdWithoutConnections(companyId);
if (!foundCompany) {
throw new NotFoundException(Messages.COMPANY_NOT_FOUND);
}

const result = await this._dbContext.connectionRepository.removeConnection(connectionToDelete);
return buildCreatedConnectionDs(result, null, null);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { CompanyInfoEntity } from '../../../entities/company-info/company-info.entity.js';
import { CreatedConnectionDTO } from '../../../entities/connection/application/dto/created-connection.dto.js';
import { SaaSRegisterDemoUserAccountDS } from '../../../entities/user/application/data-structures/demo-user-account-register.ds.js';
import { SaasUsualUserRegisterDS } from '../../../entities/user/application/data-structures/usual-register-user.ds.js';
import { FoundUserDto } from '../../../entities/user/dto/found-user.dto.js';
import { UserEntity } from '../../../entities/user/user.entity.js';
import { InTransactionEnum } from '../../../enums/in-transaction.enum.js';
import { SuccessResponse } from '../data-structures/common-responce.ds.js';
import { CreateConnectionForHostedDbDto } from '../data-structures/create-connecttion-for-selfhosted-db.dto.js';
import { DeleteConnectionForHostedDbDto } from '../data-structures/delete-connection-for-hosted-db.dto.js';
import { FreezeConnectionsInCompanyDS } from '../data-structures/freeze-connections-in-company.ds.js';
import { GetUserInfoByIdDS } from '../data-structures/get-user-info.ds.js';
import { GetUsersInfosByEmailDS } from '../data-structures/get-users-infos-by-email.ds.js';
Expand Down Expand Up @@ -66,3 +69,11 @@ export interface IFreezeConnectionsInCompany {
export interface ISaasSAMLRegisterUser {
execute(userData: SaasSAMLUserRegisterDS): Promise<UserEntity>;
}

export interface ICreateConnectionForHostedDb {
execute(inputData: CreateConnectionForHostedDbDto): Promise<CreatedConnectionDTO>;
}

export interface IDeleteConnectionForHostedDb {
execute(inputData: DeleteConnectionForHostedDbDto): Promise<CreatedConnectionDTO>;
}
Loading