Skip to content

Commit 6efdb4a

Browse files
authored
Merge pull request #22 from atlp-rwanda/ft-user-login-email-pwd
Ft user login email pwd
2 parents 37ac8a6 + 1576301 commit 6efdb4a

File tree

8 files changed

+202
-7
lines changed

8 files changed

+202
-7
lines changed

Diff for: .circleci/config.yml

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ jobs:
2727
- run:
2828
name: Build Project
2929
command: tsc
30+
- run:
31+
name: Compile TypeScript
32+
command: ./node_modules/.bin/tsc
33+
34+
3035

3136
workflows:
3237
version: 2

Diff for: .gitignore

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
/node_modules
22
.env
33
/coverage
4-
/dist
4+
/dist
5+
6+
7+
8+

Diff for: src/__test__/user.test.ts

+54-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import request from "supertest";
22
import app from "../../app";
3-
import db from '../database/config/database.config';
3+
import db from "../database/config/database.config";
44

55
describe("User", () => {
6-
let sequelizeInstance:any;
6+
let sequelizeInstance: any;
77

88
beforeAll(async () => {
9-
jest.setTimeout(60000);
9+
jest.setTimeout(60000);
1010
sequelizeInstance = await db();
1111
await sequelizeInstance.query('TRUNCATE TABLE users RESTART IDENTITY CASCADE;');
1212
});
@@ -111,6 +111,57 @@ describe("User", () => {
111111
expect(res.body.data.message).toBe('Email address is required');
112112
});
113113
});
114+
115+
describe("Test user login", () => {
116+
test("user logs in with correct credentials", async () => {
117+
const loginUser = {
118+
119+
password: "Walmond@123"
120+
};
121+
const res = await request(app).post('/api/users/login').send(loginUser);
122+
expect(res.statusCode).toBe(200);
123+
expect(res.body.message).toBe('Login successful');
124+
expect(res.body).toHaveProperty('token');
125+
});
126+
127+
test("user logs in with incorrect password", async () => {
128+
const loginUser = {
129+
130+
password: "wrongpassword"
131+
};
132+
const res = await request(app).post('/api/users/login').send(loginUser);
133+
expect(res.statusCode).toBe(401);
134+
expect(res.body.message).toBe('Invalid email or password');
135+
});
136+
137+
test("user logs in with non-existing email", async () => {
138+
const loginUser = {
139+
140+
password: "Password@123"
141+
};
142+
const res = await request(app).post('/api/users/login').send(loginUser);
143+
expect(res.statusCode).toBe(401);
144+
expect(res.body.message).toBe('Invalid email or password');
145+
});
146+
147+
test("user logs in without email", async () => {
148+
const loginUser = {
149+
password: "Password@123"
150+
};
151+
const res = await request(app).post('/api/users/login').send(loginUser);
152+
expect(res.statusCode).toBe(400);
153+
expect(res.body.data.message).toBe('Email address is required');
154+
});
155+
156+
test("user logs in without password", async () => {
157+
const loginUser = {
158+
159+
};
160+
const res = await request(app).post('/api/users/login').send(loginUser);
161+
expect(res.statusCode).toBe(400);
162+
expect(res.body.data.message).toBe('Password is required.');
163+
});
164+
});
114165
});
115166

