Skip to content

Commit 1ac9111

Browse files
authored
Merge pull request #1669 from rocket-admin/backend_selfosted_dbs
Backend selfosted dbs
2 parents 64e735a + fd6974b commit 1ac9111

8 files changed

Lines changed: 313 additions & 0 deletions

backend/src/common/data-injection.tokens.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ export enum UseCaseType {
114114
FREEZE_CONNECTIONS_IN_COMPANY = 'FREEZE_CONNECTIONS_IN_COMPANY',
115115
UNFREEZE_CONNECTIONS_IN_COMPANY = 'UNFREEZE_CONNECTIONS_IN_COMPANY',
116116
SAAS_REGISTER_USER_WITH_SAML = 'SAAS_REGISTER_USER_WITH_SAML',
117+
SAAS_CREATE_CONNECTION_FOR_HOSTED_DB = 'SAAS_CREATE_CONNECTION_FOR_HOSTED_DB',
118+
SAAS_DELETE_CONNECTION_FOR_HOSTED_DB = 'SAAS_DELETE_CONNECTION_FOR_HOSTED_DB',
117119

118120
INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
119121
VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsNotEmpty, IsNumber, IsString, IsUUID, Max, Min } from 'class-validator';
3+
4+
export class CreateConnectionForHostedDbDto {
5+
@ApiProperty({
6+
description: 'Company ID',
7+
example: '123e4567-e89b-12d3-a456-426614174000',
8+
})
9+
@IsString()
10+
@IsNotEmpty()
11+
@IsUUID()
12+
companyId: string;
13+
14+
@ApiProperty({
15+
description: 'User ID',
16+
example: '123e4567-e89b-12d3-a456-426614174000',
17+
})
18+
@IsString()
19+
@IsNotEmpty()
20+
@IsUUID()
21+
userId: string;
22+
23+
@ApiProperty({
24+
description: 'Database name',
25+
example: 'my_database',
26+
})
27+
@IsString()
28+
@IsNotEmpty()
29+
databaseName: string;
30+
31+
@ApiProperty({
32+
description: 'Database hostname',
33+
example: 'localhost',
34+
})
35+
@IsString()
36+
@IsNotEmpty()
37+
hostname: string;
38+
39+
@ApiProperty({
40+
description: 'Database port',
41+
example: 5432,
42+
})
43+
@IsNotEmpty()
44+
@IsNumber()
45+
@Max(65535)
46+
@Min(1)
47+
port: number;
48+
49+
@ApiProperty({
50+
description: 'Database username',
51+
example: 'db_user',
52+
})
53+
@IsString()
54+
@IsNotEmpty()
55+
username: string;
56+
57+
@ApiProperty({
58+
description: 'Database password',
59+
example: 'secure_password',
60+
})
61+
@IsString()
62+
@IsNotEmpty()
63+
password: string;
64+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
3+
4+
export class DeleteConnectionForHostedDbDto {
5+
@ApiProperty({
6+
description: 'Company ID',
7+
example: '123e4567-e89b-12d3-a456-426614174000',
8+
})
9+
@IsNotEmpty()
10+
@IsString()
11+
@IsUUID()
12+
companyId: string;
13+
14+
@ApiProperty({
15+
description: 'Hosted db entity ID',
16+
example: '123e4567-e89b-12d3-a456-426614174000',
17+
})
18+
@IsNotEmpty()
19+
@IsString()
20+
@IsUUID()
21+
hostedDatabaseId: string;
22+
23+
@ApiProperty({
24+
description: 'Database name',
25+
example: 'my_database',
26+
})
27+
databaseName: string;
28+
}

backend/src/microservices/saas-microservice/saas.controller.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import { SaasSAMLUserRegisterDS } from './data-structures/saas-saml-user-registe
3131
import { SaasRegisterUserWithGoogleDS } from './data-structures/sass-register-user-with-google.js';
3232
import {
3333
ICompanyRegistration,
34+
ICreateConnectionForHostedDb,
35+
IDeleteConnectionForHostedDb,
3436
IFreezeConnectionsInCompany,
3537
IGetUserInfo,
3638
ILoginUserWithGitHub,
@@ -44,6 +46,9 @@ import {
4446
ISuspendUsers,
4547
ISuspendUsersOverLimit,
4648
} from './use-cases/saas-use-cases.interface.js';
49+
import { CreatedConnectionDTO } from '../../entities/connection/application/dto/created-connection.dto.js';
50+
import { CreateConnectionForHostedDbDto } from './data-structures/create-connecttion-for-selfhosted-db.dto.js';
51+
import { DeleteConnectionForHostedDbDto } from './data-structures/delete-connection-for-hosted-db.dto.js';
4752

4853
@UseInterceptors(SentryInterceptor)
4954
@SkipThrottle()
@@ -82,6 +87,10 @@ export class SaasController {
8287
private readonly freezeConnectionsInCompanyUseCase: IFreezeConnectionsInCompany,
8388
@Inject(UseCaseType.UNFREEZE_CONNECTIONS_IN_COMPANY)
8489
private readonly unfreezeConnectionsInCompanyUseCase: IFreezeConnectionsInCompany,
90+
@Inject(UseCaseType.SAAS_CREATE_CONNECTION_FOR_HOSTED_DB)
91+
private readonly createConnectionForHostedDbUseCase: ICreateConnectionForHostedDb,
92+
@Inject(UseCaseType.SAAS_DELETE_CONNECTION_FOR_HOSTED_DB)
93+
private readonly deleteConnectionForHostedDbUseCase: IDeleteConnectionForHostedDb,
8594
) {}
8695

8796
@ApiOperation({ summary: 'Company registered webhook' })
@@ -274,4 +283,30 @@ export class SaasController {
274283
samlAttributes,
275284
});
276285
}
286+
287+
@ApiOperation({ summary: 'Created connection of hosted database' })
288+
@ApiBody({ type: CreateConnectionForHostedDbDto })
289+
@ApiResponse({
290+
status: 201,
291+
type: CreatedConnectionDTO,
292+
})
293+
@Post('/connection/hosted')
294+
async createConnectionForHostedDb(
295+
@Body() connectionData: CreateConnectionForHostedDbDto,
296+
): Promise<CreatedConnectionDTO> {
297+
return await this.createConnectionForHostedDbUseCase.execute(connectionData);
298+
}
299+
300+
@ApiOperation({ summary: 'Delete connection of hosted database' })
301+
@ApiBody({ type: DeleteConnectionForHostedDbDto })
302+
@ApiResponse({
303+
status: 201,
304+
type: CreatedConnectionDTO,
305+
})
306+
@Post('connection/hosted/delete')
307+
async deleteConnectionForHostedDb(
308+
@Body() deleteConnectionData: DeleteConnectionForHostedDbDto,
309+
): Promise<CreatedConnectionDTO> {
310+
return await this.deleteConnectionForHostedDbUseCase.execute(deleteConnectionData);
311+
}
277312
}

