From 5cae090b9aac599882eb6e999a1720d63fcff96c Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 23 Dec 2024 00:00:19 -0600 Subject: [PATCH 1/3] fixed null issue --- libs/common/src/modules/space/entities/space-product.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts index 5f8a062..a1d27ef 100644 --- a/libs/common/src/modules/space/entities/space-product.entity.ts +++ b/libs/common/src/modules/space/entities/space-product.entity.ts @@ -22,7 +22,7 @@ export class SpaceProductEntity extends AbstractEntity { product: ProductEntity; @Column({ - nullable: false, + nullable: true, type: 'int', }) productCount: number; From 39fd6e9dd92f3eb97ddd379002ec0f4b69a1a5f6 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 23 Dec 2024 00:01:11 -0600 Subject: [PATCH 2/3] Add project relationship to InviteUserEntity and invitedUsers to ProjectEntity --- .../modules/Invite-user/entities/Invite-user.entity.ts | 8 ++++++++ .../common/src/modules/project/entities/project.entity.ts | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index e2414f5..c93f94b 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -15,6 +15,7 @@ import { UserEntity } from '../../user/entities'; import { SpaceEntity } from '../../space/entities'; import { RoleType } from '@app/common/constants/role.type.enum'; import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; +import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'invite-user' }) @Unique(['email', 'invitationCode']) @@ -91,6 +92,13 @@ export class InviteUserEntity extends AbstractEntity { (inviteUserSpace) => inviteUserSpace.inviteUser, ) spaces: InviteUserSpaceEntity[]; + + @ManyToOne(() => ProjectEntity, (project) => project.invitedUsers, { + nullable: true, + }) + @JoinColumn({ name: 'project_uuid' }) + public project: ProjectEntity; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/project/entities/project.entity.ts b/libs/common/src/modules/project/entities/project.entity.ts index 01fba17..ee6a2c5 100644 --- a/libs/common/src/modules/project/entities/project.entity.ts +++ b/libs/common/src/modules/project/entities/project.entity.ts @@ -4,6 +4,7 @@ import { ProjectDto } from '../dtos'; import { CommunityEntity } from '../../community/entities'; import { SpaceModelEntity } from '../../space-model'; import { UserEntity } from '../../user/entities'; +import { InviteUserEntity } from '../../Invite-user/entities'; @Entity({ name: 'project' }) @Unique(['name']) @@ -32,6 +33,9 @@ export class ProjectEntity extends AbstractEntity { @OneToMany(() => UserEntity, (user) => user.project) public users: UserEntity[]; + @OneToMany(() => InviteUserEntity, (inviteUser) => inviteUser.project) + public invitedUsers: InviteUserEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); From 864884933e2568a5984f11a61b422ac4ec911ef8 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 23 Dec 2024 00:01:36 -0600 Subject: [PATCH 3/3] Add endpoint to check email and project existence --- libs/common/src/constants/controller-route.ts | 4 ++ .../controllers/invite-user.controller.ts | 17 +++++++ src/invite-user/dtos/add.invite-user.dto.ts | 8 ++++ src/invite-user/dtos/check-email.dto.ts | 16 +++++++ .../services/invite-user.service.ts | 47 +++++++++++++++++++ 5 files changed, 92 insertions(+) create mode 100644 src/invite-user/dtos/check-email.dto.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index f6cbfe1..7ef8808 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -733,6 +733,10 @@ export class ControllerRoute { public static readonly CREATE_USER_INVITATION_DESCRIPTION = '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 { diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts index 756ab68..4351345 100644 --- a/src/invite-user/controllers/invite-user.controller.ts +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -6,6 +6,8 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PermissionsGuard } from 'src/guards/permissions.guard'; 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') @Controller({ @@ -34,4 +36,19 @@ export class InviteUserController { 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 { + return await this.inviteUserService.checkEmailAndProject( + addUserInvitationDto, + ); + } } diff --git a/src/invite-user/dtos/add.invite-user.dto.ts b/src/invite-user/dtos/add.invite-user.dto.ts index e47758d..94f2e8a 100644 --- a/src/invite-user/dtos/add.invite-user.dto.ts +++ b/src/invite-user/dtos/add.invite-user.dto.ts @@ -61,6 +61,14 @@ export class AddUserInvitationDto { @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'], diff --git a/src/invite-user/dtos/check-email.dto.ts b/src/invite-user/dtos/check-email.dto.ts new file mode 100644 index 0000000..79bcd70 --- /dev/null +++ b/src/invite-user/dtos/check-email.dto.ts @@ -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) { + Object.assign(this, dto); + } +} diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index b63b3da..2180d0b 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -12,11 +12,14 @@ import { InviteUserRepository, InviteUserSpaceRepository, } from '@app/common/modules/Invite-user/repositiories'; +import { CheckEmailDto } from '../dtos/check-email.dto'; +import { UserRepository } from '@app/common/modules/user/repositories'; @Injectable() export class InviteUserService { constructor( private readonly inviteUserRepository: InviteUserRepository, + private readonly userRepository: UserRepository, private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, private readonly dataSource: DataSource, ) {} @@ -33,6 +36,7 @@ export class InviteUserService { phoneNumber, roleUuid, spaceUuids, + projectUuid, } = dto; const invitationCode = generateRandomString(6); @@ -67,6 +71,7 @@ export class InviteUserService { status: UserStatusEnum.INVITED, invitationCode, invitedBy: roleType, + project: { uuid: projectUuid }, }); const invitedUser = await queryRunner.manager.save(inviteUser); @@ -105,4 +110,46 @@ export class InviteUserService { await queryRunner.release(); } } + async checkEmailAndProject(dto: CheckEmailDto): Promise { + 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, + ); + } + } }