Merge pull request #181 from SyncrowIOT/SP-933-be-implement-email-existence-validation-invite-user

Sp 933 be implement email existence validation invite user
This commit is contained in:
hannathkadher
2024-12-23 10:04:19 +04:00
committed by GitHub
8 changed files with 105 additions and 1 deletions

View File

@ -733,6 +733,10 @@ export class ControllerRoute {
public static readonly CREATE_USER_INVITATION_DESCRIPTION = public static readonly CREATE_USER_INVITATION_DESCRIPTION =
'This endpoint creates an invitation for a user to assign to role and spaces.'; 'This endpoint creates an invitation for a user to assign to role and spaces.';
public static readonly CHECK_EMAIL_SUMMARY = 'Check email';
public static readonly CHECK_EMAIL_DESCRIPTION =
'This endpoint checks if an email already exists and have a project in the system.';
}; };
}; };
static PERMISSION = class { static PERMISSION = class {

View File

@ -15,6 +15,7 @@ import { UserEntity } from '../../user/entities';
import { SpaceEntity } from '../../space/entities'; import { SpaceEntity } from '../../space/entities';
import { RoleType } from '@app/common/constants/role.type.enum'; import { RoleType } from '@app/common/constants/role.type.enum';
import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; import { InviteUserDto, InviteUserSpaceDto } from '../dtos';
import { ProjectEntity } from '../../project/entities';
@Entity({ name: 'invite-user' }) @Entity({ name: 'invite-user' })
@Unique(['email', 'invitationCode']) @Unique(['email', 'invitationCode'])
@ -91,6 +92,13 @@ export class InviteUserEntity extends AbstractEntity<InviteUserDto> {
(inviteUserSpace) => inviteUserSpace.inviteUser, (inviteUserSpace) => inviteUserSpace.inviteUser,
) )
spaces: InviteUserSpaceEntity[]; spaces: InviteUserSpaceEntity[];
@ManyToOne(() => ProjectEntity, (project) => project.invitedUsers, {
nullable: true,
})
@JoinColumn({ name: 'project_uuid' })
public project: ProjectEntity;
constructor(partial: Partial<InviteUserEntity>) { constructor(partial: Partial<InviteUserEntity>) {
super(); super();
Object.assign(this, partial); Object.assign(this, partial);

View File

@ -4,6 +4,7 @@ import { ProjectDto } from '../dtos';
import { CommunityEntity } from '../../community/entities'; import { CommunityEntity } from '../../community/entities';
import { SpaceModelEntity } from '../../space-model'; import { SpaceModelEntity } from '../../space-model';
import { UserEntity } from '../../user/entities'; import { UserEntity } from '../../user/entities';
import { InviteUserEntity } from '../../Invite-user/entities';
@Entity({ name: 'project' }) @Entity({ name: 'project' })
@Unique(['name']) @Unique(['name'])
@ -32,6 +33,9 @@ export class ProjectEntity extends AbstractEntity<ProjectDto> {
@OneToMany(() => UserEntity, (user) => user.project) @OneToMany(() => UserEntity, (user) => user.project)
public users: UserEntity[]; public users: UserEntity[];
@OneToMany(() => InviteUserEntity, (inviteUser) => inviteUser.project)
public invitedUsers: InviteUserEntity[];
constructor(partial: Partial<ProjectEntity>) { constructor(partial: Partial<ProjectEntity>) {
super(); super();
Object.assign(this, partial); Object.assign(this, partial);

View File

@ -22,7 +22,7 @@ export class SpaceProductEntity extends AbstractEntity<SpaceProductEntity> {
product: ProductEntity; product: ProductEntity;
@Column({ @Column({
nullable: false, nullable: true,
type: 'int', type: 'int',
}) })
productCount: number; productCount: number;

View File

@ -6,6 +6,8 @@ import { ControllerRoute } from '@app/common/constants/controller-route';
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { PermissionsGuard } from 'src/guards/permissions.guard'; import { PermissionsGuard } from 'src/guards/permissions.guard';
import { Permissions } from 'src/decorators/permissions.decorator'; import { Permissions } from 'src/decorators/permissions.decorator';
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
import { CheckEmailDto } from '../dtos/check-email.dto';
@ApiTags('Invite User Module') @ApiTags('Invite User Module')
@Controller({ @Controller({
@ -34,4 +36,19 @@ export class InviteUserController {
user.role.type, user.role.type,
); );
} }
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Post('check-email')
@ApiOperation({
summary: ControllerRoute.INVITE_USER.ACTIONS.CHECK_EMAIL_SUMMARY,
description: ControllerRoute.INVITE_USER.ACTIONS.CHECK_EMAIL_DESCRIPTION,
})
async checkEmailAndProject(
@Body() addUserInvitationDto: CheckEmailDto,
): Promise<BaseResponseDto> {
return await this.inviteUserService.checkEmailAndProject(
addUserInvitationDto,
);
}
} }

View File

@ -61,6 +61,14 @@ export class AddUserInvitationDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public roleUuid: string; 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({ @ApiProperty({
description: 'The array of space UUIDs (at least one required)', description: 'The array of space UUIDs (at least one required)',
example: ['b5f3c9d2-58b7-4377-b3f7-60acb711d5d9'], example: ['b5f3c9d2-58b7-4377-b3f7-60acb711d5d9'],

View File

@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty } from 'class-validator';
export class CheckEmailDto {
@ApiProperty({
description: 'The email of the user',
example: 'OqM9A@example.com',
required: true,
})
@IsEmail()
@IsNotEmpty()
email: string;
constructor(dto: Partial<CheckEmailDto>) {
Object.assign(this, dto);
}
}

View File

@ -12,11 +12,14 @@ import {
InviteUserRepository, InviteUserRepository,
InviteUserSpaceRepository, InviteUserSpaceRepository,
} from '@app/common/modules/Invite-user/repositiories'; } from '@app/common/modules/Invite-user/repositiories';
import { CheckEmailDto } from '../dtos/check-email.dto';
import { UserRepository } from '@app/common/modules/user/repositories';
@Injectable() @Injectable()
export class InviteUserService { export class InviteUserService {
constructor( constructor(
private readonly inviteUserRepository: InviteUserRepository, private readonly inviteUserRepository: InviteUserRepository,
private readonly userRepository: UserRepository,
private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, private readonly inviteUserSpaceRepository: InviteUserSpaceRepository,
private readonly dataSource: DataSource, private readonly dataSource: DataSource,
) {} ) {}
@ -33,6 +36,7 @@ export class InviteUserService {
phoneNumber, phoneNumber,
roleUuid, roleUuid,
spaceUuids, spaceUuids,
projectUuid,
} = dto; } = dto;
const invitationCode = generateRandomString(6); const invitationCode = generateRandomString(6);
@ -67,6 +71,7 @@ export class InviteUserService {
status: UserStatusEnum.INVITED, status: UserStatusEnum.INVITED,
invitationCode, invitationCode,
invitedBy: roleType, invitedBy: roleType,
project: { uuid: projectUuid },
}); });
const invitedUser = await queryRunner.manager.save(inviteUser); const invitedUser = await queryRunner.manager.save(inviteUser);
@ -105,4 +110,46 @@ export class InviteUserService {
await queryRunner.release(); await queryRunner.release();
} }
} }
async checkEmailAndProject(dto: CheckEmailDto): Promise<BaseResponseDto> {
const { email } = dto;
try {
const user = await this.userRepository.findOne({
where: { email },
relations: ['project'],
});
if (user?.project) {
throw new HttpException(
'This email already has a project',
HttpStatus.BAD_REQUEST,
);
}
const invitedUser = await this.inviteUserRepository.findOne({
where: { email },
relations: ['project'],
});
if (invitedUser?.project) {
throw new HttpException(
'This email already has a project',
HttpStatus.BAD_REQUEST,
);
}
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,
);
}
}
} }