diff --git a/src/presentation/auth/controller.ts b/src/presentation/auth/controller.ts index 046bd01..f33d749 100644 --- a/src/presentation/auth/controller.ts +++ b/src/presentation/auth/controller.ts @@ -24,6 +24,19 @@ export class AuthController { }); }; + resendVerificationToken = async (req: Request, res: Response) => { + const { email } = req.params; + + const verificationToken = await this.authService.resendValidationEmail( + email + ); + + res.status(HttpCodes.OK).json({ + message: 'Success!, Please check your email to verify your account', + token: verificationToken, + }); + }; + login = async (req: Request, res: Response) => { const userAgent = req.headers['user-agent'] || ''; const ip = req.ip || ''; diff --git a/src/presentation/auth/routes.ts b/src/presentation/auth/routes.ts index d74da70..ae8c866 100644 --- a/src/presentation/auth/routes.ts +++ b/src/presentation/auth/routes.ts @@ -44,6 +44,11 @@ export class AuthRoutes { authController.register ); + router.post( + '/resend-validation-email/:email', + authController.resendVerificationToken + ); + router.post( '/forgot-password', ValidationMiddleware.validate(ForgotPasswordDto), diff --git a/src/presentation/services/auth.service.ts b/src/presentation/services/auth.service.ts index 2223b33..1fd7955 100644 --- a/src/presentation/services/auth.service.ts +++ b/src/presentation/services/auth.service.ts @@ -7,7 +7,11 @@ import { UnauthorizedError, } from '../../domain'; import { JWTAdapter } from '../../config'; -import { InternalServerError, UnauthenticatedError } from '../../domain/errors'; +import { + InternalServerError, + NotFoundError, + UnauthenticatedError, +} from '../../domain/errors'; import { CryptoAdapter } from '../../config/crypto.adapter'; import { ProducerService } from './producer.service'; import { PartialUserResponse } from '../../domain/interfaces'; @@ -103,6 +107,43 @@ export class AuthService { return createdUser; } + public async resendValidationEmail(email: string) { + const user = await prisma.user.findUnique({ + where: { email }, + }); + + if (!user) throw new NotFoundError('User not found'); + + if (user.emailValidated) + throw new BadRequestError('Email already validated'); + + const verificationToken = this.jwt.generateToken( + { user: { email } }, + '15m' + ); + + if (!verificationToken) + throw new InternalServerError('JWT token error, check server logs'); + + await prisma.user.update({ + where: { email }, + data: { + verificationToken, + }, + }); + + await this.emailService.addMessageToQueue( + { + email, + verificationToken, + type: 'email', + }, + 'verify-email' + ); + + return verificationToken; + } + private async validateCredentials(loginUserDto: LoginUserDto) { const user = await prisma.user.findUnique({ where: { email: loginUserDto.email }, diff --git a/src/presentation/services/user.service.ts b/src/presentation/services/user.service.ts index eaef107..85a6b05 100644 --- a/src/presentation/services/user.service.ts +++ b/src/presentation/services/user.service.ts @@ -2,7 +2,6 @@ import { prismaWithPasswordExtension as prisma } from '../../data/postgres'; import { AnimalFilterDto, BadRequestError, - FileUploadDto, NotFoundError, PaginationDto, UpdateUserDto, @@ -10,12 +9,16 @@ import { } from '../../domain'; import { UpdateSocialMediaDto } from '../../domain/dtos/users/update-social-media.dto'; import { AnimalEntity } from '../../domain/entities/animals.entity'; -import { PayloadUser, UserRoles } from '../../domain/interfaces'; +import { AnimalResponse, PayloadUser } from '../../domain/interfaces'; import { CheckPermissions } from '../../utils'; +import { ProducerService } from './producer.service'; import { S3Service } from './s3.service'; export class UserService { - constructor(private readonly s3Service: S3Service) {} + constructor( + private readonly s3Service: S3Service, + private readonly notificationService: ProducerService + ) {} public async getAllUsers() { const users = await prisma.user.findMany({ @@ -91,29 +94,74 @@ export class UserService { return userEntity; } - public async deleteUser(user: PayloadUser) { - const userToDelete = await prisma.user.findUnique({ - where: { email: user.email }, - include: { - shelter: { - select: { - images: true, + private async notifyDeletedAnimalsToUsers(animalsCreated: AnimalResponse[]) { + const deletedAnimalsIds = animalsCreated.map((animal) => animal.id); + + const usersWithFavs = await prisma.user.findMany({ + where: { + userFav: { + some: { + id: { + in: deletedAnimalsIds, + }, }, }, }, + include: { + userFav: true, + }, }); + usersWithFavs.forEach((user) => { + user.userFav.forEach((animal) => { + if (deletedAnimalsIds.includes(animal.id)) + this.notificationService.addMessageToQueue( + { + message: `Animal with id: ${animal.id} and name: ${animal.name} was deleted`, + userId: user.id, + }, + 'animal-changed-push-notification' + ); + }); + }); + } + + public async deleteUser(user: PayloadUser) { + const [userToDelete, animalsCreated] = await prisma.$transaction([ + prisma.user.findUnique({ + where: { email: user.email }, + include: { + shelter: { + select: { + images: true, + }, + }, + }, + }), + prisma.animal.findMany({ + where: { + createdBy: user.id, + }, + include: { + cat: true, + dog: true, + }, + }), + ]); + if (!userToDelete) throw new NotFoundError('User not found'); CheckPermissions.check(user, userToDelete.id); - await prisma.user.delete({ where: { email: userToDelete.email } }); + await this.notifyDeletedAnimalsToUsers(animalsCreated); const imagesToDelete = userToDelete.shelter?.images.map((image: string) => image) || []; if (imagesToDelete.length > 0) await this.s3Service.deleteFiles(imagesToDelete); + + await prisma.user.delete({ where: { email: userToDelete.email } }); } public async changePassword( diff --git a/src/presentation/users/routes.ts b/src/presentation/users/routes.ts index cfe691d..cefdd72 100644 --- a/src/presentation/users/routes.ts +++ b/src/presentation/users/routes.ts @@ -13,7 +13,7 @@ import { UpdatePasswordDto, UpdateSocialMediaDto, } from '../../domain'; -import { UserService, S3Service } from '../services'; +import { UserService, S3Service, ProducerService } from '../services'; export class UserRoutes { static get routes() { @@ -27,7 +27,11 @@ export class UserRoutes { envs.AWS_SECRET_ACCESS_KEY, envs.AWS_BUCKET ); - const userService = new UserService(s3Service); + const notificationService = new ProducerService( + envs.RABBITMQ_URL, + 'notification-request' + ); + const userService = new UserService(s3Service, notificationService); const userController = new UserController(userService); const fileUploadMiddleware = new FileUploadMiddleware(s3Service);