Skip to content

Commit

Permalink
rebasing from develop , adding verifie route and service and send ema…
Browse files Browse the repository at this point in the history
…il to the user
  • Loading branch information
maxCastro1 committed May 4, 2024
1 parent c265908 commit 6d5f80b
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 55 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# E-commerse Backend API

[![knights-ecomm-be CI](https://github.com/atlp-rwanda/knights-ecomm-be/actions/workflows/ci.yml/badge.svg)](https://github.com/atlp-rwanda/knights-ecomm-be/actions/workflows/ci.yml)
[![Coverage Status](https://coveralls.io/repos/github/atlp-rwanda/knights-ecomm-be/badge.svg?branch=develop)](https://coveralls.io/github/atlp-rwanda/knights-ecomm-be?branch=develop)
  
[![Coverage Status](https://coveralls.io/repos/github/atlp-rwanda/knights-ecomm-be/badge.svg?branch=ch-ci-setup)](https://coveralls.io/github/atlp-rwanda/knights-ecomm-be?branch=ch-ci-setup)
  
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
49 changes: 48 additions & 1 deletion src/__test__/route.test.ts
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand Down Expand Up @@ -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: '[email protected]',
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('<p>User verified successfully</p>');

// 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);
}
});
});
54 changes: 7 additions & 47 deletions src/controllers/authController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

4 changes: 2 additions & 2 deletions src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { UserController } from './authController';
import { userRegistration,userVerification } from './authController';

export { UserController };
export { userRegistration,userVerification };
7 changes: 3 additions & 4 deletions src/routes/UserRoutes.ts
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
// export all Services
export * from './userServices/userRegistrationService';
export * from './userServices/userValidationService';
75 changes: 75 additions & 0 deletions src/services/userServices/userRegistrationService.ts
Original file line number Diff line number Diff line change
@@ -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');
}
};
28 changes: 28 additions & 0 deletions src/services/userServices/userValidationService.ts
Original file line number Diff line number Diff line change
@@ -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('<p>User verified successfully</p>');

}
90 changes: 90 additions & 0 deletions src/utils/sendMail.ts
Original file line number Diff line number Diff line change
@@ -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: `
<!DOCTYPE html>
<html lang="en">
<head>
<style>
/* Reset styles */
body, html {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
/* Container styles */
.container {
max-width: 600px;
// margin: 0 auto;
padding: 20px;
background-color: #f9f9f9;
border-radius: 10px;
}
/* Content styles */
.content {
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
/* Footer styles */
.footer {
margin-top: 20px;
text-align: center;
color: #666;
}
</style>
</head>
<body>
<div class="container">
<h3 class="header">
Hello ${firstName} ${lastName},
</h3>
<div class="content">
<p>${text}</p>
<p> </p>
<p>${link && `<a href=${link}>click here to verifie your account</a>`}</p>
<p>This message is from: Knights Andela </p>
</div>
<div class="footer">
<p>&copy; ${new Date().getFullYear()} Knights Andela. All rights reserved.</p>
</div>
</div>
</body>
</html>
`

};

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;

0 comments on commit 6d5f80b

Please sign in to comment.