import { BadRequestException, 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 { InviteSpaceRepository, 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 { InviteSpaceEntity } from '@app/common/modules/space/entities/invite-space.entity'; import { UserService } from './user.service'; import { RoleType } from '@app/common/constants/role.type.enum'; import { InviteUserRepository, InviteUserSpaceRepository, } from '@app/common/modules/Invite-user/repositiories'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; @Injectable() export class UserSpaceService { constructor( private readonly userSpaceRepository: UserSpaceRepository, private readonly spaceRepository: SpaceRepository, private readonly inviteSpaceRepository: InviteSpaceRepository, private readonly userService: UserService, private readonly inviteUserRepository: InviteUserRepository, private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, private readonly userDevicePermissionService: UserDevicePermissionService, ) {} async getSpacesForUser(userUuid: string): Promise { let userSpaces = await this.userSpaceRepository.find({ where: { user: { uuid: userUuid } }, relations: ['space', 'space.community'], }); if (!userSpaces || userSpaces.length === 0) { userSpaces = []; } return new SuccessResponseDto({ data: userSpaces, message: `Spaces for user ${userUuid} retrieved successfully`, }); } async verifyCodeAndAddUserSpace( params: AddUserSpaceUsingCodeDto, userUuid: string, ) { const { inviteCode } = params; try { const inviteSpace = await this.findInviteSpaceByInviteCode(inviteCode); const user = await this.userService.getUserDetailsByUserUuid(userUuid); await this.checkSpaceMemberRole(user); await this.addUserToSpace(userUuid, inviteSpace.space.uuid); const deviceUUIDs = await this.getDeviceUUIDsForSpace(inviteSpace.uuid); await this.addUserPermissionsToDevices(userUuid, deviceUUIDs); await this.addUserAsActiveInvitation( user, inviteSpace.space.uuid, inviteCode, ); await this.clearSpaceInvitationCode(inviteSpace.uuid); } catch (err) { if (err instanceof HttpException) { throw err; } else { throw new HttpException( `An unexpected error occurred: ${err.message}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } } private async checkSpaceMemberRole(user: any) { try { if (user.role.type !== RoleType.SPACE_MEMBER) { throw new BadRequestException( 'You have to be a space member to join this space', ); } } catch (err) { throw new HttpException( err.message || 'User not found', err.status || HttpStatus.NOT_FOUND, ); } } private async findInviteSpaceByInviteCode( inviteCode: string, ): Promise { try { const inviteSpace = await this.inviteSpaceRepository.findOneOrFail({ where: { invitationCode: inviteCode, isActive: true, }, relations: ['space'], }); return inviteSpace; } catch (error) { throw new HttpException( 'Invalid invitation code', HttpStatus.BAD_REQUEST, ); } } private async clearSpaceInvitationCode(inviteSpaceUuid: string) { await this.inviteSpaceRepository.update( { uuid: inviteSpaceUuid }, { isActive: false }, ); } async getProjectBySpaceUuid(spaceUuid: string) { try { const project = await this.spaceRepository.findOne({ where: { uuid: spaceUuid, }, relations: ['community.project'], }); return project; } catch (error) { throw new HttpException('Space not found', HttpStatus.NOT_FOUND); } } private async addUserAsActiveInvitation( user: any, spaceUuid: string, inviteCode: string, ) { try { const space = await this.getProjectBySpaceUuid(spaceUuid); const invitedUserData = await this.inviteUserRepository.findOne({ where: { email: user.email, project: { uuid: space.community.project.uuid }, }, }); if (!invitedUserData) { const inviteUser = this.inviteUserRepository.create({ firstName: user.firstName, lastName: user.lastName, email: user.email, jobTitle: null, phoneNumber: null, roleType: { uuid: user.role.uuid }, status: UserStatusEnum.ACTIVE, invitationCode: inviteCode, invitedBy: RoleType.SPACE_OWNER, project: { uuid: space.community.project.uuid }, user: { uuid: user.uuid }, }); const invitedUser = await this.inviteUserRepository.save(inviteUser); const inviteUserSpace = this.inviteUserSpaceRepository.create({ inviteUser: { uuid: invitedUser.uuid }, space: { uuid: spaceUuid }, }); await this.inviteUserSpaceRepository.save(inviteUserSpace); } else { const inviteUserSpace = this.inviteUserSpaceRepository.create({ inviteUser: { uuid: invitedUserData.uuid }, space: { uuid: spaceUuid }, }); await this.inviteUserSpaceRepository.save(inviteUserSpace); } } catch (err) { throw new HttpException( err.message || 'Failed to add user as an active invitation.', HttpStatus.INTERNAL_SERVER_ERROR, ); } } private async addUserToSpace(userUuid: string, spaceUuid: string) { try { const user = await this.addUserSpace({ userUuid, spaceUuid }); return user; } catch (error) { throw new HttpException( `An error occurred while adding user to space ${spaceUuid} : ${error.message}}`, 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, ); } } async getDeviceUUIDsForSpace(unitUuid: string): Promise<{ uuid: string }[]> { const devices = await this.spaceRepository.find({ where: { uuid: unitUuid }, relations: ['devices', 'devices.productDevice'], }); const allDevices = devices.flatMap((space) => space.devices); return allDevices.map((device) => ({ uuid: device.uuid })); } 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); } async deleteUserSpace(spaceUuid: string) { try { await this.userSpaceRepository .createQueryBuilder('userSpace') .leftJoin('userSpace.space', 'space') .delete() .where('space.uuid = :spaceUuid', { spaceUuid }) .execute(); } catch (error) { throw new HttpException( `Failed to delete user-space associations: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } }