From b8020784e18fc4e634794bc8af755e873fb05d03 Mon Sep 17 00:00:00 2001 From: maxCastro1 Date: Sat, 4 May 2024 13:38:53 +0200 Subject: [PATCH] rebasing from develop , adding verifie route and service and send email to the user --- package.json | 2 + src/__test__/route.test.ts | 49 +++++++++- src/controllers/authController.ts | 54 ++--------- src/controllers/index.ts | 4 +- src/routes/UserRoutes.ts | 7 +- src/services/index.ts | 2 + .../userServices/userRegistrationService.ts | 75 ++++++++++++++++ .../userServices/userValidationService.ts | 28 ++++++ src/utils/sendMail.ts | 90 +++++++++++++++++++ 9 files changed, 257 insertions(+), 54 deletions(-) create mode 100644 src/services/userServices/userRegistrationService.ts create mode 100644 src/services/userServices/userValidationService.ts create mode 100644 src/utils/sendMail.ts diff --git a/package.json b/package.json index df34cc1..f24cac8 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "highlight.js": "^11.9.0", "jsend": "^1.1.0", "morgan": "^1.10.0", + "nodemailer": "^6.9.13", "nodemon": "^3.1.0", "pg": "^8.11.5", "reflect-metadata": "^0.2.2", @@ -58,6 +59,7 @@ "@types/jsend": "^1.0.32", "@types/morgan": "^1.9.9", "@types/node": "^20.12.7", + "@types/nodemailer": "^6.4.15", "@types/reflect-metadata": "^0.1.0", "@types/supertest": "^6.0.2", "@types/winston": "^2.4.4", diff --git a/src/__test__/route.test.ts b/src/__test__/route.test.ts index 6505d02..21c0305 100644 --- a/src/__test__/route.test.ts +++ b/src/__test__/route.test.ts @@ -1,6 +1,6 @@ import request from 'supertest'; import { app, server } from '../index'; -import { createConnection, getConnection, getConnectionOptions } from 'typeorm'; +import { createConnection, getConnection, getConnectionOptions, getRepository } from 'typeorm'; import { User } from '../entities/User'; beforeAll(async () => { @@ -67,5 +67,52 @@ describe('POST /user/register', () => { message: 'User registered successfully', }, }); + + // Clean up: delete the test user + const userRepository = getRepository(User); + const user = await userRepository.findOne({ where: { email: newUser.email } }); + if (user) { + await userRepository.remove(user); + } }); }); +describe('POST /user/verify/:id', () => { + it('should verify a user', async () => { + // Arrange + const newUser = { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe1@example.com', + password: 'password', + gender: 'Male', + phoneNumber: '123456789', + userType: 'Buyer', + photoUrl: 'https://example.com/photo.jpg', + }; + + // Create a new user + const res = await request(app).post('/user/register').send(newUser); + + const userRepository = getRepository(User); + const user = await userRepository.findOne({ where: { email: newUser.email } }); + + if(user){ + const verifyRes = await request(app).get(`/user/verify/${user.id}`); + + // Assert + expect(verifyRes.status).toBe(200); + expect(verifyRes.text).toEqual('

User verified successfully

'); + + // Check that the user's verified field is now true + const verifiedUser = await userRepository.findOne({ where: { email: newUser.email } }); + if (verifiedUser){ + expect(verifiedUser.verified).toBe(true); + } + + } + + if (user) { + await userRepository.remove(user); + } + }); +}); \ No newline at end of file diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index 6879bc0..627db42 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -4,52 +4,12 @@ import bcrypt from 'bcrypt'; import { getRepository } from 'typeorm'; import { responseError, responseServerError, responseSuccess } from '../utils/response.utils'; import { validate } from 'class-validator'; +import { userVerificationService, userRegistrationService } from '../services'; -class UserController { - static registerUser = async (req: Request, res: Response) => { - const { firstName, lastName, email, password, gender, phoneNumber, userType, photoUrl } = req.body; - - // Validate user input - if (!firstName || !lastName || !email || !password || !gender || !phoneNumber || !photoUrl) { - return responseError(res, 400, 'Please fill all the required fields'); - } - - const userRepository = getRepository(User); - - try { - // Check for existing user - const existingUser = await userRepository.findOneBy({ email }); - const existingUserNumber = await userRepository.findOneBy({ phoneNumber }); - - if (existingUser || existingUserNumber) { - return responseError(res, 409, 'Email or phone number already in use'); - } - - const saltRounds = 10; - const hashedPassword = await bcrypt.hash(password, saltRounds); - - // Create user - const user = new User(); - user.firstName = firstName; - user.lastName = lastName; - user.email = email; - user.password = hashedPassword; - user.userType = userType; - user.gender = gender; - user.phoneNumber = phoneNumber; - user.photoUrl = photoUrl; - - // Save user - await userRepository.save(user); - - return responseSuccess(res, 201, 'User registered successfully'); - } catch (error) { - if (error instanceof Error) { - return responseServerError(res, error.message); - } - - return responseServerError(res, 'Unknown error occurred'); - } - }; +export const userRegistration = async (req: Request, res: Response) => { + await userRegistrationService(req, res); } -export { UserController }; +export const userVerification = async (req: Request, res: Response) => { + await userVerificationService(req, res); +} + diff --git a/src/controllers/index.ts b/src/controllers/index.ts index b581bf7..a03efc6 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -1,3 +1,3 @@ -import { UserController } from './authController'; +import { userRegistration,userVerification } from './authController'; -export { UserController }; +export { userRegistration,userVerification }; diff --git a/src/routes/UserRoutes.ts b/src/routes/UserRoutes.ts index 01ce115..7fcd3b5 100644 --- a/src/routes/UserRoutes.ts +++ b/src/routes/UserRoutes.ts @@ -1,10 +1,9 @@ import { Router } from 'express'; -import { UserController } from '../controllers/index'; - -const { registerUser } = UserController; +import { userRegistration, userVerification} from '../controllers/index'; const router = Router(); -router.post('/register', registerUser); +router.post('/register', userRegistration); +router.get('/verify/:id', userVerification); export default router; diff --git a/src/services/index.ts b/src/services/index.ts index 73cc501..271665e 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1 +1,3 @@ // export all Services +export * from './userServices/userRegistrationService'; +export * from './userServices/userValidationService'; \ No newline at end of file diff --git a/src/services/userServices/userRegistrationService.ts b/src/services/userServices/userRegistrationService.ts new file mode 100644 index 0000000..f99c4b7 --- /dev/null +++ b/src/services/userServices/userRegistrationService.ts @@ -0,0 +1,75 @@ + +import { Request, Response } from 'express'; +import { User } from '../../entities/User'; +import bcrypt from 'bcrypt'; +import { getRepository } from 'typeorm'; +import { responseError, responseServerError, responseSuccess } from '../../utils/response.utils'; +import sendMail from '../../utils/sendMail'; +import dotenv from 'dotenv'; +dotenv.config(); + +export const userRegistrationService = async (req: Request, res: Response) => { + const { firstName, lastName, email, password, gender, phoneNumber, userType, photoUrl } = req.body; + + // Validate user input + if (!firstName || !lastName || !email || !password || !gender || !phoneNumber || !photoUrl) { + return responseError(res, 400, 'Please fill all the required fields'); + } + + const userRepository = getRepository(User); + + try { + // Check for existing user + const existingUser = await userRepository.findOneBy({ email }); + const existingUserNumber = await userRepository.findOneBy({ phoneNumber }); + + if (existingUser || existingUserNumber) { + return responseError(res, 409, 'Email or phone number already in use'); + } + + const saltRounds = 10; + const hashedPassword = await bcrypt.hash(password, saltRounds); + + // Create user + const user = new User(); + user.firstName = firstName; + user.lastName = lastName; + user.email = email; + user.password = hashedPassword; + user.userType = userType; + user.gender = gender; + user.phoneNumber = phoneNumber; + user.photoUrl = photoUrl; + + // Save user + await userRepository.save(user); + console.log(user) + if (process.env.AUTH_EMAIL && process.env.AUTH_PASSWORD) { + + const message = { + to: email, + from: process.env.AUTH_EMAIL, + subject: 'Welcome to the knights app', + text: `Welcome to the app, ${firstName} ${lastName}!`, + lastName: lastName, + firstName: firstName, + } + const link = `http://localhost:${process.env.PORT}/user/verify/${user.id}` + + sendMail(process.env.AUTH_EMAIL, process.env.AUTH_PASSWORD, message, link); + + + } else { + // return res.status(500).json({ error: 'Email or password for mail server not configured' }); + return responseServerError(res, 'Email or password for mail server not configured'); + } + + return responseSuccess(res, 201, 'User registered successfully'); + } catch (error) { + if (error instanceof Error) { + return responseServerError(res, error.message); + } + + return responseServerError(res, 'Unknown error occurred'); + } +}; \ No newline at end of file diff --git a/src/services/userServices/userValidationService.ts b/src/services/userServices/userValidationService.ts new file mode 100644 index 0000000..8d8fea6 --- /dev/null +++ b/src/services/userServices/userValidationService.ts @@ -0,0 +1,28 @@ +import { Request, Response } from 'express'; +import { User } from '../../entities/User'; +import { getRepository } from 'typeorm'; + + + +export const userVerificationService = async (req: Request, res: Response) => { + const { id } = req.params; + + // Validate user input + if (!id) { + return res.status(400).json({ error: 'Missing user ID' }); + } + + const userRepository = getRepository(User); + const user = await userRepository.findOneBy({id}); + + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + user.verified = true; + + await userRepository.save(user); + + return res.status(200).send('

User verified successfully

'); + +} \ No newline at end of file diff --git a/src/utils/sendMail.ts b/src/utils/sendMail.ts new file mode 100644 index 0000000..11117ab --- /dev/null +++ b/src/utils/sendMail.ts @@ -0,0 +1,90 @@ +import nodemailer from 'nodemailer'; + +const sendMail = async (userAuth: string, + passAuth: string, + message: {from: string,to:string, subject: string, text: string, firstName: string , lastName: string}, + link: string = '') => { + const transporter = nodemailer.createTransport({ + host: process.env.HOST, + port: 587, + secure: false, // true for 465, false for other ports + auth: { + user: userAuth, + pass: passAuth + }, + }); + + const { from, to, subject, text, firstName, lastName } = message; + + const mailOptions = { + from: from, + to: to, + subject: subject, + text: text, + firstName: firstName, + lastName: lastName, + html: ` + + + + + + +
+

+ Hello ${firstName} ${lastName}, +

+
+

${text}

+

+

${link && `click here to verifie your account`}

+

This message is from: Knights Andela

+
+ +
+ + + ` + + }; + + try { + const info = await transporter.sendMail(mailOptions); + console.log('Message sent: %s', info.messageId); + } catch (error) { + console.log('Error occurred while sending email', error); + } +}; + +export default sendMail;