Skip to content

Commit

Permalink
Logout feature (#26)
Browse files Browse the repository at this point in the history
* [#187584914]added logout feature

* [starts #187584914] added logout feature

* [finishes#187584914] logout feature

* [delivers##187584914] updated readme & swagger.json

* [delivers##187584914] updated readme & swagger.json

* [deliveres #187584914] logout features completed

* [deliveres #187584914] logout features completed

* [delivers #187584914] finished logout feature
  • Loading branch information
solangeihirwe03 authored and MANISHIMWESalton committed May 31, 2024
1 parent b0606a1 commit 7d18d51
Show file tree
Hide file tree
Showing 20 changed files with 830 additions and 212 deletions.
61 changes: 31 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# TEAM NINJAS BACKEND
# E-COMMERCE WEB APPLICATION SERVER - TEAM NINJAS.

This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
Our e-commerce web application server, developed by Team Ninjas, facilitates smooth online shopping with features like user authentication, product cataloging, and secure payments. It's built to enhance the user experience with high performance and reliability. Suitable for any online marketplace looking to grow.

[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
[![Maintainability](https://api.codeclimate.com/v1/badges/839fc3fa18d25362cd8b/maintainability)](https://codeclimate.com/github/atlp-rwanda/e-commerce-ninjas-bn/maintainability)
Expand All @@ -9,7 +9,6 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/atlp-rwanda/e-commerce-ninjas-bn/tree/develop.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/atlp-rwanda/e-commerce-ninjas-bn/tree/develop)
[![codecov](https://codecov.io/gh/atlp-rwanda/e-commerce-ninjas-bn/graph/badge.svg?token=6ZWudFPM1S)](https://codecov.io/gh/atlp-rwanda/e-commerce-ninjas-bn)


## HOSTED SERVER URL

[https://e-commerce-ninjas-backend.onrender.com/](https://e-commerce-ninjas-backend.onrender.com/)
Expand All @@ -22,7 +21,6 @@ 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

- Welcome Endpoint
Expand All @@ -32,6 +30,8 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
- Login Endpoint
- Admin Update Status Endpoint
- Admin Update Role Endpoint
- Logout Endpoint
- Update User Profile Endpoint

## TABLE OF API ENDPOINTS SPECIFICATION AND DESCRIPTION

Expand All @@ -43,9 +43,10 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
| 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 |
| 5 | POST | /api/auth/login | 200 OK | public | Login with Email and Password |
| 5 | PUT | /api/users/admin-update-role/:id | 200 OK | private | Update the user role by admin|
| 6 | PUT | /api/users/admin-update-user-status/:id | 200 OK | private | Admin Update Status Endpoint |
| 7 | PUT | /api/users/admin-update-role/:id | 200 OK | private | Admin Update Role Endpoint |
| 6 | PUT | /api/users/admin-update-user-status/:id | 200 OK | private | Admin Update Status Endpoint |
| 7 | PUT | /api/users/admin-update-role/:id | 200 OK | private | Admin Update Role Endpoint |
| 8 | POST | /api/auth/logout | 200 OK | private | Logout user |
| 9 | PUT | /api/users/user-update-profile/:id | 200 OK | private | Update User Profile Endpoint |

## INSTALLATION

Expand Down Expand Up @@ -92,34 +93,34 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
## INITILIAZE SEQUELIZE CLI

1. Initialize Sequelize CLI:
```sh
npx sequelize-cli init
```
```sh
npx sequelize-cli init
```
2. Generate Seeder:
```sh
npx sequelize-cli seed:generate --name name-of-your-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
```
```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.
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.
Edit the generated seeder file to include the data you want to insert.
6. Run the Seeder:
```sh
npm run createAllSeeders
```
```sh
npm run createAllSeeders
```
7. Run the Migration:
```sh
npm run createAllTables
```
```sh
npm run createAllTables
```
8. Delete the Seeder:
```sh
npm run deleteAllSeeders
```
```sh
npm run deleteAllSeeders
```
9. Delete the Migration:
```sh
npm run deleteAllTables
```
```sh
npm run deleteAllTables
```
11 changes: 11 additions & 0 deletions src/__test__/BUILD.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Believe: Know that the problem you have identified has a solution, and trust that you and your team can build something to address it.

Understand: Take the time to learn about the experience of the people affected by this problem / who will be using your solution – what does their journey look like? What are their pain points? What factors are affecting how they operate in / engage with the world?

Invent: Create a Minimum Viable Product or prototype to test out an idea that you have! Be sure to keep in mind how your “invention” will meet your users’ needs.

Listen: Get feedback from your users on your MVP / prototype and modify it as needed.

Deliver: Continue to deliver refined versions of your MVP / prototype and improve it over time!

Password@123
Binary file added src/__test__/testImage.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 70 additions & 0 deletions src/databases/models/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* eslint-disable require-jsdoc */
import { Model, DataTypes, Sequelize } from "sequelize";

interface TokenAttributes {
id: number;
userId: number;
device: string;
accessToken: string;
createdAt: Date;
updatedAt: Date;
expiresAt: Date;
}

module.exports = (sequelize: Sequelize) => {
class Tokens extends Model<TokenAttributes> implements TokenAttributes {
declare id: number;
declare userId: number;
declare device: string;
declare accessToken: string;
declare createdAt: Date;
declare updatedAt: Date;
declare expiresAt: Date;
}

Tokens.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
userId: {
type: new DataTypes.INTEGER(),
allowNull: false
},
device: {
type: new DataTypes.STRING(280),
allowNull: false
},
accessToken: {
type: new DataTypes.STRING(280),
allowNull: false
},
createdAt: {
field: "createdAt",
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updatedAt: {
field: "updatedAt",
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
expiresAt: {
type: DataTypes.DATE,
allowNull: false
}
},
{
sequelize,
tableName: "tokens",
timestamps: true,
modelName: "Tokens"
}
);

return Tokens;
};
2 changes: 1 addition & 1 deletion src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import bcrypt from "bcrypt"
dotenv.config

const generateToken = (id: number) => {
return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: "1h" });
return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: "12h" });
};

const decodeToken = (token: string) => {
Expand Down
26 changes: 13 additions & 13 deletions src/helpers/multer.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// import { Request } from "express";
// import multer from "multer";
// import path from "path";
import { Request } from "express";
import multer from "multer";
import path from "path";

// export default multer({
// storage:multer.diskStorage({}),
// fileFilter:(req:Request,file:Express.Multer.File,cb)=>{
// const ext = path.extname(file.originalname);
// if(ext!== ".png" && ext!== ".jpg" && ext!== ".jpeg"){
// return cb(new Error("Only images are allowed"));
// }
// cb(null,true);
// }
// })
export default multer({
storage:multer.diskStorage({}),
fileFilter:(req:Request,file:Express.Multer.File,cb)=>{
const ext = path.extname(file.originalname);
if(ext!== ".png" && ext!== ".jpg" && ext!== ".jpeg"){
return cb(new Error("Only images are allowed"));
}
cb(null,true);
}
})
30 changes: 15 additions & 15 deletions src/helpers/uploadImage.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// /* eslint-disable @typescript-eslint/no-explicit-any */
// import { v2 as cloudinary } from "cloudinary";
/* eslint-disable @typescript-eslint/no-explicit-any */
import { v2 as cloudinary } from "cloudinary";

// cloudinary.config({
// cloud_name: process.env.CLOUD_NAME,
// api_key: process.env.API_KEY,
// api_secret: process.env.API_SECRET
// });
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET
});

// export const uploadImages = async (fileToUpload: any): Promise<{ public_id: string; secure_url: string }> => {
// const result = await cloudinary.uploader.upload(fileToUpload.path);
// return {
// public_id: result.public_id,
// secure_url: result.secure_url
// };
// };
export const uploadImages = async (fileToUpload: any): Promise<{ public_id: string; secure_url: string }> => {
const result = await cloudinary.uploader.upload(fileToUpload.path);
return {
public_id: result.public_id,
secure_url: result.secure_url
};
};

// export default uploadImages;
export default uploadImages;
8 changes: 4 additions & 4 deletions src/middlewares/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const userAuthorization = function (roles: string[]) {
}

if (!token) {
res
return res
.status(httpStatus.UNAUTHORIZED)
.json({ status: httpStatus.UNAUTHORIZED, message: "Not authorized" });
}
Expand All @@ -32,21 +32,21 @@ export const userAuthorization = function (roles: string[]) {
decoded.id, token
);
if (!session) {
res
return res
.status(httpStatus.UNAUTHORIZED)
.json({ status: httpStatus.UNAUTHORIZED, message: "Not authorized" });
}

const user = await authRepository.findUserByAttributes("id", decoded.id);

if (!user) {
res
return res
.status(httpStatus.UNAUTHORIZED)
.json({ status: httpStatus.UNAUTHORIZED, message: "Not authorized" });
}

if (!roles.includes(user.role)) {
res
return res
.status(httpStatus.UNAUTHORIZED)
.json({ status: httpStatus.UNAUTHORIZED, message: "Not authorized" });
}
Expand Down
67 changes: 52 additions & 15 deletions src/middlewares/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { UsersAttributes } from "../databases/models/users";
import Joi from "joi";
import httpStatus from "http-status";
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 Down Expand Up @@ -68,7 +67,7 @@ const isAccountVerified = async (req: any, res: Response, next: NextFunction) =>
return res.status(httpStatus.BAD_REQUEST).json({ message: "Account already verified." });
}

const session = await authRepositories.findSessionByUserId(user.id);
const session = await authRepositories.findSessionByAttributes("userId",user.id);
if (!session) {
return res.status(httpStatus.BAD_REQUEST).json({ message: "Invalid token." });
}
Expand All @@ -81,21 +80,59 @@ 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 })
const verifyUserCredentials = async (
req: any,
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" });
}

}
const passwordMatches = await comparePassword(
req.body.password,
user.password
);
if (!passwordMatches) {
return res
.status(httpStatus.BAD_REQUEST)
.json({ message: "Invalid Email or Password" });
}

req.user = user;

const device = req.headers["user-agent"];
if (!device) {
return next();
}

const existingToken = await authRepositories.findTokenByDeviceIdAndUserId(
device,
user.id
);
if (existingToken) {
return res
.status(httpStatus.OK)
.json({
message: "Logged in successfully",
data: { token: existingToken }
});
} else {
return next();
}
} catch (error) {
return res
.status(httpStatus.INTERNAL_SERVER_ERROR)
.json({ message: "Internal Server error", data: error.message });
}
};



Expand Down
Loading

0 comments on commit 7d18d51

Please sign in to comment.