diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 1d23d9f..17222b1 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -760,6 +760,12 @@ export class ControllerRoute { public static readonly CREATE_USER_INVITATION_DESCRIPTION = 'This endpoint creates an invitation for a user to assign to role and spaces.'; + public static readonly ACTIVATION_CODE_SUMMARY = + 'Activate Invitation Code'; + + public static readonly ACTIVATION_CODE_DESCRIPTION = + 'This endpoint activate invitation code'; + public static readonly CHECK_EMAIL_SUMMARY = 'Check email'; public static readonly CHECK_EMAIL_DESCRIPTION = diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts index 4351345..4bf493f 100644 --- a/src/invite-user/controllers/invite-user.controller.ts +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -8,6 +8,7 @@ import { PermissionsGuard } from 'src/guards/permissions.guard'; import { Permissions } from 'src/decorators/permissions.decorator'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckEmailDto } from '../dtos/check-email.dto'; +import { ActivateCodeDto } from '../dtos/active-code.dto'; @ApiTags('Invite User Module') @Controller({ @@ -51,4 +52,19 @@ export class InviteUserController { addUserInvitationDto, ); } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('activation') + @ApiOperation({ + summary: ControllerRoute.INVITE_USER.ACTIONS.ACTIVATION_CODE_SUMMARY, + description: + ControllerRoute.INVITE_USER.ACTIONS.ACTIVATION_CODE_DESCRIPTION, + }) + async activationCodeController( + @Body() activateCodeDto: ActivateCodeDto, + ): Promise { + return await this.inviteUserService.activationCodeController( + activateCodeDto, + ); + } } diff --git a/src/invite-user/dtos/active-code.dto.ts b/src/invite-user/dtos/active-code.dto.ts new file mode 100644 index 0000000..ea852aa --- /dev/null +++ b/src/invite-user/dtos/active-code.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty } from 'class-validator'; + +export class ActivateCodeDto { + @ApiProperty({ + description: 'The activation code of the user', + example: '7CvRcA', + required: true, + }) + @IsString() + @IsNotEmpty() + activationCode: string; + + @ApiProperty({ + description: 'The UUID of the user', + example: 'd7a44e8a-32d5-4f39-ae2e-013f1245aead', + required: true, + }) + @IsString() + @IsNotEmpty() + userUuid: string; +} diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 402ebb1..3a8e20e 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -3,13 +3,27 @@ import { InviteUserService } from './services/invite-user.service'; import { InviteUserController } from './controllers/invite-user.controller'; import { ConfigModule } from '@nestjs/config'; -import { UserRepository } from '@app/common/modules/user/repositories'; +import { + UserRepository, + UserSpaceRepository, +} from '@app/common/modules/user/repositories'; import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/Invite-user.repository.module'; import { InviteUserRepository, InviteUserSpaceRepository, } from '@app/common/modules/Invite-user/repositiories'; import { EmailService } from '@app/common/util/email.service'; +import { SpaceUserService, ValidationService } from 'src/space/services'; +import { CommunityService } from 'src/community/services'; +import { SpaceRepository } from '@app/common/modules/space'; +import { SpaceModelRepository } from '@app/common/modules/space-model'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { UserSpaceService } from 'src/users/services'; +import { UserDevicePermissionService } from 'src/user-device-permission/services'; +import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; +import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], @@ -20,6 +34,19 @@ import { EmailService } from '@app/common/util/email.service'; UserRepository, EmailService, InviteUserSpaceRepository, + SpaceUserService, + ValidationService, + UserSpaceRepository, + CommunityService, + SpaceRepository, + SpaceModelRepository, + CommunityRepository, + ProjectRepository, + TuyaService, + UserSpaceService, + UserDevicePermissionService, + DeviceUserPermissionRepository, + PermissionTypeRepository, ], exports: [InviteUserService], }) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index aaeabd7..2376b3d 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -16,6 +16,9 @@ import { CheckEmailDto } from '../dtos/check-email.dto'; import { UserRepository } from '@app/common/modules/user/repositories'; import { EmailService } from '@app/common/util/email.service'; import { SpaceEntity } from '@app/common/modules/space'; +import { ActivateCodeDto } from '../dtos/active-code.dto'; +import { UserSpaceService } from 'src/users/services'; +import { SpaceUserService } from 'src/space/services'; @Injectable() export class InviteUserService { @@ -24,6 +27,9 @@ export class InviteUserService { private readonly userRepository: UserRepository, private readonly emailService: EmailService, private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, + private readonly userSpaceService: UserSpaceService, + private readonly spaceUserService: SpaceUserService, + private readonly dataSource: DataSource, ) {} @@ -171,4 +177,86 @@ export class InviteUserService { ); } } + async activationCodeController( + dto: ActivateCodeDto, + ): Promise { + try { + const { activationCode, userUuid } = dto; + const user = await this.userRepository.findOne({ + where: { uuid: userUuid, isActive: true, isUserVerified: true }, + }); + + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + } + const { email } = user; + const invitedUser = await this.inviteUserRepository.findOne({ + where: { + email, + invitationCode: activationCode, + status: UserStatusEnum.INVITED, + isActive: true, + }, + relations: ['project', 'spaces.space.community'], + }); + + if (!invitedUser) { + throw new HttpException( + 'Invalid activation code', + HttpStatus.BAD_REQUEST, + ); + } + + for (const invitedSpace of invitedUser.spaces) { + try { + const deviceUUIDs = + await this.userSpaceService.getDeviceUUIDsForSpace( + invitedSpace.space.uuid, + ); + + await this.userSpaceService.addUserPermissionsToDevices( + userUuid, + deviceUUIDs, + ); + + await this.spaceUserService.associateUserToSpace({ + communityUuid: invitedSpace.space.community.uuid, + spaceUuid: invitedSpace.space.uuid, + userUuid: user.uuid, + projectUuid: invitedUser.project.uuid, + }); + } catch (spaceError) { + console.error( + `Error processing space ${invitedSpace.space.uuid}:`, + spaceError, + ); + // Skip to the next space + continue; + } + } + await this.inviteUserRepository.update( + { uuid: invitedUser.uuid }, + { status: UserStatusEnum.ACTIVE }, + ); + await this.userRepository.update( + { uuid: userUuid }, + { + project: { uuid: invitedUser.project.uuid }, + inviteUser: { uuid: invitedUser.uuid }, + }, + ); + return new SuccessResponseDto({ + statusCode: HttpStatus.OK, + success: true, + message: 'The code has been successfully activated', + }); + } catch (error) { + console.error('Error activating the code:', error); + throw new HttpException( + error.message || + 'An unexpected error occurred while activating the code', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 0ee9af5..0ee6b03 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -117,9 +117,7 @@ export class UserSpaceService { ); } - private async getDeviceUUIDsForSpace( - unitUuid: string, - ): Promise<{ uuid: string }[]> { + async getDeviceUUIDsForSpace(unitUuid: string): Promise<{ uuid: string }[]> { const devices = await this.spaceRepository.find({ where: { uuid: unitUuid }, relations: ['devices', 'devices.productDevice'], @@ -130,7 +128,7 @@ export class UserSpaceService { return allDevices.map((device) => ({ uuid: device.uuid })); } - private async addUserPermissionsToDevices( + async addUserPermissionsToDevices( userUuid: string, deviceUUIDs: { uuid: string }[], ): Promise {