backend/src/microservices/saas-microservice/saas.module.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { SaaSRegisterUserWIthSamlUseCase } from './use-cases/register-user-with-
2020
import { SaasUsualRegisterUseCase } from './use-cases/saas-usual-register-user.use.case.js';
2121
import { SuspendUsersUseCase } from './use-cases/suspend-users.use.case.js';
2222
import { SuspendUsersOverLimitUseCase } from './use-cases/suspend-users-over-limit.use.case.js';
23+
import { CreateConnectionForHostedDbUseCase } from './use-cases/create-connection-for-hosted-db.use.case.js';
24+
import { DeleteConnectionForHostedDbUseCase } from './use-cases/delete-connection-for-hosted-db.use.case.js';
2325
import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connections-in-company-use.case.js';
2426

2527
@Module({
@@ -85,6 +87,14 @@ import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connec
8587
provide: UseCaseType.SAAS_SUSPEND_USERS_OVER_LIMIT,
8688
useClass: SuspendUsersOverLimitUseCase,
8789
},
90+
{
91+
provide: UseCaseType.SAAS_CREATE_CONNECTION_FOR_HOSTED_DB,
92+
useClass: CreateConnectionForHostedDbUseCase,
93+
},
94+
{
95+
provide: UseCaseType.SAAS_DELETE_CONNECTION_FOR_HOSTED_DB,
96+
useClass: DeleteConnectionForHostedDbUseCase,
97+
},
8898
SignInAuditService,
8999
],
90100
controllers: [SaasController],
@@ -108,6 +118,8 @@ export class SaasModule {
108118
{ path: 'saas/company/freeze-connections', method: RequestMethod.PUT },
109119
{ path: 'saas/company/unfreeze-connections', method: RequestMethod.PUT },
110120
{ path: 'saas/user/saml/login', method: RequestMethod.POST },
121+
{ path: 'saas/connection/hosted', method: RequestMethod.POST },
122+
{ path: 'saas/connection/hosted/delete', method: RequestMethod.POST },
111123
);
112124
}
113125
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { Inject, Injectable, InternalServerErrorException, Scope } from '@nestjs/common';
2+
import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js';
3+
import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js';
4+
import AbstractUseCase from '../../../common/abstract-use.case.js';
5+
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
6+
import { BaseType } from '../../../common/data-injection.tokens.js';
7+
import { Messages } from '../../../exceptions/text/messages.js';
8+
import { slackPostMessage } from '../../../helpers/index.js';
9+
import { AccessLevelEnum } from '../../../enums/index.js';
10+
import { generateCedarPolicyForGroup } from '../../../entities/cedar-authorization/cedar-policy-generator.js';
11+
import { CreatedConnectionDTO } from '../../../entities/connection/application/dto/created-connection.dto.js';
12+
import { ConnectionEntity } from '../../../entities/connection/connection.entity.js';
13+
import { readSslCertificate } from '../../../entities/connection/ssl-certificate/read-certificate.js';
14+
import { buildCreatedConnectionDs } from '../../../entities/connection/utils/build-created-connection.ds.js';
15+
import { CreateConnectionForHostedDbDto } from '../data-structures/create-connecttion-for-selfhosted-db.dto.js';
16+
import { ICreateConnectionForHostedDb } from './saas-use-cases.interface.js';
17+
18+
@Injectable({ scope: Scope.REQUEST })
19+
export class CreateConnectionForHostedDbUseCase
20+
extends AbstractUseCase<CreateConnectionForHostedDbDto, CreatedConnectionDTO>
21+
implements ICreateConnectionForHostedDb
22+
{
23+
constructor(
24+
@Inject(BaseType.GLOBAL_DB_CONTEXT)
25+
protected _dbContext: IGlobalDatabaseContext,
26+
) {
27+
super();
28+
}
29+
30+
protected async implementation(inputData: CreateConnectionForHostedDbDto): Promise<CreatedConnectionDTO> {
31+
const { companyId, userId, databaseName, hostname, port, username, password } = inputData;
32+
33+
const connectionAuthor = await this._dbContext.userRepository.findOneUserById(userId);
34+
if (!connectionAuthor) {
35+
throw new InternalServerErrorException(Messages.USER_NOT_FOUND);
36+
}
37+
38+
await slackPostMessage(
39+
Messages.USER_TRY_CREATE_CONNECTION(connectionAuthor.email, ConnectionTypesEnum.postgres),
40+
);
41+
42+
const cert = await readSslCertificate();
43+
44+
const connectionParams = {
45+
type: ConnectionTypesEnum.postgres,
46+
host: hostname,
47+
port: port,
48+
username: username,
49+
password: password,
50+
database: databaseName,
51+
schema: null,
52+
sid: null,
53+
ssh: false,
54+
privateSSHKey: null,
55+
sshHost: null,
56+
sshPort: null,
57+
sshUsername: null,
58+
ssl: true,
59+
cert: cert,
60+
azure_encryption: false,
61+
authSource: null,
62+
dataCenter: null,
63+
};
64+
65+
const dao = getDataAccessObject(connectionParams);
66+
await dao.testConnect();
67+
68+
const connection = new ConnectionEntity();
69+
connection.type = ConnectionTypesEnum.postgres;
70+
connection.host = hostname;
71+
connection.port = port;
72+
connection.username = username;
73+
connection.password = password;
74+
connection.database = databaseName;
75+
connection.ssl = true;
76+
connection.cert = cert;
77+
connection.ssh = false;
78+
connection.azure_encryption = false;
79+
connection.masterEncryption = false;
80+
connection.author = connectionAuthor;
81+
82+
const savedConnection = await this._dbContext.connectionRepository.saveNewConnection(connection);
83+
84+
const createdAdminGroup = await this._dbContext.groupRepository.createdAdminGroupInConnection(
85+
savedConnection,
86+
connectionAuthor,
87+
);
88+
await this._dbContext.permissionRepository.createdDefaultAdminPermissionsInGroup(createdAdminGroup);
89+
createdAdminGroup.cedarPolicy = generateCedarPolicyForGroup(
90+
savedConnection.id,
91+
true,
92+
{
93+
connection: { connectionId: savedConnection.id, accessLevel: AccessLevelEnum.edit },
94+
group: { groupId: createdAdminGroup.id, accessLevel: AccessLevelEnum.edit },
95+
tables: [],
96+
},
97+
);
98+
await this._dbContext.groupRepository.saveNewOrUpdatedGroup(createdAdminGroup);
99+
delete createdAdminGroup.connection;
100+
await this._dbContext.userRepository.saveUserEntity(connectionAuthor);
101+
savedConnection.groups = [createdAdminGroup];
102+
103+
const foundCompany = await this._dbContext.companyInfoRepository.findCompanyInfoByCompanyIdWithoutConnections(companyId);
104+
if (foundCompany) {
105+
const connectionToUpdate = await this._dbContext.connectionRepository.findOne({
106+
where: { id: savedConnection.id },
107+
});
108+
connectionToUpdate.company = foundCompany;
109+
await this._dbContext.connectionRepository.saveUpdatedConnection(connectionToUpdate);
110+
}
111+
112+
await slackPostMessage(
113+
Messages.USER_CREATED_CONNECTION(connectionAuthor.email, ConnectionTypesEnum.postgres),
114+
);
115+
116+
const connectionRO = buildCreatedConnectionDs(savedConnection, null, null);
117+
return connectionRO;
118+
}
119+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
2+
import AbstractUseCase from '../../../common/abstract-use.case.js';
3+
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
4+
import { BaseType } from '../../../common/data-injection.tokens.js';
5+
import { Messages } from '../../../exceptions/text/messages.js';
6+
import { CreatedConnectionDTO } from '../../../entities/connection/application/dto/created-connection.dto.js';
7+
import { buildCreatedConnectionDs } from '../../../entities/connection/utils/build-created-connection.ds.js';
8+
import { DeleteConnectionForHostedDbDto } from '../data-structures/delete-connection-for-hosted-db.dto.js';
9+
import { IDeleteConnectionForHostedDb } from './saas-use-cases.interface.js';
10+
11+
@Injectable({ scope: Scope.REQUEST })
12+
export class DeleteConnectionForHostedDbUseCase
13+
extends AbstractUseCase<DeleteConnectionForHostedDbDto, CreatedConnectionDTO>
14+
implements IDeleteConnectionForHostedDb
15+
{
16+
constructor(
17+
@Inject(BaseType.GLOBAL_DB_CONTEXT)
18+
protected _dbContext: IGlobalDatabaseContext,
19+
) {
20+
super();
21+
}
22+
23+
protected async implementation(inputData: DeleteConnectionForHostedDbDto): Promise<CreatedConnectionDTO> {
24+
const { companyId, hostedDatabaseId } = inputData;
25+
26+
const connectionToDelete = await this._dbContext.connectionRepository.findAndDecryptConnection(
27+
hostedDatabaseId,
28+
null,
29+
);
30+
if (!connectionToDelete) {
31+
throw new NotFoundException(Messages.CONNECTION_NOT_FOUND);
32+
}
33+
34+
const foundCompany = await this._dbContext.companyInfoRepository.findCompanyInfoByCompanyIdWithoutConnections(companyId);
35+
if (!foundCompany) {
36+
throw new NotFoundException(Messages.COMPANY_NOT_FOUND);
37+
}
38+
39+
const result = await this._dbContext.connectionRepository.removeConnection(connectionToDelete);
40+
return buildCreatedConnectionDs(result, null, null);
41+
}
42+
}

backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { CompanyInfoEntity } from '../../../entities/company-info/company-info.entity.js';
2+
import { CreatedConnectionDTO } from '../../../entities/connection/application/dto/created-connection.dto.js';
23
import { SaaSRegisterDemoUserAccountDS } from '../../../entities/user/application/data-structures/demo-user-account-register.ds.js';
34
import { SaasUsualUserRegisterDS } from '../../../entities/user/application/data-structures/usual-register-user.ds.js';
45
import { FoundUserDto } from '../../../entities/user/dto/found-user.dto.js';
56
import { UserEntity } from '../../../entities/user/user.entity.js';
67
import { InTransactionEnum } from '../../../enums/in-transaction.enum.js';
78
import { SuccessResponse } from '../data-structures/common-responce.ds.js';
9+
import { CreateConnectionForHostedDbDto } from '../data-structures/create-connecttion-for-selfhosted-db.dto.js';
10+
import { DeleteConnectionForHostedDbDto } from '../data-structures/delete-connection-for-hosted-db.dto.js';
811
import { FreezeConnectionsInCompanyDS } from '../data-structures/freeze-connections-in-company.ds.js';
912
import { GetUserInfoByIdDS } from '../data-structures/get-user-info.ds.js';
1013
import { GetUsersInfosByEmailDS } from '../data-structures/get-users-infos-by-email.ds.js';
@@ -66,3 +69,11 @@ export interface IFreezeConnectionsInCompany {
6669
export interface ISaasSAMLRegisterUser {
6770
execute(userData: SaasSAMLUserRegisterDS): Promise<UserEntity>;
6871
}
72+
73+
export interface ICreateConnectionForHostedDb {
74+
execute(inputData: CreateConnectionForHostedDbDto): Promise<CreatedConnectionDTO>;
75+
}
76+
77+
export interface IDeleteConnectionForHostedDb {
78+
execute(inputData: DeleteConnectionForHostedDbDto): Promise<CreatedConnectionDTO>;
79+
}

0 commit comments

Comments
 (0)