Fixed user space permission

This commit is contained in:
hannathkadher
2024-11-05 11:48:57 +04:00
parent a3eaa0fa3c
commit cde9e8b602
7 changed files with 272 additions and 4 deletions

View File

@ -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<BaseResponseDto> {

View File

@ -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<AddUserSpaceDto>) {
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<AddUserSpaceDto>) {
Object.assign(this, dto);
}
}

View File

@ -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<BaseResponseDto> {
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,
);
}
}
}

View File

@ -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<AddUserSpaceDto>) {
Object.assign(this, dto);
}
}
export class AddUserSpaceUsingCodeDto {
@ApiProperty({
description: 'inviteCode',
required: true,
})
@IsString()
@IsNotEmpty()
public inviteCode: string;
constructor(dto: Partial<AddUserSpaceDto>) {
Object.assign(this, dto);
}
}

View File

@ -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';

View File

@ -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<BaseResponseDto> {
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<SpaceEntity> {
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<void> {
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);
}
}

View File

@ -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],