Skip to content

Commit 6b3132e

Browse files
authored
Merge pull request #1178 from rocket-admin/backend_demo_user_account
feat: add demo user registration functionality and related entities
2 parents d77c9ff + 4bf3e37 commit 6b3132e

10 files changed

Lines changed: 292 additions & 1 deletion

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export enum UseCaseType {
9999
SAAS_COMPANY_REGISTRATION = 'SAAS_COMPANY_REGISTRATION',
100100
SAAS_GET_USER_INFO = 'SAAS_GET_USER_INFO',
101101
SAAS_USUAL_REGISTER_USER = 'SAAS_USUAL_REGISTER_USER',
102+
SAAS_DEMO_USER_REGISTRATION = 'SAAS_DEMO_USER_REGISTRATION',
102103
SAAS_LOGIN_USER_WITH_GOOGLE = 'SAAS_LOGIN_USER_WITH_GOOGLE',
103104
SAAS_LOGIN_USER_WITH_GITHUB = 'SAAS_LOGIN_USER_WITH_GITHUB',
104105
SAAS_SAAS_GET_USERS_INFOS_BY_EMAIL = 'SAAS_SAAS_GET_USERS_INFOS_BY_EMAIL',
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class SaaSRegisterDemoUserAccountDS {
2+
email: string;
3+
gclidValue: string;
4+
companyName: string;
5+
companyId: string;
6+
}

backend/src/entities/user/user.entity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export class UserEntity {
4444
@Column({ default: false })
4545
suspended: boolean;
4646

47+
@Column({ default: false })
48+
isDemoAccount: boolean;
49+
4750
@BeforeInsert()
4851
async hashPassword() {
4952
if (this.password) {

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
IGetUserInfo,
1919
ILoginUserWithGitHub,
2020
ILoginUserWithGoogle,
21+
ISaasDemoRegisterUser,
2122
ISaaSGetCompanyInfoByUserId,
2223
ISaaSGetUsersCountInCompany,
2324
ISaasGetUsersInfosByEmail,
@@ -41,6 +42,8 @@ export class SaasController {
4142
private readonly getUsersInfosByEmailUseCase: ISaasGetUsersInfosByEmail,
4243
@Inject(UseCaseType.SAAS_USUAL_REGISTER_USER)
4344
private readonly usualRegisterUserUseCase: ISaasRegisterUser,
45+
@Inject(UseCaseType.SAAS_DEMO_USER_REGISTRATION)
46+
private readonly demoRegisterUserUseCase: ISaasDemoRegisterUser,
4447
@Inject(UseCaseType.SAAS_LOGIN_USER_WITH_GOOGLE)
4548
private readonly loginUserWithGoogleUseCase: ILoginUserWithGoogle,
4649
@Inject(UseCaseType.SAAS_LOGIN_USER_WITH_GITHUB)
@@ -117,6 +120,23 @@ export class SaasController {
117120
return await this.usualRegisterUserUseCase.execute({ email, password, gclidValue, name, companyId, companyName });
118121
}
119122

123+
@ApiOperation({ summary: 'Register demo user register webhook' })
124+
@ApiBody({ type: SaasUsualUserRegisterDS })
125+
@ApiResponse({
126+
status: 201,
127+
description: 'Demo user account has been successfully registered.',
128+
type: FoundUserDto,
129+
})
130+
@Post('user/demo/register')
131+
async registerDemoUserAccount(
132+
@Body('email') email: string,
133+
@Body('gclidValue') gclidValue: string,
134+
@Body('companyId') companyId: string,
135+
@Body('companyName') companyName: string,
136+
): Promise<FoundUserDto> {
137+
return await this.demoRegisterUserUseCase.execute({ email, gclidValue, companyId, companyName });
138+
}
139+
120140
@ApiOperation({ summary: 'Login or create user with google webhook' })
121141
@ApiBody({ type: SaasRegisterUserWithGoogleDS })
122142
@ApiResponse({

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { RegisteredCompanyWebhookUseCase } from './use-cases/register-company-we
1414
import { SaasUsualRegisterUseCase } from './use-cases/saas-usual-register-user.use.case.js';
1515
import { SuspendUsersUseCase } from './use-cases/suspend-users.use.case.js';
1616
import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connections-in-company-use.case.js';
17+
import { SaasRegisterDemoUserAccountUseCase } from './use-cases/register-demo-user-account.use.case.js';
1718

1819
@Module({
1920
imports: [],
@@ -66,6 +67,10 @@ import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connec
6667
provide: UseCaseType.UNFREEZE_CONNECTIONS_IN_COMPANY,
6768
useClass: UnFreezeConnectionsInCompanyUseCase,
6869
},
70+
{
71+
provide: UseCaseType.SAAS_DEMO_USER_REGISTRATION,
72+
useClass: SaasRegisterDemoUserAccountUseCase,
73+
},
6974
],
7075
controllers: [SaasController],
7176
exports: [],
@@ -78,6 +83,7 @@ export class SaasModule {
7883
{ path: 'saas/company/registered', method: RequestMethod.POST },
7984
{ path: 'saas/user/:userId', method: RequestMethod.GET },
8085
{ path: 'saas/user/register', method: RequestMethod.POST },
86+
{ path: 'saas/user/demo/register', method: RequestMethod.POST },
8187
{ path: 'saas/user/google/login', method: RequestMethod.POST },
8288
{ path: 'saas/user/github/login', method: RequestMethod.POST },
8389
{ path: 'saas/company/:companyId/users/suspend', method: RequestMethod.PUT },
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { Inject, Injectable } 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 { CompanyInfoEntity } from '../../../entities/company-info/company-info.entity.js';
6+
import { ConnectionEntity } from '../../../entities/connection/connection.entity.js';
7+
import { GroupEntity } from '../../../entities/group/group.entity.js';
8+
import { PermissionEntity } from '../../../entities/permission/permission.entity.js';
9+
import { SaaSRegisterDemoUserAccountDS } from '../../../entities/user/application/data-structures/demo-user-account-register.ds.js';
10+
import { FoundUserDto } from '../../../entities/user/dto/found-user.dto.js';
11+
import { UserRoleEnum } from '../../../entities/user/enums/user-role.enum.js';
12+
import { UserEntity } from '../../../entities/user/user.entity.js';
13+
import { buildConnectionEntitiesFromTestDtos } from '../../../entities/user/utils/build-connection-entities-from-test-dtos.js';
14+
import { buildDefaultAdminGroups } from '../../../entities/user/utils/build-default-admin-groups.js';
15+
import { buildDefaultAdminPermissions } from '../../../entities/user/utils/build-default-admin-permissions.js';
16+
import { Constants } from '../../../helpers/constants/constants.js';
17+
import { ISaasDemoRegisterUser } from './saas-use-cases.interface.js';
18+
19+
@Injectable()
20+
export class SaasRegisterDemoUserAccountUseCase
21+
extends AbstractUseCase<SaaSRegisterDemoUserAccountDS, FoundUserDto>
22+
implements ISaasDemoRegisterUser
23+
{
24+
constructor(
25+
@Inject(BaseType.GLOBAL_DB_CONTEXT)
26+
protected _dbContext: IGlobalDatabaseContext,
27+
) {
28+
super();
29+
}
30+
31+
protected async implementation(userData: SaaSRegisterDemoUserAccountDS): Promise<FoundUserDto> {
32+
const savedUser = await this.createDemoUser(userData);
33+
const createdTestConnections = await this.createTestConnections(savedUser);
34+
const createdTestGroups = await this.createTestGroups(savedUser, createdTestConnections);
35+
await this.createTestPermissions(createdTestGroups);
36+
const savedCompanyInfo = await this.createCompanyInfo(userData, createdTestConnections);
37+
38+
savedUser.company = savedCompanyInfo;
39+
await this._dbContext.userRepository.saveUserEntity(savedUser);
40+
41+
return this.buildFoundUserDto(savedUser);
42+
}
43+
44+
private async createDemoUser(userData: SaaSRegisterDemoUserAccountDS): Promise<UserEntity> {
45+
const { email, gclidValue } = userData;
46+
47+
const demoUser = new UserEntity();
48+
demoUser.email = email;
49+
demoUser.isDemoAccount = true;
50+
demoUser.gclid = gclidValue;
51+
demoUser.name = 'Demo User';
52+
demoUser.role = UserRoleEnum.ADMIN;
53+
54+
return await this._dbContext.userRepository.save(demoUser);
55+
}
56+
57+
private async createTestConnections(savedUser: UserEntity): Promise<ConnectionEntity[]> {
58+
const testConnections = Constants.getTestConnectionsArr();
59+
const testConnectionsEntities = buildConnectionEntitiesFromTestDtos(testConnections);
60+
61+
return await Promise.all(
62+
testConnectionsEntities.map(async (connection): Promise<ConnectionEntity> => {
63+
connection.author = savedUser;
64+
return await this._dbContext.connectionRepository.saveNewConnection(connection);
65+
}),
66+
);
67+
}
68+
69+
private async createTestGroups(
70+
savedUser: UserEntity,
71+
createdTestConnections: ConnectionEntity[],
72+
): Promise<GroupEntity[]> {
73+
const testGroupsEntities = buildDefaultAdminGroups(savedUser, createdTestConnections);
74+
75+
return await Promise.all(
76+
testGroupsEntities.map(async (group: GroupEntity) => {
77+
return await this._dbContext.groupRepository.saveNewOrUpdatedGroup(group);
78+
}),
79+
);
80+
}
81+
82+
private async createTestPermissions(createdTestGroups: GroupEntity[]): Promise<void> {
83+
const testPermissionsEntities = buildDefaultAdminPermissions(createdTestGroups);
84+
85+
await Promise.all(
86+
testPermissionsEntities.map(async (permission: PermissionEntity) => {
87+
await this._dbContext.permissionRepository.saveNewOrUpdatedPermission(permission);
88+
}),
89+
);
90+
}
91+
92+
private async createCompanyInfo(
93+
userData: SaaSRegisterDemoUserAccountDS,
94+
createdTestConnections: ConnectionEntity[],
95+
): Promise<CompanyInfoEntity> {
96+
const { companyId, companyName } = userData;
97+
98+
const newCompanyInfo = new CompanyInfoEntity();
99+
newCompanyInfo.id = companyId;
100+
newCompanyInfo.name = companyName;
101+
newCompanyInfo.show_test_connections = true;
102+
newCompanyInfo.connections = [...createdTestConnections];
103+
104+
return await this._dbContext.companyInfoRepository.save(newCompanyInfo);
105+
}
106+
107+
private buildFoundUserDto(savedUser: UserEntity): FoundUserDto {
108+
return {
109+
id: savedUser.id,
110+
createdAt: savedUser.createdAt,
111+
isActive: savedUser.isActive,
112+
email: savedUser.email,
113+
intercom_hash: null,
114+
name: savedUser.name,
115+
role: savedUser.role,
116+
is_2fa_enabled: false,
117+
suspended: false,
118+
externalRegistrationProvider: savedUser.externalRegistrationProvider,
119+
show_test_connections: savedUser.showTestConnections,
120+
};
121+
}
122+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CompanyInfoEntity } from '../../../entities/company-info/company-info.entity.js';
2+
import { SaaSRegisterDemoUserAccountDS } from '../../../entities/user/application/data-structures/demo-user-account-register.ds.js';
23
import { SaasUsualUserRegisterDS } from '../../../entities/user/application/data-structures/usual-register-user.ds.js';
34
import { FoundUserDto } from '../../../entities/user/dto/found-user.dto.js';
45
import { UserEntity } from '../../../entities/user/user.entity.js';
@@ -29,6 +30,10 @@ export interface ISaasRegisterUser {
2930
execute(userData: SaasUsualUserRegisterDS): Promise<FoundUserDto>;
3031
}
3132

33+
export interface ISaasDemoRegisterUser {
34+
execute(userData: SaaSRegisterDemoUserAccountDS): Promise<FoundUserDto>;
35+
}
36+
3237
export interface ILoginUserWithGoogle {
3338
execute(inputData: SaasRegisterUserWithGoogleDS, inTransaction: InTransactionEnum): Promise<UserEntity>;
3439
}

backend/src/microservices/saas-microservice/use-cases/saas-usual-register-user.use.case.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HttpException, HttpStatus, Inject } from '@nestjs/common';
1+
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
22
import assert from 'assert';
33
import AbstractUseCase from '../../../common/abstract-use.case.js';
44
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
@@ -21,6 +21,7 @@ import { Constants } from '../../../helpers/constants/constants.js';
2121
import { SaasCompanyGatewayService } from '../../gateways/saas-gateway.ts/saas-company-gateway.service.js';
2222
import { ISaasRegisterUser } from './saas-use-cases.interface.js';
2323

24+
@Injectable()
2425
export class SaasUsualRegisterUseCase
2526
extends AbstractUseCase<SaasUsualUserRegisterDS, FoundUserDto>
2627
implements ISaasRegisterUser
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm';
2+
3+
export class AddIsDemoAccountToUserEntity1747235569575 implements MigrationInterface {
4+
name = 'AddIsDemoAccountToUserEntity1747235569575';
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`ALTER TABLE "user" ADD "isDemoAccount" boolean NOT NULL DEFAULT false`);
8+
}
9+
10+
public async down(queryRunner: QueryRunner): Promise<void> {
11+
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isDemoAccount"`);
12+
}
13+
}

backend/test/ava-tests/saas-tests/user-e2e.test.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ import {
2020
import { sendRequestToSaasPart } from '../../utils/send-request-to-saas-part.util.js';
2121
import { TestUtils } from '../../utils/test.utils.js';
2222
import { Cacher } from '../../../src/helpers/cache/cacher.js';
23+
import { Constants } from '../../../src/helpers/constants/constants.js';
24+
import { getTestData } from '../../utils/get-test-data.js';
25+
import { createTestTable } from '../../utils/create-test-table.js';
26+
import { MockFactory } from '../../mock.factory.js';
2327

2428
let app: INestApplication;
2529
let currentTest: string;
2630
let testUtils: TestUtils;
31+
const mockFactory = new MockFactory();
2732

2833
test.before(async () => {
2934
const moduleFixture = await Test.createTestingModule({
@@ -469,3 +474,112 @@ test.skip(`${currentTest} should login user successfully with company id from cu
469474
}
470475
t.pass();
471476
});
477+
478+
currentTest = 'POST /user/demo/register';
479+
test.serial(`${currentTest} should register demo user`, async (t) => {
480+
const result = await fetch('http://rocketadmin-private-microservice:3001/saas/user/demo/register', {
481+
method: 'POST',
482+
headers: {
483+
'Content-Type': 'application/json',
484+
Accept: 'application/json',
485+
},
486+
});
487+
if (result.status > 201) {
488+
console.info('result.body -> ', await result.json());
489+
}
490+
const token = `${Constants.JWT_COOKIE_KEY_NAME}=${TestUtils.getJwtTokenFromResponse2(result)}`;
491+
492+
//check test connections was created
493+
const getUserConnectionsResult = await request(app.getHttpServer())
494+
.get('/connections')
495+
.set('Cookie', token)
496+
.set('Content-Type', 'application/json')
497+
.set('Accept', 'application/json');
498+
const getUserConnectionsRO = JSON.parse(getUserConnectionsResult.text);
499+
t.is(getUserConnectionsResult.status, 200);
500+
t.is(getUserConnectionsRO.hasOwnProperty('connections'), true);
501+
t.is(getUserConnectionsRO.connections.length, 4);
502+
503+
//check user can add connection and use it
504+
505+
const connectionToTestDB = getTestData(mockFactory).connectionToPostgres;
506+
507+
const { testTableName, testTableColumnName, testEntitiesSeedsCount, testTableSecondColumnName } =
508+
await createTestTable(connectionToTestDB);
509+
510+
const createConnectionResponse = await request(app.getHttpServer())
511+
.post('/connection')
512+
.send(connectionToTestDB)
513+
.set('Cookie', token)
514+
.set('Content-Type', 'application/json')
515+
.set('Accept', 'application/json');
516+
const createConnectionRO = JSON.parse(createConnectionResponse.text);
517+
t.is(createConnectionResponse.status, 201);
518+
519+
const getTableRowsResponse = await request(app.getHttpServer())
520+
.get(`/table/rows/${createConnectionRO.id}?tableName=${testTableName}&page=1&perPage=50`)
521+
.set('Cookie', token)
522+
.set('Content-Type', 'application/json')
523+
.set('Accept', 'application/json');
524+
525+
const getTableRowsRO = JSON.parse(getTableRowsResponse.text);
526+
t.is(getTableRowsResponse.status, 200);
527+
528+
t.is(typeof getTableRowsRO, 'object');
529+
t.is(getTableRowsRO.hasOwnProperty('rows'), true);
530+
t.is(getTableRowsRO.hasOwnProperty('primaryColumns'), true);
531+
t.is(getTableRowsRO.hasOwnProperty('pagination'), true);
532+
t.is(getTableRowsRO.rows.length, 42);
533+
t.is(typeof getTableRowsRO.primaryColumns, 'object');
534+
t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('column_name'), true);
535+
t.is(getTableRowsRO.primaryColumns[0].hasOwnProperty('data_type'), true);
536+
t.is(getTableRowsRO.primaryColumns[0].column_name, 'id');
537+
t.is(getTableRowsRO.primaryColumns[0].data_type, 'integer');
538+
539+
//check that user has a company
540+
541+
const getUserCompanyResult = await request(app.getHttpServer())
542+
.get('/company/my/full')
543+
.set('Cookie', token)
544+
.set('Content-Type', 'application/json')
545+
.set('Accept', 'application/json');
546+
const getUserCompanyRO = JSON.parse(getUserCompanyResult.text);
547+
t.is(getUserCompanyResult.status, 200);
548+
549+
t.truthy(getUserCompanyRO);
550+
t.is(typeof getUserCompanyRO, 'object');
551+
t.is(getUserCompanyRO.hasOwnProperty('id'), true);
552+
t.is(typeof getUserCompanyRO.id, 'string');
553+
t.is(getUserCompanyRO.hasOwnProperty('name'), true);
554+
t.is(typeof getUserCompanyRO.name, 'string');
555+
t.is(getUserCompanyRO.hasOwnProperty('subscriptionLevel'), true);
556+
t.is(getUserCompanyRO.subscriptionLevel, 'TEAM_PLAN');
557+
t.is(getUserCompanyRO.hasOwnProperty('is_payment_method_added'), true);
558+
t.is(getUserCompanyRO.is_payment_method_added, false);
559+
t.is(getUserCompanyRO.hasOwnProperty('is2faEnabled'), true);
560+
t.is(getUserCompanyRO.is2faEnabled, false);
561+
t.is(getUserCompanyRO.hasOwnProperty('show_test_connections'), true);
562+
t.is(getUserCompanyRO.show_test_connections, true);
563+
t.is(getUserCompanyRO.hasOwnProperty('connections'), true);
564+
t.true(Array.isArray(getUserCompanyRO.connections));
565+
t.is(getUserCompanyRO.connections.length, 5);
566+
567+
for (const connection of getUserCompanyRO.connections) {
568+
t.is(typeof connection.id, 'string');
569+
t.is(typeof connection.title, 'string');
570+
t.is(typeof connection.author, 'object');
571+
t.is(connection.author.role, 'ADMIN');
572+
t.true(Array.isArray(connection.groups));
573+
for (const group of connection.groups) {
574+
t.is(typeof group.id, 'string');
575+
t.is(group.isMain, true);
576+
t.is(typeof group.title, 'string');
577+
t.true(Array.isArray(group.users));
578+
for (const user of group.users) {
579+
t.is(typeof user.id, 'string');
580+
t.is(user.role, 'ADMIN');
581+
t.is(typeof user.email, 'string');
582+
}
583+
}
584+
}
585+
});

0 commit comments

Comments
 (0)