From cde9e8b602b4cdd1cd7dcdd88d79df5b56879057 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 5 Nov 2024 11:48:57 +0400 Subject: [PATCH] Fixed user space permission --- src/space/controllers/space.controller.ts | 3 +- src/space/dtos/add.space.dto.ts | 40 ++++++ .../controllers/user-space.controller.ts | 38 +++++- src/users/dtos/add.space.dto.ts | 68 ++++++++++ src/users/dtos/index.ts | 1 + src/users/services/user-space.service.ts | 118 +++++++++++++++++- src/users/user.module.ts | 8 ++ 7 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 src/users/dtos/add.space.dto.ts diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index cb8cbb8..e0615eb 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -111,6 +111,7 @@ export class SpaceController { return this.spaceService.getSpacesHierarchyForSpace(params.spaceUuid); } + //should it be post? @ApiBearerAuth() @UseGuards(JwtAuthGuard) @ApiOperation({ @@ -118,7 +119,7 @@ export class SpaceController { description: ControllerRoute.SPACE.ACTIONS.CREATE_INVITATION_CODE_SPACE_DESCRIPTION, }) - @Post(':spaceUuid/invitation-code') + @Get(':spaceUuid/invitation-code') async generateSpaceInvitationCode( @Param() params: GetSpaceParam, ): Promise { diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index 9d383bd..a33bb34 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -33,3 +33,43 @@ export class AddSpaceDto { @IsBoolean() isPrivate: boolean; } + +export class AddUserSpaceDto { + @ApiProperty({ + description: 'spaceUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public spaceUuid: string; + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} + +export class AddUserSpaceUsingCodeDto { + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + @ApiProperty({ + description: 'inviteCode', + required: true, + }) + @IsString() + @IsNotEmpty() + public inviteCode: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/users/controllers/user-space.controller.ts b/src/users/controllers/user-space.controller.ts index 519a033..df36a5a 100644 --- a/src/users/controllers/user-space.controller.ts +++ b/src/users/controllers/user-space.controller.ts @@ -1,11 +1,20 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Get, + HttpException, + HttpStatus, + Param, + Post, + UseGuards, +} from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { UserSpaceService } from '../services'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { UserParamDto } from '../dtos'; +import { AddUserSpaceUsingCodeDto, UserParamDto } from '../dtos'; @ApiTags('User Module') @Controller({ @@ -27,4 +36,29 @@ export class UserSpaceController { ): Promise { return this.userSpaceService.getSpacesForUser(params.userUuid); } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('/verify-code') + async verifyCodeAndAddUserSpace( + @Body() dto: AddUserSpaceUsingCodeDto, + @Param() params: UserParamDto, + ) { + try { + await this.userSpaceService.verifyCodeAndAddUserSpace( + dto, + params.userUuid, + ); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'user space added successfully', + }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/users/dtos/add.space.dto.ts b/src/users/dtos/add.space.dto.ts new file mode 100644 index 0000000..635cec9 --- /dev/null +++ b/src/users/dtos/add.space.dto.ts @@ -0,0 +1,68 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsBoolean, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; + +export class AddSpaceDto { + @ApiProperty({ + description: 'Name of the space (e.g., Floor 1, Unit 101)', + example: 'Unit 101', + }) + @IsString() + @IsNotEmpty() + spaceName: string; + + @ApiProperty({ + description: 'UUID of the parent space (if any, for hierarchical spaces)', + example: 'f5d7e9c3-44bc-4b12-88f1-1b3cda84752e', + required: false, + }) + @IsUUID() + @IsOptional() + parentUuid?: string; + + @ApiProperty({ + description: 'Indicates whether the space is private or public', + example: false, + default: false, + }) + @IsBoolean() + isPrivate: boolean; +} + +export class AddUserSpaceDto { + @ApiProperty({ + description: 'spaceUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public spaceUuid: string; + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} + +export class AddUserSpaceUsingCodeDto { + @ApiProperty({ + description: 'inviteCode', + required: true, + }) + @IsString() + @IsNotEmpty() + public inviteCode: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/users/dtos/index.ts b/src/users/dtos/index.ts index 5ca1ea7..83bb739 100644 --- a/src/users/dtos/index.ts +++ b/src/users/dtos/index.ts @@ -1,3 +1,4 @@ export * from './update.user.dto'; export * from './user-community-param.dto'; export * from './user-param.dto'; +export * from './add.space.dto'; diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 68e6f00..ca3b8d9 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -2,10 +2,20 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { AddUserSpaceDto, AddUserSpaceUsingCodeDto } from '../dtos'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; +import { UserDevicePermissionService } from 'src/user-device-permission/services'; +import { PermissionType } from '@app/common/constants/permission-type.enum'; +import { SpaceEntity } from '@app/common/modules/space/entities'; @Injectable() export class UserSpaceService { - constructor(private readonly userSpaceRepository: UserSpaceRepository) {} + constructor( + private readonly userSpaceRepository: UserSpaceRepository, + private readonly spaceRepository: SpaceRepository, + private readonly userDevicePermissionService: UserDevicePermissionService, + ) {} async getSpacesForUser(userUuid: string): Promise { const userSpaces = await this.userSpaceRepository.find({ @@ -25,4 +35,110 @@ export class UserSpaceService { message: `Spaces for user ${userUuid} retrieved successfully`, }); } + + async verifyCodeAndAddUserSpace( + params: AddUserSpaceUsingCodeDto, + userUuid: string, + ) { + try { + const space = await this.findUnitByInviteCode(params.inviteCode); + + await this.addUserToSpace(userUuid, space.uuid); + + await this.clearUnitInvitationCode(space.uuid); + + const deviceUUIDs = await this.getDeviceUUIDsForSpace(space.uuid); + + await this.addUserPermissionsToDevices(userUuid, deviceUUIDs); + } catch (err) { + throw new HttpException( + 'Invalid invitation code', + HttpStatus.BAD_REQUEST, + ); + } + } + + private async findUnitByInviteCode(inviteCode: string): Promise { + const space = await this.spaceRepository.findOneOrFail({ + where: { + invitationCode: inviteCode, + }, + }); + + return space; + } + + private async addUserToSpace(userUuid: string, spaceUuid: string) { + const user = await this.addUserSpace({ userUuid, spaceUuid }); + + if (user.uuid) { + return user; + } else { + throw new HttpException( + 'Failed to add user to unit', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async addUserSpace(addUserSpaceDto: AddUserSpaceDto) { + try { + return await this.userSpaceRepository.save({ + user: { uuid: addUserSpaceDto.userUuid }, + space: { uuid: addUserSpaceDto.spaceUuid }, + }); + } catch (err) { + if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { + throw new HttpException( + 'User already belongs to this unit', + HttpStatus.BAD_REQUEST, + ); + } + throw new HttpException( + err.message || 'Internal Server Error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async clearUnitInvitationCode(spaceUuid: string) { + await this.spaceRepository.update( + { uuid: spaceUuid }, + { invitationCode: null }, + ); + } + + private async getDeviceUUIDsForSpace( + unitUuid: string, + ): Promise<{ uuid: string }[]> { + const devices = await this.spaceRepository.find({ + where: { uuid: unitUuid }, + relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'], + }); + + const allDevices = devices.flatMap((space) => space.devices); + + return allDevices.map((device) => ({ uuid: device.uuid })); + } + + private async addUserPermissionsToDevices( + userUuid: string, + deviceUUIDs: { uuid: string }[], + ): Promise { + const permissionPromises = deviceUUIDs.map(async (device) => { + try { + await this.userDevicePermissionService.addUserPermission({ + userUuid, + deviceUuid: device.uuid, + permissionType: PermissionType.CONTROLLABLE, + }); + } catch (error) { + console.error( + `Failed to add permission for device ${device.uuid}: ${error.message}`, + ); + } + }); + + await Promise.all(permissionPromises); + } } diff --git a/src/users/user.module.ts b/src/users/user.module.ts index d65ee68..20bd06f 100644 --- a/src/users/user.module.ts +++ b/src/users/user.module.ts @@ -12,6 +12,10 @@ import { UserSpaceController } from './controllers'; import { CommunityModule } from 'src/community/community.module'; import { UserSpaceService } from './services'; import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +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, CommunityModule], @@ -20,9 +24,13 @@ import { CommunityRepository } from '@app/common/modules/community/repositories' UserService, UserRepository, RegionRepository, + SpaceRepository, TimeZoneRepository, UserSpaceRepository, CommunityRepository, + UserDevicePermissionService, + DeviceUserPermissionRepository, + PermissionTypeRepository, UserSpaceService, ], exports: [UserService],