Files
backend/src/users/services/user-space.service.ts
2025-02-21 21:22:48 +04:00

268 lines
8.4 KiB
TypeScript

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