diff --git a/backend/src/common/data-injection.tokens.ts b/backend/src/common/data-injection.tokens.ts index c497e8cf6..241b4d9f1 100644 --- a/backend/src/common/data-injection.tokens.ts +++ b/backend/src/common/data-injection.tokens.ts @@ -104,6 +104,7 @@ export enum UseCaseType { SAAS_LOGIN_USER_WITH_GITHUB = 'SAAS_LOGIN_USER_WITH_GITHUB', SAAS_SAAS_GET_USERS_INFOS_BY_EMAIL = 'SAAS_SAAS_GET_USERS_INFOS_BY_EMAIL', SAAS_SUSPEND_USERS = 'SAAS_SUSPEND_USERS', + SAAS_SUSPEND_USERS_OVER_LIMIT = 'SAAS_SUSPEND_USERS_OVER_LIMIT', SAAS_GET_COMPANY_INFO_BY_USER_ID = 'SAAS_GET_COMPANY_INFO_BY_USER_ID', SAAS_GET_USERS_COUNT_IN_COMPANY = 'SAAS_GET_USERS_COUNT_IN_COMPANY', FREEZE_CONNECTIONS_IN_COMPANY = 'FREEZE_CONNECTIONS_IN_COMPANY', diff --git a/backend/src/entities/user/repository/user-custom-repository-extension.ts b/backend/src/entities/user/repository/user-custom-repository-extension.ts index 0432f48af..5605a405e 100644 --- a/backend/src/entities/user/repository/user-custom-repository-extension.ts +++ b/backend/src/entities/user/repository/user-custom-repository-extension.ts @@ -130,6 +130,17 @@ export const userCustomRepositoryExtension: IUserRepository = { return await usersQB.getCount(); }, + async findUsersInCompany(companyId: string, orderByRole: boolean = false): Promise> { + const usersQB = this.createQueryBuilder('user') + .leftJoin('user.company', 'company') + .where('company.id = :companyId', { companyId: companyId }) + .orderBy('user.createdAt', 'ASC'); + if (orderByRole) { + usersQB.addOrderBy('user.role', 'ASC'); + } + return await usersQB.getMany(); + }, + async getUserEmailOrReturnNull(userId: string): Promise { const userQB = this.createQueryBuilder('user').where('user.id = :userId', { userId: userId }); const user = await userQB.getOne(); diff --git a/backend/src/entities/user/repository/user.repository.interface.ts b/backend/src/entities/user/repository/user.repository.interface.ts index d0b06f0bd..996217f0a 100644 --- a/backend/src/entities/user/repository/user.repository.interface.ts +++ b/backend/src/entities/user/repository/user.repository.interface.ts @@ -63,5 +63,7 @@ export interface IUserRepository { countUsersInCompany(companyId: string): Promise; + findUsersInCompany(companyId: string, orderByRole: boolean): Promise>; + suspendNewestUsersInCompany(companyId: string, unsuspendedUsersLeft: number): Promise>; } diff --git a/backend/src/microservices/saas-microservice/saas.controller.ts b/backend/src/microservices/saas-microservice/saas.controller.ts index 19562c373..dbcc5c42a 100644 --- a/backend/src/microservices/saas-microservice/saas.controller.ts +++ b/backend/src/microservices/saas-microservice/saas.controller.ts @@ -39,6 +39,7 @@ import { ISaasRegisterUser, ISaasSAMLRegisterUser, ISuspendUsers, + ISuspendUsersOverLimit, } from './use-cases/saas-use-cases.interface.js'; import { SkipThrottle } from '@nestjs/throttler'; import { Messages } from '../../exceptions/text/messages.js'; @@ -69,6 +70,8 @@ export class SaasController { private readonly registerUserWithSamlUseCase: ISaasSAMLRegisterUser, @Inject(UseCaseType.SAAS_SUSPEND_USERS) private readonly suspendUsersUseCase: ISuspendUsers, + @Inject(UseCaseType.SAAS_SUSPEND_USERS_OVER_LIMIT) + private readonly suspendUsersOverLimitUseCase: ISuspendUsersOverLimit, @Inject(UseCaseType.SAAS_GET_COMPANY_INFO_BY_USER_ID) private readonly getCompanyInfoByUserIdUseCase: ISaaSGetCompanyInfoByUserId, @Inject(UseCaseType.SAAS_GET_USERS_COUNT_IN_COMPANY) @@ -197,6 +200,13 @@ export class SaasController { return { success: true }; } + @ApiOperation({ summary: 'Suspending users' }) + @Put('/company/:companyId/users/suspend-above-limit') + async suspendUsersOverLimit(@Body('companyId') companyId: string): Promise { + await this.suspendUsersOverLimitUseCase.execute(companyId); + return { success: true }; + } + @ApiOperation({ summary: 'Get company info by user id' }) @ApiResponse({ status: 200, diff --git a/backend/src/microservices/saas-microservice/saas.module.ts b/backend/src/microservices/saas-microservice/saas.module.ts index 754b4e928..249ea6607 100644 --- a/backend/src/microservices/saas-microservice/saas.module.ts +++ b/backend/src/microservices/saas-microservice/saas.module.ts @@ -16,6 +16,7 @@ import { SuspendUsersUseCase } from './use-cases/suspend-users.use.case.js'; import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connections-in-company-use.case.js'; import { SaasRegisterDemoUserAccountUseCase } from './use-cases/register-demo-user-account.use.case.js'; import { SaaSRegisterUserWIthSamlUseCase } from './use-cases/register-user-with-saml-use.case.js'; +import { SuspendUsersOverLimitUseCase } from './use-cases/suspend-users-over-limit.use.case.js'; @Module({ imports: [], @@ -76,6 +77,10 @@ import { SaaSRegisterUserWIthSamlUseCase } from './use-cases/register-user-with- provide: UseCaseType.SAAS_REGISTER_USER_WITH_SAML, useClass: SaaSRegisterUserWIthSamlUseCase, }, + { + provide: UseCaseType.SAAS_SUSPEND_USERS_OVER_LIMIT, + useClass: SuspendUsersOverLimitUseCase, + }, ], controllers: [SaasController], exports: [], @@ -92,6 +97,7 @@ export class SaasModule { { path: 'saas/user/google/login', method: RequestMethod.POST }, { path: 'saas/user/github/login', method: RequestMethod.POST }, { path: 'saas/company/:companyId/users/suspend', method: RequestMethod.PUT }, + { path: 'saas/company/:companyId/users/suspend-above-limit', method: RequestMethod.PUT }, { path: 'saas/user/:userId/company', method: RequestMethod.GET }, { path: 'saas/company/:companyId/users/count', method: RequestMethod.GET }, { path: 'saas/company/freeze-connections', method: RequestMethod.PUT }, diff --git a/backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts b/backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts index fbbe002df..b619efcc4 100644 --- a/backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts +++ b/backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts @@ -47,6 +47,10 @@ export interface ISuspendUsers { execute(usersData: SuspendUsersDS): Promise; } +export interface ISuspendUsersOverLimit { + execute(companyId: string): Promise; +} + export interface ISaaSGetCompanyInfoByUserId { execute(userId: string): Promise; } diff --git a/backend/src/microservices/saas-microservice/use-cases/suspend-users-over-limit.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/suspend-users-over-limit.use.case.ts new file mode 100644 index 000000000..4b95a17fa --- /dev/null +++ b/backend/src/microservices/saas-microservice/use-cases/suspend-users-over-limit.use.case.ts @@ -0,0 +1,26 @@ +import { Inject, Injectable } 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 { ISuspendUsersOverLimit } from './saas-use-cases.interface.js'; +import { Constants } from '../../../helpers/constants/constants.js'; + +@Injectable() +export class SuspendUsersOverLimitUseCase extends AbstractUseCase implements ISuspendUsersOverLimit { + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } + + protected async implementation(companyId: string): Promise { + const foundUsersInCompany = await this._dbContext.userRepository.findUsersInCompany(companyId, true); + const usersToSuspend = foundUsersInCompany.slice(Constants.FREE_PLAN_USERS_COUNT); + + if (usersToSuspend.length > 0) { + const userIdsToSuspend = usersToSuspend.map((user) => user.id); + await this._dbContext.userRepository.suspendUsers(userIdsToSuspend); + } + } +}