SP-1830: add company name to invite user entity (#468)

This commit is contained in:
ZaydSkaff
2025-07-10 15:41:46 +03:00
committed by GitHub
parent a17a271213
commit a9cb1b6704
7 changed files with 543 additions and 510 deletions

View File

@ -1,6 +1,6 @@
import { RoleType } from '@app/common/constants/role.type.enum';
import { UserStatusEnum } from '@app/common/constants/user-status.enum';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class InviteUserDto {
@IsString()
@ -12,8 +12,12 @@ export class InviteUserDto {
public email: string;
@IsString()
@IsNotEmpty()
public jobTitle: string;
@IsOptional()
public jobTitle?: string;
@IsString()
@IsOptional()
public companyName?: string;
@IsEnum(UserStatusEnum)
@IsNotEmpty()

View File

@ -37,6 +37,11 @@ export class InviteUserEntity extends AbstractEntity<InviteUserDto> {
})
jobTitle: string;
@Column({
nullable: true,
})
companyName: string;
@Column({
nullable: false,
enum: Object.values(UserStatusEnum),

View File

@ -5,6 +5,7 @@ import {
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
export class AddUserInvitationDto {
@ -44,6 +45,15 @@ export class AddUserInvitationDto {
@IsOptional()
public jobTitle?: string;
@ApiProperty({
description: 'The company name of the user',
example: 'Tech Corp',
required: false,
})
@IsString()
@IsOptional()
public companyName?: string;
@ApiProperty({
description: 'The phone number of the user',
example: '+1234567890',
@ -58,7 +68,7 @@ export class AddUserInvitationDto {
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
required: true,
})
@IsString()
@IsUUID('4')
@IsNotEmpty()
public roleUuid: string;
@ApiProperty({
@ -66,15 +76,17 @@ export class AddUserInvitationDto {
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
required: true,
})
@IsString()
@IsUUID('4')
@IsNotEmpty()
public projectUuid: string;
@ApiProperty({
description: 'The array of space UUIDs (at least one required)',
example: ['b5f3c9d2-58b7-4377-b3f7-60acb711d5d9'],
required: true,
})
@IsArray()
@IsUUID('4', { each: true })
@ArrayMinSize(1)
public spaceUuids: string[];
constructor(dto: Partial<AddUserInvitationDto>) {

View File

@ -1,78 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import {
ArrayMinSize,
IsArray,
IsBoolean,
IsNotEmpty,
IsOptional,
IsString,
} from 'class-validator';
import { ApiProperty, OmitType } from '@nestjs/swagger';
import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';
import { AddUserInvitationDto } from './add.invite-user.dto';
export class UpdateUserInvitationDto {
@ApiProperty({
description: 'The first name of the user',
example: 'John',
required: true,
})
@IsString()
@IsNotEmpty()
public firstName: string;
@ApiProperty({
description: 'The last name of the user',
example: 'Doe',
required: true,
})
@IsString()
@IsNotEmpty()
public lastName: string;
@ApiProperty({
description: 'The job title of the user',
example: 'Software Engineer',
required: false,
})
@IsString()
@IsOptional()
public jobTitle?: string;
@ApiProperty({
description: 'The phone number of the user',
example: '+1234567890',
required: false,
})
@IsString()
@IsOptional()
public phoneNumber?: string;
@ApiProperty({
description: 'The role uuid of the user',
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
required: true,
})
@IsString()
@IsNotEmpty()
public roleUuid: string;
@ApiProperty({
description: 'The project uuid of the user',
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
required: true,
})
@IsString()
@IsNotEmpty()
public projectUuid: string;
@ApiProperty({
description: 'The array of space UUIDs (at least one required)',
example: ['b5f3c9d2-58b7-4377-b3f7-60acb711d5d9'],
required: true,
})
@IsArray()
@ArrayMinSize(1)
public spaceUuids: string[];
constructor(dto: Partial<UpdateUserInvitationDto>) {
Object.assign(this, dto);
}
}
export class UpdateUserInvitationDto extends OmitType(AddUserInvitationDto, [
'email',
]) {}
export class DisableUserInvitationDto {
@ApiProperty({
description: 'The disable status of the user',

View File

@ -8,6 +8,8 @@ import {
InviteUserRepository,
InviteUserSpaceRepository,
} from '@app/common/modules/Invite-user/repositiories';
import { ProjectEntity } from '@app/common/modules/project/entities';
import { RoleTypeEntity } from '@app/common/modules/role-type/entities';
import { RoleTypeRepository } from '@app/common/modules/role-type/repositories';
import { SpaceRepository } from '@app/common/modules/space';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
@ -61,6 +63,7 @@ export class InviteUserService {
lastName,
email,
jobTitle,
companyName,
phoneNumber,
roleUuid,
spaceUuids,
@ -90,6 +93,8 @@ export class InviteUserService {
);
}
await this.checkRole(roleUuid, queryRunner);
await this.checkProject(projectUuid, queryRunner);
// Validate spaces
const validSpaces = await this.validateSpaces(
spaceUuids,
@ -102,6 +107,7 @@ export class InviteUserService {
lastName,
email,
jobTitle,
companyName,
phoneNumber,
roleType: { uuid: roleUuid },
status: UserStatusEnum.INVITED,
@ -157,185 +163,6 @@ export class InviteUserService {
await queryRunner.release();
}
}
private async validateSpaces(
spaceUuids: string[],
entityManager: EntityManager,
): Promise<SpaceEntity[]> {
const spaceRepo = entityManager.getRepository(SpaceEntity);
const validSpaces = await spaceRepo.find({
where: { uuid: In(spaceUuids) },
});
if (validSpaces.length !== spaceUuids.length) {
const validSpaceUuids = validSpaces.map((space) => space.uuid);
const invalidSpaceUuids = spaceUuids.filter(
(uuid) => !validSpaceUuids.includes(uuid),
);
throw new HttpException(
`Invalid space UUIDs: ${invalidSpaceUuids.join(', ')}`,
HttpStatus.BAD_REQUEST,
);
}
return validSpaces;
}
async checkEmailAndProject(dto: CheckEmailDto): Promise<BaseResponseDto> {
const { email } = dto;
try {
const user = await this.userRepository.findOne({
where: { email },
relations: ['project'],
});
this.validateUserOrInvite(user, 'User');
const invitedUser = await this.inviteUserRepository.findOne({
where: { email },
relations: ['project'],
});
this.validateUserOrInvite(invitedUser, 'Invited User');
return new SuccessResponseDto({
statusCode: HttpStatus.OK,
success: true,
message: 'Valid email',
});
} catch (error) {
console.error('Error checking email and project:', error);
throw new HttpException(
error.message ||
'An unexpected error occurred while checking the email',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private validateUserOrInvite(user: any, userType: string): void {
if (user) {
if (!user.isActive) {
throw new HttpException(
`${userType} is deleted`,
HttpStatus.BAD_REQUEST,
);
}
if (user.project) {
throw new HttpException(
`${userType} already has a project`,
HttpStatus.BAD_REQUEST,
);
}
}
}
async activationCode(dto: ActivateCodeDto): Promise<BaseResponseDto> {
const { activationCode, userUuid } = dto;
try {
const user = await this.getUser(userUuid);
const invitedUser = await this.inviteUserRepository.findOne({
where: {
email: user.email,
status: UserStatusEnum.INVITED,
isActive: true,
},
relations: ['project', 'spaces.space.community', 'roleType'],
});
if (invitedUser) {
if (invitedUser.invitationCode !== activationCode) {
throw new HttpException(
'Invalid activation code',
HttpStatus.BAD_REQUEST,
);
}
// Handle invited user with valid activation code
await this.handleInvitedUser(user, invitedUser);
} else {
// Handle case for non-invited user
await this.handleNonInvitedUser(activationCode, userUuid);
}
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,
);
}
}
private async getUser(userUuid: string): Promise<UserEntity> {
const user = await this.userRepository.findOne({
where: { uuid: userUuid, isActive: true, isUserVerified: true },
});
if (!user) {
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
}
return user;
}
private async handleNonInvitedUser(
activationCode: string,
userUuid: string,
): Promise<void> {
await this.userSpaceService.verifyCodeAndAddUserSpace(
{ inviteCode: activationCode },
userUuid,
);
}
private async handleInvitedUser(
user: UserEntity,
invitedUser: InviteUserEntity,
): Promise<void> {
for (const invitedSpace of invitedUser.spaces) {
try {
const deviceUUIDs = await this.userSpaceService.getDeviceUUIDsForSpace(
invitedSpace.space.uuid,
);
await this.userSpaceService.addUserPermissionsToDevices(
user.uuid,
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,
);
continue; // Skip to the next space
}
}
// Update invited user and associated user data
await this.inviteUserRepository.update(
{ uuid: invitedUser.uuid },
{ status: UserStatusEnum.ACTIVE, user: { uuid: user.uuid } },
);
await this.userRepository.update(
{ uuid: user.uuid },
{
project: { uuid: invitedUser.project.uuid },
roleType: { uuid: invitedUser.roleType.uuid },
},
);
}
async updateUserInvitation(
dto: UpdateUserInvitationDto,
@ -357,6 +184,9 @@ export class InviteUserService {
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
}
await this.checkRole(dto.roleUuid, queryRunner);
await this.checkProject(projectUuid, queryRunner);
// Perform update actions if status is 'INVITED'
if (userOldData.status === UserStatusEnum.INVITED) {
await this.updateWhenUserIsInvite(queryRunner, dto, invitedUserUuid);
@ -439,174 +269,7 @@ export class InviteUserService {
await queryRunner.release();
}
}
private async getRoleTypeByUuid(roleUuid: string) {
const role = await this.roleTypeRepository.findOne({
where: { uuid: roleUuid },
});
return role.type;
}
private async updateWhenUserIsInvite(
queryRunner: QueryRunner,
dto: UpdateUserInvitationDto,
invitedUserUuid: string,
): Promise<void> {
const { firstName, lastName, jobTitle, phoneNumber, roleUuid, spaceUuids } =
dto;
// Update user invitation details
await queryRunner.manager.update(
this.inviteUserRepository.target,
{ uuid: invitedUserUuid },
{
firstName,
lastName,
jobTitle,
phoneNumber,
roleType: { uuid: roleUuid },
},
);
// Remove old space associations
await queryRunner.manager.delete(this.inviteUserSpaceRepository.target, {
inviteUser: { uuid: invitedUserUuid },
});
// Save new space associations
const spaceData = spaceUuids.map((spaceUuid) => ({
inviteUser: { uuid: invitedUserUuid },
space: { uuid: spaceUuid },
}));
await queryRunner.manager.save(
this.inviteUserSpaceRepository.target,
spaceData,
);
}
private async updateWhenUserIsActive(
queryRunner: QueryRunner,
dto: UpdateUserInvitationDto,
invitedUserUuid: string,
): Promise<void> {
const {
firstName,
lastName,
jobTitle,
phoneNumber,
roleUuid,
spaceUuids,
projectUuid,
} = dto;
const userData = await this.inviteUserRepository.findOne({
where: { uuid: invitedUserUuid },
relations: ['user.userSpaces.space', 'user.userSpaces.space.community'],
});
if (!userData) {
throw new HttpException(
`User with invitedUserUuid ${invitedUserUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
// Update user details
await queryRunner.manager.update(
this.inviteUserRepository.target,
{ uuid: invitedUserUuid },
{
firstName,
lastName,
jobTitle,
phoneNumber,
roleType: { uuid: roleUuid },
},
);
await this.userRepository.update(
{ uuid: userData.user.uuid },
{
roleType: { uuid: roleUuid },
},
);
// Disassociate the user from all current spaces
const disassociatePromises = userData.user.userSpaces.map((userSpace) =>
this.spaceUserService
.disassociateUserFromSpace({
communityUuid: userSpace.space.community.uuid,
spaceUuid: userSpace.space.uuid,
userUuid: userData.user.uuid,
projectUuid,
})
.catch((error) => {
console.error(
`Failed to disassociate user from space ${userSpace.space.uuid}:`,
error,
);
throw error;
}),
);
await Promise.allSettled(disassociatePromises);
// Process new spaces
const associatePromises = spaceUuids.map(async (spaceUuid) => {
try {
// Fetch space details
const spaceDetails = await this.getSpaceByUuid(spaceUuid);
// Fetch device UUIDs for the space
const deviceUUIDs =
await this.userSpaceService.getDeviceUUIDsForSpace(spaceUuid);
// Grant permissions to the user for all devices in the space
await this.userSpaceService.addUserPermissionsToDevices(
userData.user.uuid,
deviceUUIDs,
);
// Associate the user with the new space
await this.spaceUserService.associateUserToSpace({
communityUuid: spaceDetails.communityUuid,
spaceUuid: spaceUuid,
userUuid: userData.user.uuid,
projectUuid,
});
} catch (error) {
console.error(`Failed to process space ${spaceUuid}:`, error);
throw error;
}
});
await Promise.all(associatePromises);
}
async getSpaceByUuid(spaceUuid: string) {
try {
const space = await this.spaceRepository.findOne({
where: {
uuid: spaceUuid,
},
relations: ['community'],
});
if (!space) {
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
}
return {
uuid: space.uuid,
createdAt: space.createdAt,
updatedAt: space.updatedAt,
name: space.spaceName,
spaceTuyaUuid: space.community.externalId,
communityUuid: space.community.uuid,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Space not found', HttpStatus.NOT_FOUND);
}
}
}
async disableUserInvitation(
dto: DisableUserInvitationDto,
invitedUserUuid: string,
@ -686,74 +349,6 @@ export class InviteUserService {
}
}
private async updateUserStatus(
invitedUserUuid: string,
projectUuid: string,
isEnabled: boolean,
) {
await this.inviteUserRepository.update(
{ uuid: invitedUserUuid, project: { uuid: projectUuid } },
{ isEnabled },
);
}
private async disassociateUserFromSpaces(user: any, projectUuid: string) {
const disassociatePromises = user.userSpaces.map((userSpace) =>
this.spaceUserService.disassociateUserFromSpace({
communityUuid: userSpace.space.community.uuid,
spaceUuid: userSpace.space.uuid,
userUuid: user.uuid,
projectUuid,
}),
);
const results = await Promise.allSettled(disassociatePromises);
results.forEach((result, index) => {
if (result.status === 'rejected') {
console.error(
`Failed to disassociate user from space ${user.userSpaces[index].space.uuid}:`,
result.reason,
);
}
});
}
private async associateUserToSpaces(
user: any,
userData: any,
projectUuid: string,
invitedUserUuid: string,
disable: boolean,
) {
const spaceUuids = userData.spaces.map((space) => space.space.uuid);
const associatePromises = spaceUuids.map(async (spaceUuid) => {
try {
const spaceDetails = await this.getSpaceByUuid(spaceUuid);
const deviceUUIDs =
await this.userSpaceService.getDeviceUUIDsForSpace(spaceUuid);
await this.userSpaceService.addUserPermissionsToDevices(
user.uuid,
deviceUUIDs,
);
await this.spaceUserService.associateUserToSpace({
communityUuid: spaceDetails.communityUuid,
spaceUuid,
userUuid: user.uuid,
projectUuid,
});
await this.updateUserStatus(invitedUserUuid, projectUuid, disable);
} catch (error) {
console.error(`Failed to associate user to space ${spaceUuid}:`, error);
}
});
await Promise.allSettled(associatePromises);
}
async deleteUserInvitation(
invitedUserUuid: string,
): Promise<BaseResponseDto> {
@ -824,4 +419,486 @@ export class InviteUserService {
await queryRunner.release();
}
}
async activationCode(dto: ActivateCodeDto): Promise<BaseResponseDto> {
const { activationCode, userUuid } = dto;
try {
const user = await this.getUser(userUuid);
const invitedUser = await this.inviteUserRepository.findOne({
where: {
email: user.email,
status: UserStatusEnum.INVITED,
isActive: true,
},
relations: ['project', 'spaces.space.community', 'roleType'],
});
if (invitedUser) {
if (invitedUser.invitationCode !== activationCode) {
throw new HttpException(
'Invalid activation code',
HttpStatus.BAD_REQUEST,
);
}
// Handle invited user with valid activation code
await this.handleInvitedUser(user, invitedUser);
} else {
// Handle case for non-invited user
await this.handleNonInvitedUser(activationCode, userUuid);
}
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,
);
}
}
async checkEmailAndProject(dto: CheckEmailDto): Promise<BaseResponseDto> {
const { email } = dto;
try {
const user = await this.userRepository.findOne({
where: { email },
relations: ['project'],
});
this.validateUserOrInvite(user, 'User');
const invitedUser = await this.inviteUserRepository.findOne({
where: { email },
relations: ['project'],
});
this.validateUserOrInvite(invitedUser, 'Invited User');
return new SuccessResponseDto({
statusCode: HttpStatus.OK,
success: true,
message: 'Valid email',
});
} catch (error) {
console.error('Error checking email and project:', error);
throw new HttpException(
error.message ||
'An unexpected error occurred while checking the email',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private async getSpaceByUuid(spaceUuid: string) {
try {
const space = await this.spaceRepository.findOne({
where: {
uuid: spaceUuid,
},
relations: ['community'],
});
if (!space) {
throw new BadRequestException(`Invalid space UUID ${spaceUuid}`);
}
return {
uuid: space.uuid,
createdAt: space.createdAt,
updatedAt: space.updatedAt,
name: space.spaceName,
spaceTuyaUuid: space.community.externalId,
communityUuid: space.community.uuid,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Space not found', HttpStatus.NOT_FOUND);
}
}
}
private async validateSpaces(
spaceUuids: string[],
entityManager: EntityManager,
): Promise<SpaceEntity[]> {
const spaceRepo = entityManager.getRepository(SpaceEntity);
const validSpaces = await spaceRepo.find({
where: { uuid: In(spaceUuids) },
});
if (validSpaces.length !== spaceUuids.length) {
const validSpaceUuids = validSpaces.map((space) => space.uuid);
const invalidSpaceUuids = spaceUuids.filter(
(uuid) => !validSpaceUuids.includes(uuid),
);
throw new HttpException(
`Invalid space UUIDs: ${invalidSpaceUuids.join(', ')}`,
HttpStatus.BAD_REQUEST,
);
}
return validSpaces;
}
private async checkRole(
roleUuid: string,
queryRunner: QueryRunner,
): Promise<BaseResponseDto> {
try {
const role = await queryRunner.manager.findOne(RoleTypeEntity, {
where: { uuid: roleUuid },
});
if (!role) {
throw new HttpException('Role not found', HttpStatus.NOT_FOUND);
}
return new SuccessResponseDto({
statusCode: HttpStatus.OK,
success: true,
message: 'Valid role',
});
} catch (error) {
console.error('Error checking role:', error);
throw new HttpException(
error.message || 'An unexpected error occurred while checking the role',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private async checkProject(
projectUuid: string,
queryRunner: QueryRunner,
): Promise<BaseResponseDto> {
try {
const project = await queryRunner.manager.findOne(ProjectEntity, {
where: { uuid: projectUuid },
});
if (!project) {
throw new HttpException('Project not found', HttpStatus.NOT_FOUND);
}
return new SuccessResponseDto({
statusCode: HttpStatus.OK,
success: true,
message: 'Valid project',
});
} catch (error) {
console.error('Error checking project:', error);
throw new HttpException(
error.message ||
'An unexpected error occurred while checking the project',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private validateUserOrInvite(user: any, userType: string): void {
if (user) {
if (!user.isActive) {
throw new HttpException(
`${userType} is deleted`,
HttpStatus.BAD_REQUEST,
);
}
if (user.project) {
throw new HttpException(
`${userType} already has a project`,
HttpStatus.BAD_REQUEST,
);
}
}
}
private async getUser(userUuid: string): Promise<UserEntity> {
const user = await this.userRepository.findOne({
where: { uuid: userUuid, isActive: true, isUserVerified: true },
});
if (!user) {
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
}
return user;
}
private async handleNonInvitedUser(
activationCode: string,
userUuid: string,
): Promise<void> {
await this.userSpaceService.verifyCodeAndAddUserSpace(
{ inviteCode: activationCode },
userUuid,
);
}
private async handleInvitedUser(
user: UserEntity,
invitedUser: InviteUserEntity,
): Promise<void> {
for (const invitedSpace of invitedUser.spaces) {
try {
const deviceUUIDs = await this.userSpaceService.getDeviceUUIDsForSpace(
invitedSpace.space.uuid,
);
await this.userSpaceService.addUserPermissionsToDevices(
user.uuid,
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,
);
continue; // Skip to the next space
}
}
// Update invited user and associated user data
await this.inviteUserRepository.update(
{ uuid: invitedUser.uuid },
{ status: UserStatusEnum.ACTIVE, user: { uuid: user.uuid } },
);
await this.userRepository.update(
{ uuid: user.uuid },
{
project: { uuid: invitedUser.project.uuid },
roleType: { uuid: invitedUser.roleType.uuid },
},
);
}
private async getRoleTypeByUuid(roleUuid: string) {
const role = await this.roleTypeRepository.findOne({
where: { uuid: roleUuid },
});
return role.type;
}
private async updateWhenUserIsInvite(
queryRunner: QueryRunner,
dto: UpdateUserInvitationDto,
invitedUserUuid: string,
): Promise<void> {
const {
firstName,
lastName,
jobTitle,
companyName,
phoneNumber,
roleUuid,
spaceUuids,
} = dto;
// Update user invitation details
await queryRunner.manager.update(
this.inviteUserRepository.target,
{ uuid: invitedUserUuid },
{
firstName,
lastName,
jobTitle,
companyName,
phoneNumber,
roleType: { uuid: roleUuid },
},
);
// Remove old space associations
await queryRunner.manager.delete(this.inviteUserSpaceRepository.target, {
inviteUser: { uuid: invitedUserUuid },
});
// Save new space associations
const spaceData = spaceUuids.map((spaceUuid) => ({
inviteUser: { uuid: invitedUserUuid },
space: { uuid: spaceUuid },
}));
await queryRunner.manager.save(
this.inviteUserSpaceRepository.target,
spaceData,
);
}
private async updateWhenUserIsActive(
queryRunner: QueryRunner,
dto: UpdateUserInvitationDto,
invitedUserUuid: string,
): Promise<void> {
const {
firstName,
lastName,
jobTitle,
companyName,
phoneNumber,
roleUuid,
spaceUuids,
projectUuid,
} = dto;
const userData = await this.inviteUserRepository.findOne({
where: { uuid: invitedUserUuid },
relations: ['user.userSpaces.space', 'user.userSpaces.space.community'],
});
if (!userData) {
throw new HttpException(
`User with invitedUserUuid ${invitedUserUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
// Update user details
await queryRunner.manager.update(
this.inviteUserRepository.target,
{ uuid: invitedUserUuid },
{
firstName,
lastName,
jobTitle,
companyName,
phoneNumber,
roleType: { uuid: roleUuid },
},
);
await this.userRepository.update(
{ uuid: userData.user.uuid },
{
roleType: { uuid: roleUuid },
},
);
// Disassociate the user from all current spaces
const disassociatePromises = userData.user.userSpaces.map((userSpace) =>
this.spaceUserService
.disassociateUserFromSpace({
communityUuid: userSpace.space.community.uuid,
spaceUuid: userSpace.space.uuid,
userUuid: userData.user.uuid,
projectUuid,
})
.catch((error) => {
console.error(
`Failed to disassociate user from space ${userSpace.space.uuid}:`,
error,
);
throw error;
}),
);
await Promise.allSettled(disassociatePromises);
// Process new spaces
const associatePromises = spaceUuids.map(async (spaceUuid) => {
try {
// Fetch space details
const spaceDetails = await this.getSpaceByUuid(spaceUuid);
// Fetch device UUIDs for the space
const deviceUUIDs =
await this.userSpaceService.getDeviceUUIDsForSpace(spaceUuid);
// Grant permissions to the user for all devices in the space
await this.userSpaceService.addUserPermissionsToDevices(
userData.user.uuid,
deviceUUIDs,
);
// Associate the user with the new space
await this.spaceUserService.associateUserToSpace({
communityUuid: spaceDetails.communityUuid,
spaceUuid: spaceUuid,
userUuid: userData.user.uuid,
projectUuid,
});
} catch (error) {
console.error(`Failed to process space ${spaceUuid}:`, error);
throw error;
}
});
await Promise.all(associatePromises);
}
private async updateUserStatus(
invitedUserUuid: string,
projectUuid: string,
isEnabled: boolean,
) {
await this.inviteUserRepository.update(
{ uuid: invitedUserUuid, project: { uuid: projectUuid } },
{ isEnabled },
);
}
private async disassociateUserFromSpaces(user: any, projectUuid: string) {
const disassociatePromises = user.userSpaces.map((userSpace) =>
this.spaceUserService.disassociateUserFromSpace({
communityUuid: userSpace.space.community.uuid,
spaceUuid: userSpace.space.uuid,
userUuid: user.uuid,
projectUuid,
}),
);
const results = await Promise.allSettled(disassociatePromises);
results.forEach((result, index) => {
if (result.status === 'rejected') {
console.error(
`Failed to disassociate user from space ${user.userSpaces[index].space.uuid}:`,
result.reason,
);
}
});
}
private async associateUserToSpaces(
user: any,
userData: any,
projectUuid: string,
invitedUserUuid: string,
disable: boolean,
) {
const spaceUuids = userData.spaces.map((space) => space.space.uuid);
const associatePromises = spaceUuids.map(async (spaceUuid) => {
try {
const spaceDetails = await this.getSpaceByUuid(spaceUuid);
const deviceUUIDs =
await this.userSpaceService.getDeviceUUIDsForSpace(spaceUuid);
await this.userSpaceService.addUserPermissionsToDevices(
user.uuid,
deviceUUIDs,
);
await this.spaceUserService.associateUserToSpace({
communityUuid: spaceDetails.communityUuid,
spaceUuid,
userUuid: user.uuid,
projectUuid,
});
await this.updateUserStatus(invitedUserUuid, projectUuid, disable);
} catch (error) {
console.error(`Failed to associate user to space ${spaceUuid}:`, error);
}
});
await Promise.allSettled(associatePromises);
}
}

View File

@ -33,6 +33,7 @@ export class ProjectUserService {
'status',
'phoneNumber',
'jobTitle',
'companyName',
'invitedBy',
'isEnabled',
],
@ -91,6 +92,7 @@ export class ProjectUserService {
'status',
'phoneNumber',
'jobTitle',
'companyName',
'invitedBy',
'isEnabled',
],

View File

@ -1,28 +1,28 @@
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
import { PermissionType } from '@app/common/constants/permission-type.enum';
import { RoleType } from '@app/common/constants/role.type.enum';
import { UserStatusEnum } from '@app/common/constants/user-status.enum';
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import {
InviteUserRepository,
InviteUserSpaceRepository,
} from '@app/common/modules/Invite-user/repositiories';
import { InviteSpaceEntity } from '@app/common/modules/space/entities/invite-space.entity';
import {
InviteSpaceRepository,
SpaceRepository,
} from '@app/common/modules/space/repositories';
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
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 { AddUserSpaceDto, AddUserSpaceUsingCodeDto } from '../dtos';
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 {
@ -154,6 +154,7 @@ export class UserSpaceService {
lastName: user.lastName,
email: user.email,
jobTitle: null,
companyName: null,
phoneNumber: null,
roleType: { uuid: user.role.uuid },
status: UserStatusEnum.ACTIVE,