Skip to content

Commit 3fd1c4e

Browse files
authored
Merge pull request #2295 from Real-Dev-Squad/develop
Dev To Main Sync
2 parents 32ebdc3 + c31d7db commit 3fd1c4e

File tree

9 files changed

+272
-22
lines changed

9 files changed

+272
-22
lines changed

controllers/stocks.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,42 @@ const fetchStocks = async (req, res) => {
4444
* @param req {Object} - Express request object
4545
* @param res {Object} - Express response object
4646
*/
47+
/**
48+
* @deprecated
49+
* WARNING: This API endpoint is being deprecated and will be removed in future.
50+
* Please use the updated API endpoint: `/stocks/:userId` for retrieving user stocks details.
51+
*
52+
* This API is kept temporarily for backward compatibility.
53+
*/
4754
const getSelfStocks = async (req, res) => {
4855
try {
4956
const { id: userId } = req.userData;
5057
const userStocks = await stocks.fetchUserStocks(userId);
58+
59+
res.set(
60+
"X-Deprecation-Warning",
61+
"WARNING: This endpoint is being deprecated and will be removed in the future. Please use `/stocks/:userId` route to get the user stocks details."
62+
);
63+
return res.json({
64+
message: userStocks.length > 0 ? "User stocks returned successfully!" : "No stocks found",
65+
userStocks,
66+
});
67+
} catch (err) {
68+
logger.error(`Error while getting user stocks ${err}`);
69+
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
70+
}
71+
};
72+
73+
/**
74+
* Fetches all the stocks of the authenticated user
75+
*
76+
* @param req {Object} - Express request object
77+
* @param res {Object} - Express response object
78+
*/
79+
const getUserStocks = async (req, res) => {
80+
try {
81+
const userStocks = await stocks.fetchUserStocks(req.params.userId);
82+
5183
return res.json({
5284
message: userStocks.length > 0 ? "User stocks returned successfully!" : "No stocks found",
5385
userStocks,
@@ -62,4 +94,5 @@ module.exports = {
6294
addNewStock,
6395
fetchStocks,
6496
getSelfStocks,
97+
getUserStocks,
6598
};

controllers/users.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ const discordDeveloperRoleId = config.get("discordDeveloperRoleId");
3939

4040
const verifyUser = async (req, res) => {
4141
const userId = req.userData.id;
42+
const devFeatureFlag = req.query.dev === "true";
4243
try {
4344
if (!req.userData?.profileURL) {
4445
return res.boom.serverUnavailable("ProfileURL is Missing");
4546
}
46-
await userQuery.addOrUpdate({ profileStatus: "PENDING" }, userId);
47+
await userQuery.addOrUpdate({ profileStatus: "PENDING" }, userId, devFeatureFlag);
4748
} catch (error) {
4849
logger.error(`Error while verifying user: ${error}`);
4950
return res.boom.serverUnavailable(SOMETHING_WENT_WRONG);
@@ -500,7 +501,7 @@ const updateSelf = async (req, res) => {
500501
const { roles } = discordMember;
501502
if (roles && roles.includes(discordDeveloperRoleId)) {
502503
if (req.body.disabledRoles && devFeatureFlag) {
503-
const updatedUser = await userQuery.addOrUpdate({ disabled_roles: rolesToDisable }, userId);
504+
const updatedUser = await userQuery.addOrUpdate({ disabled_roles: rolesToDisable }, userId, devFeatureFlag);
504505
if (updatedUser) {
505506
return res
506507
.status(200)
@@ -514,7 +515,7 @@ const updateSelf = async (req, res) => {
514515
}
515516
}
516517

517-
const updatedUser = await userQuery.addOrUpdate(req.body, userId);
518+
const updatedUser = await userQuery.addOrUpdate(req.body, userId, devFeatureFlag);
518519

519520
if (!updatedUser.isNewUser) {
520521
// Success criteria, user finished the sign-up process.
@@ -711,9 +712,9 @@ const getUserImageForVerification = async (req, res) => {
711712
const updateUser = async (req, res) => {
712713
try {
713714
const { id: profileDiffId, message } = req.body;
714-
const devFeatureFlag = req.query.dev;
715+
const devFeatureFlag = req.query.dev === "true";
715716
let profileDiffData;
716-
if (devFeatureFlag === "true") {
717+
if (devFeatureFlag) {
717718
profileDiffData = await profileDiffsQuery.fetchProfileDiffUnobfuscated(profileDiffId);
718719
} else {
719720
profileDiffData = await profileDiffsQuery.fetchProfileDiff(profileDiffId);
@@ -728,7 +729,7 @@ const updateUser = async (req, res) => {
728729

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

731-
await userQuery.addOrUpdate(profileDiff, userId);
732+
await userQuery.addOrUpdate(profileDiff, userId, devFeatureFlag);
732733

733734
const meta = {
734735
approvedBy: req.userData.id,
@@ -749,9 +750,9 @@ const updateUser = async (req, res) => {
749750
const generateChaincode = async (req, res) => {
750751
try {
751752
const { id } = req.userData;
752-
753+
const devFeatureFlag = req.query.dev === "true";
753754
const chaincode = await chaincodeQuery.storeChaincode(id);
754-
await userQuery.addOrUpdate({ chaincode }, id);
755+
await userQuery.addOrUpdate({ chaincode }, id, devFeatureFlag);
755756
return res.json({
756757
chaincode,
757758
message: "Chaincode returned successfully",
@@ -766,7 +767,8 @@ const profileURL = async (req, res) => {
766767
try {
767768
const userId = req.userData.id;
768769
const { profileURL } = req.body;
769-
await userQuery.addOrUpdate({ profileURL }, userId);
770+
const devFeatureFlag = req.query.dev === "true";
771+
await userQuery.addOrUpdate({ profileURL }, userId, devFeatureFlag);
770772
return res.json({
771773
message: "updated profile URL!!",
772774
});
@@ -958,6 +960,7 @@ const setInDiscordScript = async (req, res) => {
958960
const updateRoles = async (req, res) => {
959961
try {
960962
const result = await dataAccess.retrieveUsers({ id: req.params.id });
963+
const devFeatureFlag = req.query.dev === "true";
961964
if (result?.userExists) {
962965
const dataToUpdate = req.body;
963966
const roles = req?.userData?.roles;
@@ -966,7 +969,7 @@ const updateRoles = async (req, res) => {
966969

967970
const response = await getRoleToUpdate(result.user, dataToUpdate);
968971
if (response.updateRole) {
969-
await userQuery.addOrUpdate(response.newUserRoles, result.user.id);
972+
await userQuery.addOrUpdate(response.newUserRoles, result.user.id, devFeatureFlag);
970973
if (dataToUpdate?.archived) {
971974
const body = {
972975
reason: reason || "",

middlewares/userAuthorization.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { NextFunction } from "express";
2+
import { CustomRequest, CustomResponse } from "../types/global";
3+
4+
export const userAuthorization = (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
5+
if (req.params.userId === req.userData.id) {
6+
return next();
7+
}
8+
res.boom.forbidden("Unauthorized access");
9+
};

models/stocks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const userStocksModel = firestore.collection("user-stocks");
66
* Adds Stocks
77
*
88
* @param stockData { Object }: stock data object to be stored in DB
9-
* @return {Promise<{stockId: string}>}
9+
* @return {Promise<{id: string, stockData: Object}>}
1010
*/
1111
const addStock = async (stockData) => {
1212
try {

models/users.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const archiveUsers = async (usersData) => {
8484
* @param userId { String }: User Id String to be used to update the user
8585
* @return {Promise<{isNewUser: boolean, userId: string}|{isNewUser: boolean, userId: string}>}
8686
*/
87-
const addOrUpdate = async (userData, userId = null) => {
87+
const addOrUpdate = async (userData, userId = null, devFeatureFlag) => {
8888
try {
8989
// userId exists Update user
9090
if (userId !== null) {
@@ -96,14 +96,24 @@ const addOrUpdate = async (userData, userId = null) => {
9696
if ("id" in userData) {
9797
delete userData.id;
9898
}
99-
await userModel.doc(userId).set(
100-
{
101-
...user.data(),
102-
...userData,
103-
updated_at: Date.now(),
104-
},
105-
{ merge: true }
106-
);
99+
if (devFeatureFlag) {
100+
await userModel.doc(userId).set(
101+
{
102+
...userData,
103+
updated_at: Date.now(),
104+
},
105+
{ merge: true }
106+
);
107+
} else {
108+
await userModel.doc(userId).set(
109+
{
110+
...user.data(),
111+
...userData,
112+
updated_at: Date.now(),
113+
},
114+
{ merge: true }
115+
);
116+
}
107117

108118
const logData = {
109119
type: logType.USER_DETAILS_UPDATED,

routes/stocks.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ const express = require("express");
22
const router = express.Router();
33
const authenticate = require("../middlewares/authenticate");
44
const authorizeRoles = require("../middlewares/authorizeRoles");
5-
const { addNewStock, fetchStocks, getSelfStocks } = require("../controllers/stocks");
5+
const { addNewStock, fetchStocks, getSelfStocks, getUserStocks } = require("../controllers/stocks");
66
const { createStock } = require("../middlewares/validators/stocks");
77
const { SUPERUSER } = require("../constants/roles");
8+
const { devFlagMiddleware } = require("../middlewares/devFlag");
9+
const { userAuthorization } = require("../middlewares/userAuthorization");
810

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

1316
module.exports = router;

test/integration/stocks.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import chai from "chai";
2+
import chaiHttp from "chai-http";
3+
import app from "../../server";
4+
import authService from "../../services/authService";
5+
import addUser from "../utils/addUser";
6+
import cleanDb from "../utils/cleanDb";
7+
import stocks from "../../models/stocks";
8+
import sinon from "sinon";
9+
import config from "config";
10+
11+
const cookieName: string = config.get("userToken.cookieName");
12+
chai.use(chaiHttp);
13+
const { expect } = chai;
14+
15+
describe("GET /stocks/:userId", function () {
16+
let jwt: string;
17+
let userId: string;
18+
let userStock;
19+
const stockData = { name: "EURO", quantity: 2, price: 10 };
20+
21+
beforeEach(async function () {
22+
userId = await addUser();
23+
jwt = authService.generateAuthToken({ userId });
24+
const { id } = await stocks.addStock(stockData);
25+
userStock = { stockId: id, stockName: "EURO", quantity: 1, orderValue: 10, initialStockValue: 2 };
26+
});
27+
28+
afterEach(async function () {
29+
await cleanDb();
30+
sinon.restore();
31+
});
32+
33+
it("Should return user stocks when stocks are available", async function () {
34+
await stocks.updateUserStocks(userId, userStock);
35+
36+
const res = await chai.request(app).get(`/stocks/${userId}?dev=true`).set("cookie", `${cookieName}=${jwt}`);
37+
38+
expect(res).to.have.status(200);
39+
expect(res.body).to.be.an("object");
40+
expect(res.body.message).to.equal("User stocks returned successfully!");
41+
expect(res.body.userStocks).to.be.an("array");
42+
expect(res.body.userStocks.map(({ id, ...rest }) => rest)).to.deep.equal([{ ...userStock, userId }]);
43+
});
44+
45+
it("Should return empty object when no stocks are found", function (done) {
46+
chai
47+
.request(app)
48+
.get(`/stocks/${userId}?dev=true`)
49+
.set("cookie", `${cookieName}=${jwt}`)
50+
.end((err, res) => {
51+
if (err) return done(err);
52+
53+
expect(res).to.have.status(200);
54+
expect(res.body).to.be.an("object");
55+
expect(res.body.message).to.equal("No stocks found");
56+
expect(res.body.userStocks).to.be.an("array");
57+
58+
return done();
59+
});
60+
});
61+
62+
it("Should return 403 for unauthorized access", function (done) {
63+
const userId = "anotherUser123";
64+
65+
chai
66+
.request(app)
67+
.get(`/stocks/${userId}?dev=true`)
68+
.set("cookie", `${cookieName}=${jwt}`)
69+
.end((err, res) => {
70+
if (err) return done(err);
71+
72+
expect(res).to.have.status(403);
73+
expect(res.body).to.be.an("object");
74+
expect(res.body.message).to.equal("Unauthorized access");
75+
76+
return done();
77+
});
78+
});
79+
80+
it("Should return 500 when an internal server error occurs", function (done) {
81+
sinon.stub(stocks, "fetchUserStocks").throws(new Error("Database error"));
82+
83+
chai
84+
.request(app)
85+
.get(`/stocks/${userId}?dev=true`)
86+
.set("cookie", `${cookieName}=${jwt}`)
87+
.end((err, res) => {
88+
if (err) return done(err);
89+
90+
expect(res).to.have.status(500);
91+
expect(res.body).to.be.an("object");
92+
expect(res.body.message).to.equal("An internal server error occurred");
93+
94+
return done();
95+
});
96+
});
97+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import * as sinon from "sinon";
2+
import chai from "chai";
3+
const { expect } = chai;
4+
const { userAuthorization } = require("../../../middlewares/userAuthorization");
5+
6+
describe("userAuthorization Middleware", function () {
7+
let req;
8+
let res;
9+
let next;
10+
11+
beforeEach(function () {
12+
req = {
13+
params: {},
14+
userData: {},
15+
};
16+
res = {
17+
boom: {
18+
forbidden: sinon.spy((message) => {
19+
res.status = 403;
20+
res.message = message;
21+
}),
22+
},
23+
};
24+
next = sinon.spy();
25+
});
26+
27+
it("should call next() if userId matches userData.id", function () {
28+
req.params.userId = "123";
29+
req.userData.id = "123";
30+
31+
userAuthorization(req, res, next);
32+
33+
expect(next.calledOnce).to.be.true;
34+
expect(res.boom.forbidden.notCalled).to.be.true;
35+
});
36+
37+
it("should call res.boom.forbidden() if userId does not match userData.id", function () {
38+
req.params.userId = "123";
39+
req.userData.id = "456";
40+
41+
userAuthorization(req, res, next);
42+
43+
expect(res.boom.forbidden.calledOnce).to.be.true;
44+
expect(res.status).to.equal(403);
45+
expect(res.message).to.equal("Unauthorized access");
46+
expect(next.notCalled).to.be.true;
47+
});
48+
49+
it("should call res.boom.forbidden() if userData.id is missing", function () {
50+
req.params.userId = "123";
51+
52+
userAuthorization(req, res, next);
53+
54+
expect(res.boom.forbidden.calledOnce).to.be.true;
55+
expect(res.status).to.equal(403);
56+
expect(res.message).to.equal("Unauthorized access");
57+
expect(next.notCalled).to.be.true;
58+
});
59+
60+
it("should call res.boom.forbidden() if userId is missing", function () {
61+
req.userData.id = "123";
62+
63+
userAuthorization(req, res, next);
64+
65+
expect(res.boom.forbidden.calledOnce).to.be.true;
66+
expect(res.status).to.equal(403);
67+
expect(res.message).to.equal("Unauthorized access");
68+
expect(next.notCalled).to.be.true;
69+
});
70+
});

0 commit comments

Comments
 (0)