116167
describe("Testing endpoint", () => {

Diff for: src/controllers/user.controller.ts

+46
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { hashPassword } from "../utils/password.utils";
55
import { generateToken } from "../utils/tokenGenerator.utils";
66
import { sendVerificationEmail } from "../utils/email.utils";
77

8+
9+
import { comparePassword } from "../utils/password.utils";
10+
811
export const userSignup = async (req: Request, res: Response) => {
912
const subject = "Email Verification";
1013
const text = `Please verify your email by clicking on the following link:`;
@@ -41,3 +44,46 @@ export const userSignup = async (req: Request, res: Response) => {
4144
console.log(error, "Error in creating account");
4245
}
4346
};
47+
48+
49+
//User Login Controller
50+
const userLogin = async (req: Request, res: Response) => {
51+
try {
52+
const { email, password } = req.body;
53+
54+
const user = await UserService.getUserByEmail(email);
55+
if (!user) {
56+
return res.status(401).json({
57+
status: "fail",
58+
message: "Invalid email or password",
59+
});
60+
}
61+
62+
const isPasswordValid = await comparePassword(password, user.password);
63+
if (!isPasswordValid) {
64+
return res.status(401).json({
65+
status: "fail",
66+
message: "Invalid email or password",
67+
});
68+
}
69+
70+
const token = await generateToken(user);
71+
72+
return res.status(200).json({
73+
status: "success",
74+
message: "Login successful",
75+
token: token,
76+
data: {
77+
user,
78+
},
79+
});
80+
} catch (error) {
81+
console.error("Error during login:", error);
82+
return res.status(500).json({
83+
status: "error",
84+
message: "An error occurred during login",
85+
});
86+
}
87+
};
88+
89+
export default userLogin;

Diff for: src/routes/user.route.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import express from 'express';
22
import { userSignup } from '../controllers/user.controller';
3-
import { validateUser } from '../validations/user.validate';
3+
import userLogin from '../controllers/user.controller';
4+
import { validateUser,validateUserLogin } from '../validations/user.validate';
45

56
const userRoutes = express.Router();
67

78
userRoutes.post('/signup',validateUser,userSignup);
8-
9+
userRoutes.post('/login', validateUserLogin, userLogin);
910
export default userRoutes;

Diff for: src/utils/password.utils.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ export async function hashPassword(password:any) {
33
const salt = await bcrypt.genSalt(10)
44
const hash = await bcrypt.hash(password, salt);
55
return hash;
6-
}
6+
}
7+
8+
export const comparePassword = async (password: string, hashedPassword: string) => {
9+
return await bcrypt.compare(password, hashedPassword);
10+
};

Diff for: src/validations/user.validate.ts

+27
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,31 @@ export const validateUser = async(
7474
});
7575
}
7676
next();
77+
};
78+
79+
80+
const loginValidation = Joi.object({
81+
email: Joi.string()
82+
.email({ minDomainSegments: 2, tlds: { allow: ["com", "net"] } })
83+
.required()
84+
.messages({
85+
"string.email": "Please enter a valid email address",
86+
"any.required": "Email address is required",
87+
}),
88+
password: Joi.string().required().messages({
89+
"any.required": "Password is required.",
90+
}),
91+
});
92+
93+
export const validateUserLogin = (req: Request, res: Response, next: NextFunction) => {
94+
const { error } = loginValidation.validate(req.body, { abortEarly: false });
95+
if (error) {
96+
return res.status(400).json({
97+
status: "fail",
98+
data: {
99+
message: error.details.map(detail => detail.message).join(", "),
100+
},
101+
});
102+
}
103+
next();
77104
};

Diff for: swagger.config.ts

+57
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,63 @@ const options = {
118118
}
119119
}
120120
}
121+
},
122+
123+
//User Login Route Documentation
124+
'/api/users/login': {
125+
post: {
126+
summary: 'Login with Email and Password',
127+
tags: ['Authentication'],
128+
security: [],
129+
requestBody: {
130+
content: {
131+
'application/json': {
132+
schema: {
133+
type: 'object',
134+
properties: {
135+
136+
email: {
137+
type: 'string',
138+
example: '[email protected]'
139+
},
140+
password: {
141+
type: 'string',
142+
example: 'Test@123'
143+
}
144+
},
145+
required: ['email', 'password']
146+
}
147+
}
148+
}
149+
},
150+
responses: {
151+
201: {
152+
description: 'OK',
153+
content: {
154+
'application/json': {
155+
schema: {
156+
type: 'object',
157+
properties: {
158+
159+
email: { type: 'string' },
160+
password: { type: 'string' },
161+
162+
},
163+
required: [
164+
165+
'email',
166+
'password',
167+
168+
]
169+
}
170+
}
171+
}
172+
},
173+
400: {
174+
description: 'Bad Request'
175+
}
176+
}
177+
}
121178
}
122179
}
123180
},

0 commit comments

Comments
 (0)