Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[delivers #187584915] Added user login #35

Merged
merged 2 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
PORT=
NODE_ENV=
NODE_EN=
DOCKER_DATABASE_HOST_AUTH_METHOD=
DOCKER_DATABASE_NAME=
DOCKER_DATABASE_PASSWORD=
JWT_SECRET=
NODE_ENV=

SMTP_HOST_PORT=
MP=
SMTP_HOST=
MAIL_ID=

CLOUD_NAME=
API_KEY=
API_SECRET=

SERVER_URL_DEV=
SERVER_URL_PRO=

DATABASE_URL_DEV=
DATABASE_URL_TEST=
DATABASE_URL_PRO=
DB_HOST_TYPE=



DB_HOST_TYPE=
DOCKER_DATABASE_USER=
DOCKER_DATABASE_HOST_AUTH_METHOD=
DOCKER_DATABASE_NAME=
DOCKER_DATABASE_PASSWORD=
57 changes: 10 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Team Ninjas Backend
# TEAM NINJAS BACKEND

This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.

Expand All @@ -23,26 +23,28 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
[https://github.com/atlp-rwanda/e-commerce-ninjas-bn](https://github.com/atlp-rwanda/e-commerce-ninjas-bn)


## Completed Features
## COMPLETED FEATURES

- Welcome Endpoint
- Register Endpoint
- Verification Email Endpoint
- Resend verification Endpoint
- Login Endpoint

## TABLE OF API ENDPOINTS SPECIFICATION AND DESCRIPTION


| No | VERBS | ENDPOINTS | STATUS | ACCESS | DESCRIPTION |
|----|-------|------------------------------|-------------|--------|---------------------------|
| 1 | GET | / | 200 OK | public | Show welcome message |
| 2 | GET | /api/auth/verify-email/:token| 200 OK | public | Verifying email |
| 3 | POST | /api/auth/register | 201 CREATED | public | create user account |
| 2 | POST | /api/auth/register | 201 CREATED | public | create user account |
| 3 | GET | /api/auth/verify-email/:token| 200 OK | public | Verifying email |
| 4 | POST | /api/auth/send-verify-email | 200 OK | public | Resend verification email |
| 4 | POST | /api/auth/login | 200 OK | public | Login with Email and Password |



## Installation
## INSTALLATION

1. Clone the repository:

Expand All @@ -63,7 +65,7 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
npm run dev
```

## Folder Structure
## FOLDER STRUCTURE

- `.env`: Secure environment variables.
- `src/`: Source code directory.
Expand All @@ -84,7 +86,7 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
- `services/`: Service functions like sendEmails.
- `index.ts`: Startup file for all requests.

## Initialize Sequelize CLI
## INITILIAZE SEQUELIZE CLI

1. Initialize Sequelize CLI:
```sh
Expand Down Expand Up @@ -117,43 +119,4 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
9. Delete the Migration:
```sh
npm run deleteAllTables
```



## Initialize Sequelize CLI

1. Initialize Sequelize CLI:
```sh
npx sequelize-cli init
```
2. Generate Seeder:
```sh
npx sequelize-cli seed:generate --name name-of-your-seeder
```
3. Generate Migrations:
```sh
npx sequelize-cli migration:generate --name name-of-your-migration
```
4. Define Migration:
Edit the generated migration file to include the tables you want to create.
5. Define Seeder Data:
Edit the generated seeder file to include the data you want to insert.
6. Run the Seeder:
```sh
npm run createAllSeeders
```
7. Run the Migration:
```sh
npm run createAllTables
```
8. Delete the Seeder:
```sh
npm run deleteAllSeeders
```
9. Delete the Migration:
```sh
npm run deleteAllTables
```


```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"start": "ts-node-dev src/index.ts",
"dev": "ts-node-dev src/index.ts",
"test": "cross-env NODE_ENV=test npm run deleteAllTables && cross-env NODE_ENV=test npm run createAllTables && cross-env NODE_ENV=test npm run createAllSeeders && nyc cross-env NODE_ENV=test mocha --require ts-node/register 'src/**/*.spec.ts' --timeout 600000 --exit",
"test-dev": "cross-env NODE_ENV=development npm run deleteAllTables && cross-env NODE_ENV=development npm run createAllTables && cross-env NODE_ENV=development npm run createAllSeeders && nyc cross-env NODE_ENV=development mocha --require ts-node/register 'src/**/*.spec.ts' --timeout 600000 --exit",
"coveralls": "nyc --reporter=lcov --reporter=text-lcov npm test | coveralls",
"coverage": "cross-env NODE_ENV=test nyc mocha --require ts-node/register 'src/**/*.spec.ts' --timeout 600000 --exit",
"lint": "eslint . --ext .ts",
Expand Down
7 changes: 6 additions & 1 deletion src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import jwt,{JwtPayload} from "jsonwebtoken"
import dotenv from "dotenv"
import bcrypt from "bcrypt"

dotenv.config

Expand All @@ -12,4 +13,8 @@ dotenv.config
;
};

export { generateToken, decodeToken}
const comparePassword = async (password: string, hashedPassword: string) =>{
return await bcrypt.compare(password, hashedPassword);
}

export { generateToken, decodeToken, comparePassword }
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,4 @@ app.listen(PORT, () => {
console.log(`Server is running on the port ${PORT}`);
});


export default app;
51 changes: 40 additions & 11 deletions src/middlewares/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import authRepositories from "../modules/auth/repository/authRepositories";
import { UsersAttributes } from "../databases/models/users";
import Joi from "joi";
import httpStatus from "http-status";
import { decodeToken } from "../helpers";
import { comparePassword, decodeToken } from "../helpers";
import { IRequest } from "../types";

const validation = (schema: Joi.ObjectSchema | Joi.ArraySchema) => async (req: Request, res: Response, next: NextFunction) => {
try {
Expand All @@ -20,22 +21,33 @@ const validation = (schema: Joi.ObjectSchema | Joi.ArraySchema) => async (req: R
}
};


const isUserExist = async (req: Request, res: Response, next: NextFunction) => {
try {
const userExists: UsersAttributes = await authRepositories.findUserByAttributes("email", req.body.email);
if (userExists && userExists.isVerified === true) {
return res.status(httpStatus.BAD_REQUEST).json({ status: httpStatus.BAD_REQUEST, message: "Account already exists." });
let userExists: UsersAttributes | null = null;

if (req.body.email) {
userExists = await authRepositories.findUserByAttributes("email", req.body.email);
if (userExists) {
if (userExists.isVerified) {
return res.status(httpStatus.BAD_REQUEST).json({ status: httpStatus.BAD_REQUEST, message: "Account already exists." });
}
return res.status(httpStatus.BAD_REQUEST).json({ status: httpStatus.BAD_REQUEST, message: "Account already exists. Please verify your account" });
}
}
if (userExists && userExists.isVerified === false) {
return res.status(httpStatus.BAD_REQUEST).json({ status: httpStatus.BAD_REQUEST, message: "Account already exists. Please verify your account" });

if (req.params.id) {
userExists = await authRepositories.findUserByAttributes("id", req.params.id);
if (userExists) {
return next();
}
return res.status(httpStatus.BAD_REQUEST).json({ status: httpStatus.BAD_REQUEST, message: "User not found" });
}

return next();
} catch (error) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message })
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message });
}

}
};

const isAccountVerified = async (req: any, res: Response, next: NextFunction) => {
try {
Expand Down Expand Up @@ -69,5 +81,22 @@ const isAccountVerified = async (req: any, res: Response, next: NextFunction) =>
}
}

const verifyUserCredentials = async (req: Request, res: Response, next: NextFunction) => {
try {
const user: UsersAttributes = await authRepositories.findUserByAttributes("email", req.body.email);
if (!user) {
return res.status(httpStatus.BAD_REQUEST).json({ message: "Invalid Email or Password", data: null });
}
const passwordMatches = await comparePassword(req.body.password, user.password)
if (!passwordMatches) return res.status(httpStatus.BAD_REQUEST).json({ message: "Invalid Email or Password", data: null });
(req as IRequest).loginUserId = user.id;
return next();
} catch (error) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ message: "Server error", data: error.message })
}

}



export { validation, isUserExist, isAccountVerified };
export { validation, isUserExist, isAccountVerified, verifyUserCredentials };
25 changes: 21 additions & 4 deletions src/modules/auth/controller/authControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@ import userRepositories from "../repository/authRepositories";
import { generateToken } from "../../../helpers";
import httpStatus from "http-status";
import { UsersAttributes } from "../../../databases/models/users";
import { IRequest } from "../../../types";

import authRepositories from "../repository/authRepositories";
import { sendVerificationEmail } from "../../../services/sendEmail";

const registerUser = async (req: Request, res: Response): Promise<void> => {
try {
const register: UsersAttributes = await userRepositories.createUser(req.body);
const token: string = generateToken(register.id);
const session = {userId: register.id, device: req.headers["user-device"] , token: token, otp: null };
const session = { userId: register.id, device: req.headers["user-device"], token: token, otp: null };
await authRepositories.createSession(session);
await sendVerificationEmail(register.email, "Verification Email", `${process.env.SERVER_URL_PRO}/api/auth/verify-email/${token}`);
res.status(httpStatus.CREATED).json({ message: "Account created successfully. Please check email to verify account.", data: register })
res.status(httpStatus.CREATED).json({ message: "Account created successfully. Please check email to verify account.", data: { user: register } })
} catch (error) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message });
}

}

const sendVerifyEmail = async(req:any, res:Response) =>{
const sendVerifyEmail = async (req: any, res: Response) => {
try {
await sendVerificationEmail(req.user.email, "Verification Email", `${process.env.SERVER_URL_PRO}/api/auth/verify-email/${req.session.token}`);
res.status(httpStatus.OK).json({ status: httpStatus.OK, message: "Verification email sent successfully." });
Expand All @@ -40,4 +42,19 @@ const verifyEmail = async (req: any, res: Response) => {
}
}

export default { registerUser, sendVerifyEmail, verifyEmail }

const loginUser = async (req: Request, res: Response) => {
try {
const userId = (req as IRequest).loginUserId;
const token = generateToken(userId);
const session = { userId, device: req.headers["user-device"], token: token, otp: null };
await userRepositories.createSession(session);
res.status(httpStatus.OK).json({ message: "Logged in successfully", data: { token } });
}
catch (err) {
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ message: "Server error", data: err.message });
}
}


export default { registerUser, sendVerifyEmail, verifyEmail, loginUser }
3 changes: 2 additions & 1 deletion src/modules/auth/repository/authRepositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const createUser = async (body:any) =>{
return await Users.create(body)
}


const findUserByAttributes = async (key:string, value:any) =>{
return await Users.findOne({ where: { [key]: value} })
}
Expand All @@ -26,4 +27,4 @@ const destroySession = async (userId: number, token:string) =>{
return await Session.destroy({ where: {userId, token } });
}

export default {createUser, createSession, findUserByAttributes, destroySession, UpdateUserByAttributes, findSessionByUserId}
export default { createUser, createSession, findUserByAttributes, destroySession, UpdateUserByAttributes, findSessionByUserId }
Loading
Loading