Skip to content

Commit

Permalink
Logout feature (#26) (#46)
Browse files Browse the repository at this point in the history
* Logout feature (#26)

* [#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

* fixing bugs

* rebased

---------

Co-authored-by: Solange Duhimbaze Ihirwe <[email protected]>
  • Loading branch information
MANISHIMWESalton and solangeihirwe03 authored Jun 4, 2024
1 parent c0fbf6f commit 3986393
Show file tree
Hide file tree
Showing 15 changed files with 505 additions and 143 deletions.
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ Our e-commerce web application server, developed by Team Ninjas, facilitates smo

## HOSTED SERVER URL

[https://e-commerce-ninjas-backend.onrender.com/](https://e-commerce-ninjas-backend.onrender.com/)
[https://e-commerce-ninjas-platform-backend.onrender.com/](https://e-commerce-ninjas-platform-backend.onrender.com/)

#### Hosted Swagger Documentation

[https://e-commerce-ninjas-backend.onrender.com/api-docs](https://e-commerce-ninjas-backend.onrender.com/api-docs)
[https://e-commerce-ninjas-platform-backend.onrender.com/api-docs](https://e-commerce-ninjas-platform-backend.onrender.com/api-docs)

#### Github Repository For E-Commerce-Ninjas Backend

Expand All @@ -28,9 +28,14 @@ Our e-commerce web application server, developed by Team Ninjas, facilitates smo
- Verification Email Endpoint
- Resend verification Endpoint
- Login Endpoint
- Admin get users Endpoint
- Admin get user Endpoint
- Admin Update Status Endpoint
- Admin Update Role Endpoint
- Logout Endpoint
- Update User Profile Endpoint
- Get User Profile Endpoint
- Login Via google account

## TABLE OF API ENDPOINTS SPECIFICATION AND DESCRIPTION

Expand All @@ -42,11 +47,14 @@ Our e-commerce web application server, developed by Team Ninjas, facilitates smo
| 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 |
| 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 | GET | /api/auth/google | 200 OK | public | Login Via google account |

| 6 | GET | /api/user/admin-get-users | 200 OK | private | Admin get all users Endpoint |
| 7 | GET | /api/user/admin-get-user/:id | 200 OK | private | Admin get user Endpoint |
| 8 | PUT | /api/user/admin-update-user-status/:id | 200 OK | private | Admin Update Status Endpoint |
| 9 | PUT | /api/user/admin-update-role/:id | 200 OK | private | Admin Update Role Endpoint |
| 10 | POST | /api/auth/logout | 200 OK | private | Logout user |
| 11 | PUT | /api/user/user-update-profile | 200 OK | private | Update User Profile Endpoint |
| 11 | GET | /api/user/user-get-profile | 200 OK | private | Get User Profile Endpoint |
| 12 | GET | /api/auth/google | 200 OK | public | Login Via google account |
## INSTALLATION

1. Clone the repository:
Expand Down Expand Up @@ -122,4 +130,4 @@ Our e-commerce web application server, developed by Team Ninjas, facilitates smo
9. Delete the Migration:
```sh
npm run deleteAllTables
```
```
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"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-local": "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",
"coverage": "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",
"lint": "eslint . --ext .ts",
"lint-staged": "lint-staged",
"prepare": "husky",
Expand All @@ -35,7 +35,8 @@
"exclude": [
"src/index.spec.ts",
"src/databases/**/*.*",
"src/modules/**/test/*.spec.ts"
"src/modules/**/test/*.spec.ts",
"src/services/googleAuth.ts"
],
"reporter": [
"html",
Expand Down
70 changes: 0 additions & 70 deletions src/databases/models/tokens.ts

This file was deleted.

11 changes: 3 additions & 8 deletions src/databases/models/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import { Model, DataTypes, Optional } from "sequelize";
import sequelizeConnection from "../config/db.config";
import bcrypt from "bcrypt";

// Define an interface with required and optional attributes
export interface UsersAttributes {
id: number;
firstName?: string;
Expand All @@ -25,8 +23,6 @@ export interface UsersAttributes {
createdAt?: Date;
updatedAt?: Date;
}

// Define an interface for creation attributes which excludes the optional fields
export interface UsersCreationAttributes
extends Optional<UsersAttributes, "id"> {}

Expand All @@ -53,10 +49,9 @@ class Users
declare createdAt?: Date;
declare updatedAt?: Date;

// Define any static methods or associations here
static associate(models: any) {
Users.hasOne(models.Tokens, { foreignKey: "userId", as: "token" });
}
static associate(models: any) {
Users.hasOne(models.Sessions, { foreignKey: "userId",as: "session" });
}
}

Users.init(
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;
24 changes: 21 additions & 3 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import authRepositories from "./modules/auth/repository/authRepositories";

chai.use(chaiHttp);
chai.use(sinonChai);
//

const router = () => chai.request(app);

describe("Initial configuration", () => {
Expand Down Expand Up @@ -113,19 +113,37 @@ describe("userAuthorization middleware", () => {
});
});

it("should respond with 401 if user status is not enabled", async () => {
req.headers.authorization = "Bearer validToken";
sinon.stub(helpers, "decodeToken").resolves({ id: "userId" });
sinon.stub(authRepositories, "findSessionByUserIdAndToken").resolves({});
sinon
.stub(authRepositories, "findUserByAttributes")
.resolves({ role: "admin", status: "disabled" });

const middleware = userAuthorization(roles);
await middleware(req, res, next);

expect(res.status).to.have.been.calledWith(httpStatus.UNAUTHORIZED);
expect(res.json).to.have.been.calledWith({
status: httpStatus.UNAUTHORIZED,
message: "Not authorized",
});
});

it("should call next if user is authorized", async () => {
req.headers.authorization = "Bearer validToken";
sinon.stub(helpers, "decodeToken").resolves({ id: "userId" });
sinon.stub(authRepositories, "findSessionByUserIdAndToken").resolves({});
sinon
.stub(authRepositories, "findUserByAttributes")
.resolves({ role: "admin" });
.resolves({ role: "admin", status: "enabled" });

const middleware = userAuthorization(roles);
await middleware(req, res, next);

expect(next).to.have.been.calledOnce;
expect(req.user).to.deep.equal({ role: "admin" });
expect(req.user).to.deep.equal({ role: "admin", status: "enabled" });
expect(req.session).to.deep.equal({});
});

Expand Down
6 changes: 6 additions & 0 deletions src/middlewares/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export const userAuthorization = function (roles: string[]) {
.json({ status: httpStatus.UNAUTHORIZED, message: "Not authorized" });
}

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

req.user = user;
req.session = session;
next();
Expand Down
23 changes: 21 additions & 2 deletions src/modules/user/controller/userControllers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Request, Response } from "express";
import httpStatus from "http-status";
import uploadImages from "../../../helpers/uploadImage";
import userRepositories from "../repository/userRepositories";
import authRepositories from "../../auth/repository/authRepositories";

Expand All @@ -20,7 +21,7 @@ const adminGetUsers = async (req:Request, res:Response) =>{

const adminGetUser = async (req:Request, res:Response) =>{
try {
const data = await userRepositories.getUserById(Number(req.params.id))
const data = await authRepositories.findUserByAttributes("id", req.params.id);
return res.status(httpStatus.OK).json({
message: "Successfully",
data
Expand Down Expand Up @@ -58,7 +59,25 @@ const updateUserStatus = async (req: Request, res: Response): Promise<void> => {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message });
}
};
const getUserDetails = async(req:Request,res:Response)=>{
try {
const user = await authRepositories.findUserByAttributes("id", req.user.id);
res.status(httpStatus.OK).json({status: httpStatus.OK,data:{user:user}});
} catch (error) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message })
}
}

const updateUserProfile = async (req: Request, res: Response) => {
try {
const upload = await uploadImages(req.file);
const userData = { ...req.body, profilePicture:upload.secure_url };
const user = await userRepositories.updateUserProfile(userData, Number(req.user.id));
res.status(httpStatus.OK).json({status:httpStatus.OK, data:{user:user}});
} catch (error) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({status:httpStatus.INTERNAL_SERVER_ERROR, error: error.message});
}
}


export default { updateUserStatus, updateUserRole, adminGetUsers , adminGetUser };
export default { updateUserStatus, updateUserRole, adminGetUsers , adminGetUser,updateUserProfile ,getUserDetails};
10 changes: 6 additions & 4 deletions src/modules/user/repository/userRepositories.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import Users from "../../../databases/models/users";


const getAllUsers = async () => {
return Users.findAll();
}

const getUserById = async (id: number) => {
return Users.findByPk(id);
const updateUserProfile = async (user: any, id:number) => {
await Users.update({...user},{where:{id},returning:true})
const updateUser = await Users.findOne({where:{id}})
return updateUser;
}

export default { getAllUsers, getUserById}
export default { getAllUsers,updateUserProfile}
Binary file added src/modules/user/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.
Loading

0 comments on commit 3986393

Please sign in to comment.