Skip to content

Commit

Permalink
Merge pull request #2295 from Real-Dev-Squad/develop
Browse files Browse the repository at this point in the history
Dev To Main Sync
  • Loading branch information
iamitprakash authored Dec 16, 2024
2 parents 32ebdc3 + c31d7db commit 3fd1c4e
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 22 deletions.
33 changes: 33 additions & 0 deletions controllers/stocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,42 @@ const fetchStocks = async (req, res) => {
* @param req {Object} - Express request object
* @param res {Object} - Express response object
*/
/**
* @deprecated
* WARNING: This API endpoint is being deprecated and will be removed in future.
* Please use the updated API endpoint: `/stocks/:userId` for retrieving user stocks details.
*
* This API is kept temporarily for backward compatibility.
*/
const getSelfStocks = async (req, res) => {
try {
const { id: userId } = req.userData;
const userStocks = await stocks.fetchUserStocks(userId);

res.set(
"X-Deprecation-Warning",
"WARNING: This endpoint is being deprecated and will be removed in the future. Please use `/stocks/:userId` route to get the user stocks details."
);
return res.json({
message: userStocks.length > 0 ? "User stocks returned successfully!" : "No stocks found",
userStocks,
});
} catch (err) {
logger.error(`Error while getting user stocks ${err}`);
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
}
};

/**
* Fetches all the stocks of the authenticated user
*
* @param req {Object} - Express request object
* @param res {Object} - Express response object
*/
const getUserStocks = async (req, res) => {
try {
const userStocks = await stocks.fetchUserStocks(req.params.userId);

return res.json({
message: userStocks.length > 0 ? "User stocks returned successfully!" : "No stocks found",
userStocks,
Expand All @@ -62,4 +94,5 @@ module.exports = {
addNewStock,
fetchStocks,
getSelfStocks,
getUserStocks,
};
23 changes: 13 additions & 10 deletions controllers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ const discordDeveloperRoleId = config.get("discordDeveloperRoleId");

const verifyUser = async (req, res) => {
const userId = req.userData.id;
const devFeatureFlag = req.query.dev === "true";
try {
if (!req.userData?.profileURL) {
return res.boom.serverUnavailable("ProfileURL is Missing");
}
await userQuery.addOrUpdate({ profileStatus: "PENDING" }, userId);
await userQuery.addOrUpdate({ profileStatus: "PENDING" }, userId, devFeatureFlag);
} catch (error) {
logger.error(`Error while verifying user: ${error}`);
return res.boom.serverUnavailable(SOMETHING_WENT_WRONG);
Expand Down Expand Up @@ -500,7 +501,7 @@ const updateSelf = async (req, res) => {
const { roles } = discordMember;
if (roles && roles.includes(discordDeveloperRoleId)) {
if (req.body.disabledRoles && devFeatureFlag) {
const updatedUser = await userQuery.addOrUpdate({ disabled_roles: rolesToDisable }, userId);
const updatedUser = await userQuery.addOrUpdate({ disabled_roles: rolesToDisable }, userId, devFeatureFlag);
if (updatedUser) {
return res
.status(200)
Expand All @@ -514,7 +515,7 @@ const updateSelf = async (req, res) => {
}
}

const updatedUser = await userQuery.addOrUpdate(req.body, userId);
const updatedUser = await userQuery.addOrUpdate(req.body, userId, devFeatureFlag);

if (!updatedUser.isNewUser) {
// Success criteria, user finished the sign-up process.
Expand Down Expand Up @@ -711,9 +712,9 @@ const getUserImageForVerification = async (req, res) => {
const updateUser = async (req, res) => {
try {
const { id: profileDiffId, message } = req.body;
const devFeatureFlag = req.query.dev;
const devFeatureFlag = req.query.dev === "true";
let profileDiffData;
if (devFeatureFlag === "true") {
if (devFeatureFlag) {
profileDiffData = await profileDiffsQuery.fetchProfileDiffUnobfuscated(profileDiffId);
} else {
profileDiffData = await profileDiffsQuery.fetchProfileDiff(profileDiffId);
Expand All @@ -728,7 +729,7 @@ const updateUser = async (req, res) => {

await profileDiffsQuery.updateProfileDiff({ approval: profileDiffStatus.APPROVED }, profileDiffId);

await userQuery.addOrUpdate(profileDiff, userId);
await userQuery.addOrUpdate(profileDiff, userId, devFeatureFlag);

const meta = {
approvedBy: req.userData.id,
Expand All @@ -749,9 +750,9 @@ const updateUser = async (req, res) => {
const generateChaincode = async (req, res) => {
try {
const { id } = req.userData;

const devFeatureFlag = req.query.dev === "true";
const chaincode = await chaincodeQuery.storeChaincode(id);
await userQuery.addOrUpdate({ chaincode }, id);
await userQuery.addOrUpdate({ chaincode }, id, devFeatureFlag);
return res.json({
chaincode,
message: "Chaincode returned successfully",
Expand All @@ -766,7 +767,8 @@ const profileURL = async (req, res) => {
try {
const userId = req.userData.id;
const { profileURL } = req.body;
await userQuery.addOrUpdate({ profileURL }, userId);
const devFeatureFlag = req.query.dev === "true";
await userQuery.addOrUpdate({ profileURL }, userId, devFeatureFlag);
return res.json({
message: "updated profile URL!!",
});
Expand Down Expand Up @@ -958,6 +960,7 @@ const setInDiscordScript = async (req, res) => {
const updateRoles = async (req, res) => {
try {
const result = await dataAccess.retrieveUsers({ id: req.params.id });
const devFeatureFlag = req.query.dev === "true";
if (result?.userExists) {
const dataToUpdate = req.body;
const roles = req?.userData?.roles;
Expand All @@ -966,7 +969,7 @@ const updateRoles = async (req, res) => {

const response = await getRoleToUpdate(result.user, dataToUpdate);
if (response.updateRole) {
await userQuery.addOrUpdate(response.newUserRoles, result.user.id);
await userQuery.addOrUpdate(response.newUserRoles, result.user.id, devFeatureFlag);
if (dataToUpdate?.archived) {
const body = {
reason: reason || "",
Expand Down
9 changes: 9 additions & 0 deletions middlewares/userAuthorization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NextFunction } from "express";
import { CustomRequest, CustomResponse } from "../types/global";

export const userAuthorization = (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
if (req.params.userId === req.userData.id) {
return next();
}
res.boom.forbidden("Unauthorized access");
};
2 changes: 1 addition & 1 deletion models/stocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const userStocksModel = firestore.collection("user-stocks");
* Adds Stocks
*
* @param stockData { Object }: stock data object to be stored in DB
* @return {Promise<{stockId: string}>}
* @return {Promise<{id: string, stockData: Object}>}
*/
const addStock = async (stockData) => {
try {
Expand Down
28 changes: 19 additions & 9 deletions models/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const archiveUsers = async (usersData) => {
* @param userId { String }: User Id String to be used to update the user
* @return {Promise<{isNewUser: boolean, userId: string}|{isNewUser: boolean, userId: string}>}
*/
const addOrUpdate = async (userData, userId = null) => {
const addOrUpdate = async (userData, userId = null, devFeatureFlag) => {
try {
// userId exists Update user
if (userId !== null) {
Expand All @@ -96,14 +96,24 @@ const addOrUpdate = async (userData, userId = null) => {
if ("id" in userData) {
delete userData.id;
}
await userModel.doc(userId).set(
{
...user.data(),
...userData,
updated_at: Date.now(),
},
{ merge: true }
);
if (devFeatureFlag) {
await userModel.doc(userId).set(
{
...userData,
updated_at: Date.now(),
},
{ merge: true }
);
} else {
await userModel.doc(userId).set(
{
...user.data(),
...userData,
updated_at: Date.now(),
},
{ merge: true }
);
}

const logData = {
type: logType.USER_DETAILS_UPDATED,
Expand Down
7 changes: 5 additions & 2 deletions routes/stocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ const express = require("express");
const router = express.Router();
const authenticate = require("../middlewares/authenticate");
const authorizeRoles = require("../middlewares/authorizeRoles");
const { addNewStock, fetchStocks, getSelfStocks } = require("../controllers/stocks");
const { addNewStock, fetchStocks, getSelfStocks, getUserStocks } = require("../controllers/stocks");
const { createStock } = require("../middlewares/validators/stocks");
const { SUPERUSER } = require("../constants/roles");
const { devFlagMiddleware } = require("../middlewares/devFlag");
const { userAuthorization } = require("../middlewares/userAuthorization");

router.get("/", fetchStocks);
router.post("/", authenticate, authorizeRoles([SUPERUSER]), createStock, addNewStock);
router.get("/user/self", authenticate, getSelfStocks);
router.get("/user/self", authenticate, getSelfStocks); // this route will soon be deprecated, please use `/stocks/:userId` route.
router.get("/:userId", devFlagMiddleware, authenticate, userAuthorization, getUserStocks);

module.exports = router;
97 changes: 97 additions & 0 deletions test/integration/stocks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import chai from "chai";
import chaiHttp from "chai-http";
import app from "../../server";
import authService from "../../services/authService";
import addUser from "../utils/addUser";
import cleanDb from "../utils/cleanDb";
import stocks from "../../models/stocks";
import sinon from "sinon";
import config from "config";

const cookieName: string = config.get("userToken.cookieName");
chai.use(chaiHttp);
const { expect } = chai;

describe("GET /stocks/:userId", function () {
let jwt: string;
let userId: string;
let userStock;
const stockData = { name: "EURO", quantity: 2, price: 10 };

beforeEach(async function () {
userId = await addUser();
jwt = authService.generateAuthToken({ userId });
const { id } = await stocks.addStock(stockData);
userStock = { stockId: id, stockName: "EURO", quantity: 1, orderValue: 10, initialStockValue: 2 };
});

afterEach(async function () {
await cleanDb();
sinon.restore();
});

it("Should return user stocks when stocks are available", async function () {
await stocks.updateUserStocks(userId, userStock);

const res = await chai.request(app).get(`/stocks/${userId}?dev=true`).set("cookie", `${cookieName}=${jwt}`);

expect(res).to.have.status(200);
expect(res.body).to.be.an("object");
expect(res.body.message).to.equal("User stocks returned successfully!");
expect(res.body.userStocks).to.be.an("array");
expect(res.body.userStocks.map(({ id, ...rest }) => rest)).to.deep.equal([{ ...userStock, userId }]);
});

it("Should return empty object when no stocks are found", function (done) {
chai
.request(app)
.get(`/stocks/${userId}?dev=true`)
.set("cookie", `${cookieName}=${jwt}`)
.end((err, res) => {
if (err) return done(err);

expect(res).to.have.status(200);
expect(res.body).to.be.an("object");
expect(res.body.message).to.equal("No stocks found");
expect(res.body.userStocks).to.be.an("array");

return done();
});
});

it("Should return 403 for unauthorized access", function (done) {
const userId = "anotherUser123";

chai
.request(app)
.get(`/stocks/${userId}?dev=true`)
.set("cookie", `${cookieName}=${jwt}`)
.end((err, res) => {
if (err) return done(err);

expect(res).to.have.status(403);
expect(res.body).to.be.an("object");
expect(res.body.message).to.equal("Unauthorized access");

return done();
});
});

it("Should return 500 when an internal server error occurs", function (done) {
sinon.stub(stocks, "fetchUserStocks").throws(new Error("Database error"));

chai
.request(app)
.get(`/stocks/${userId}?dev=true`)
.set("cookie", `${cookieName}=${jwt}`)
.end((err, res) => {
if (err) return done(err);

expect(res).to.have.status(500);
expect(res.body).to.be.an("object");
expect(res.body.message).to.equal("An internal server error occurred");

return done();
});
});
});
70 changes: 70 additions & 0 deletions test/unit/middlewares/userAuthorization.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as sinon from "sinon";
import chai from "chai";
const { expect } = chai;
const { userAuthorization } = require("../../../middlewares/userAuthorization");

describe("userAuthorization Middleware", function () {
let req;
let res;
let next;

beforeEach(function () {
req = {
params: {},
userData: {},
};
res = {
boom: {
forbidden: sinon.spy((message) => {
res.status = 403;
res.message = message;
}),
},
};
next = sinon.spy();
});

it("should call next() if userId matches userData.id", function () {
req.params.userId = "123";
req.userData.id = "123";

userAuthorization(req, res, next);

expect(next.calledOnce).to.be.true;
expect(res.boom.forbidden.notCalled).to.be.true;
});

it("should call res.boom.forbidden() if userId does not match userData.id", function () {
req.params.userId = "123";
req.userData.id = "456";

userAuthorization(req, res, next);

expect(res.boom.forbidden.calledOnce).to.be.true;
expect(res.status).to.equal(403);
expect(res.message).to.equal("Unauthorized access");
expect(next.notCalled).to.be.true;
});

it("should call res.boom.forbidden() if userData.id is missing", function () {
req.params.userId = "123";

userAuthorization(req, res, next);

expect(res.boom.forbidden.calledOnce).to.be.true;
expect(res.status).to.equal(403);
expect(res.message).to.equal("Unauthorized access");
expect(next.notCalled).to.be.true;
});

it("should call res.boom.forbidden() if userId is missing", function () {
req.userData.id = "123";

userAuthorization(req, res, next);

expect(res.boom.forbidden.calledOnce).to.be.true;
expect(res.status).to.equal(403);
expect(res.message).to.equal("Unauthorized access");
expect(next.notCalled).to.be.true;
});
});
Loading

0 comments on commit 3fd1c4e

Please sign in to comment.