Skip to content

Commit 5eb57b5

Browse files
authored
Merge pull request #254 from map-of-pi/misc-refactor-work
Approved (1).
2 parents 8d149ec + 35ac628 commit 5eb57b5

File tree

8 files changed

+222
-47
lines changed

8 files changed

+222
-47
lines changed

src/middlewares/isToggle.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { NextFunction, Request, Response } from "express";
2+
import Toggle from "../models/misc/Toggle";
3+
import logger from '../config/loggingConfig';
4+
5+
export const isToggle = (toggleName: string) => async (
6+
req: Request,
7+
res: Response,
8+
next: NextFunction
9+
) => {
10+
try {
11+
const toggle = await Toggle.findOne({ name: toggleName });
12+
13+
if (!toggle || !toggle.enabled) {
14+
return res.status(403).json({
15+
message: "Feature is currently disabled",
16+
});
17+
}
18+
19+
return next();
20+
} catch (error) {
21+
logger.error(`Failed to fetch toggle ${toggleName}:`, error);
22+
return res.status(500).json({
23+
message: `Failed to fetch toggle ${toggleName}; please try again later`
24+
});
25+
}
26+
};

src/models/SellerItem.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ const sellerItemSchema = new Schema<ISellerItem>(
4646
}
4747
);
4848

49+
sellerItemSchema.index({ seller_id: 1 });
50+
4951
// Creating the Seller model from the schema
5052
const SellerItem = mongoose.model<ISellerItem>("Seller-Item", sellerItemSchema);
5153

src/routes/seller.routes.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Router } from "express";
33
import * as sellerController from "../controllers/sellerController";
44
import { isSellerFound } from "../middlewares/isSellerFound";
55
import { verifyToken } from "../middlewares/verifyToken";
6+
import { isToggle } from "../middlewares/isToggle";
67
import upload from "../utils/multer";
78

89
/**
@@ -289,7 +290,11 @@ sellerRoutes.delete(
289290
* 500:
290291
* description: Internal server error
291292
*/
292-
sellerRoutes.get("/item/:seller_id", sellerController.getSellerItems);
293+
sellerRoutes.get(
294+
"/item/:seller_id",
295+
isToggle("onlineShoppingFeature"),
296+
sellerController.getSellerItems
297+
);
293298

294299
/**
295300
* @swagger
@@ -319,7 +324,8 @@ sellerRoutes.get("/item/:seller_id", sellerController.getSellerItems);
319324
* description: Internal server error
320325
*/
321326
sellerRoutes.put(
322-
"/item/add",
327+
"/item/add",
328+
isToggle("onlineShoppingFeature"),
323329
verifyToken,
324330
isSellerFound,
325331
upload.single("image"),
@@ -358,6 +364,7 @@ sellerRoutes.put(
358364
*/
359365
sellerRoutes.delete(
360366
"/item/delete/:item_id",
367+
isToggle("onlineShoppingFeature"),
361368
verifyToken,
362369
isSellerFound,
363370
sellerController.deleteSellerItem

src/services/seller.service.ts

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,60 @@ import { IUser, IUserSettings, ISeller, ISellerWithSettings, ISellerItem, ISanct
1313
import logger from "../config/loggingConfig";
1414

1515
// Helper function to get settings for all sellers and merge them into seller objects
16-
const resolveSellerSettings = async (sellers: ISeller[], trustLevelFilters?: number[]): Promise<ISellerWithSettings[]> => {
17-
const sellersWithSettings = await Promise.all(
18-
sellers.map(async (seller) => {
19-
try {
20-
const sellerObject = seller.toObject();
21-
22-
// Fetch the user settings for the seller
23-
const userSettings = await UserSettings.findOne({ user_settings_id: seller.seller_id }).exec();
24-
25-
// Check if the seller's trust level is allowed
26-
if (trustLevelFilters && trustLevelFilters.includes(userSettings?.trust_meter_rating ?? -1)) {
27-
return null; // Exclude this seller
28-
}
29-
30-
// Merge seller and settings into a single object
31-
return {
32-
...sellerObject,
33-
trust_meter_rating: userSettings?.trust_meter_rating,
34-
user_name: userSettings?.user_name,
35-
findme: userSettings?.findme,
36-
email: userSettings?.email ?? null,
37-
phone_number: userSettings?.phone_number ?? null,
38-
search_filters: userSettings?.search_filters ?? null,
39-
} as ISellerWithSettings;
40-
} catch (error) {
41-
logger.error(`Failed to resolve settings for sellerID ${ seller.seller_id }:`, error);
42-
43-
// Return a fallback seller object with minimal information
44-
return {
45-
...seller.toObject(),
46-
trust_meter_rating: TrustMeterScale.ZERO,
47-
user_name: seller.name,
48-
findme: null,
49-
email: null,
50-
phone_number: null
51-
} as unknown as ISellerWithSettings;
52-
}
53-
})
16+
const resolveSellerSettings = async (
17+
sellers: ISeller[],
18+
trustLevelFilters?: number[]
19+
): Promise<ISellerWithSettings[]> => {
20+
21+
if (!sellers.length) return [];
22+
23+
const sellerIds = sellers.map(seller => seller.seller_id);
24+
25+
// Batch fetch all relevant user settings in a single query
26+
const allUserSettings = await UserSettings.find({
27+
user_settings_id: { $in: sellerIds }
28+
}).exec();
29+
30+
// Create a map for quick user settings lookup
31+
const settingsMap = new Map(
32+
allUserSettings.map(setting => [setting.user_settings_id, setting])
5433
);
34+
35+
const sellersWithSettings = sellers.map((seller) => {
36+
const sellerObject = seller.toObject();
37+
const userSettings = settingsMap.get(seller.seller_id);
38+
39+
// Check if the seller's trust level is allowed
40+
const trustMeterRating = userSettings?.trust_meter_rating ?? -1;
41+
if (trustLevelFilters && !trustLevelFilters.includes(trustMeterRating)) {
42+
return null; // Exclude this seller
43+
}
44+
45+
try {
46+
return {
47+
...sellerObject,
48+
trust_meter_rating: trustMeterRating,
49+
user_name: userSettings?.user_name,
50+
findme: userSettings?.findme,
51+
email: userSettings?.email ?? null,
52+
phone_number: userSettings?.phone_number ?? null,
53+
search_filters: userSettings?.search_filters ?? null,
54+
} as ISellerWithSettings;
55+
} catch (error) {
56+
logger.error(`Failed to resolve settings for sellerID ${ seller.seller_id }:`, error);
57+
58+
// Return a fallback seller object with minimal information
59+
return {
60+
...sellerObject,
61+
trust_meter_rating: TrustMeterScale.ZERO,
62+
user_name: seller.name,
63+
findme: null,
64+
email: null,
65+
phone_number: null,
66+
} as unknown as ISellerWithSettings;
67+
}
68+
});
69+
5570
return sellersWithSettings.filter(Boolean) as ISellerWithSettings[];
5671
};
5772

@@ -70,11 +85,11 @@ export const getAllSellers = async (
7085
const baseCriteria: Record<string, any> = {};
7186
const sellerTypeFilters: SellerType[] = [];
7287

73-
if (!searchFilters.include_active_sellers) sellerTypeFilters.push(SellerType.Active);
74-
if (!searchFilters.include_inactive_sellers) sellerTypeFilters.push(SellerType.Inactive);
75-
if (!searchFilters.include_test_sellers) sellerTypeFilters.push(SellerType.Test);
76-
// exclude filtered seller types
77-
if (sellerTypeFilters.length) baseCriteria.seller_type = { $nin: sellerTypeFilters };
88+
if (searchFilters.include_active_sellers) sellerTypeFilters.push(SellerType.Active);
89+
if (searchFilters.include_inactive_sellers) sellerTypeFilters.push(SellerType.Inactive);
90+
if (searchFilters.include_test_sellers) sellerTypeFilters.push(SellerType.Test);
91+
// include filtered seller types
92+
if (sellerTypeFilters.length) baseCriteria.seller_type = { $in: sellerTypeFilters };
7893

7994
// Trust Level Filters
8095
const trustLevels = [
@@ -84,7 +99,7 @@ export const getAllSellers = async (
8499
{ key: "include_trust_level_0", value: TrustMeterScale.ZERO },
85100
];
86101
const trustLevelFilters = trustLevels
87-
.filter(({ key }) => !searchFilters[key]) // exclude unchecked trust levels
102+
.filter(({ key }) => searchFilters[key]) // Only include checked trust levels
88103
.map(({ value }) => value);
89104

90105
// Search Query Filter

test/jest.setup.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ beforeAll(async () => {
3232

3333
// Load the mock data into Map of PI DB collections
3434
await User.insertMany(mockData.users);
35+
await UserSettings.createIndexes();
3536
await UserSettings.insertMany(mockData.userSettings);
3637
// Ensure indexes are created for the schema models before running tests
3738
await Seller.createIndexes();
3839
await Seller.insertMany(mockData.sellers);
40+
await SellerItem.createIndexes();
3941
await SellerItem.insertMany(mockData.sellerItems);
4042
await ReviewFeedback.insertMany(mockData.reviews);
4143
await SanctionedRegion.insertMany(mockData.sanctionedRegion);

test/middlewares/isToggle.spec.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { NextFunction, Request, Response } from "express";
2+
import { isToggle } from "../../src/middlewares/isToggle";
3+
import Toggle from "../../src/models/misc/Toggle";
4+
5+
describe("isToggle function", () => {
6+
let req: Partial<Request>;
7+
let res: Partial<Response>;
8+
let next: NextFunction;
9+
10+
beforeEach(() => {
11+
req = {};
12+
res = {
13+
status: jest.fn().mockReturnThis(),
14+
json: jest.fn(),
15+
};
16+
next = jest.fn();
17+
});
18+
19+
it("should pass middleware if expected toggle is enabled", async () => {
20+
const middleware = isToggle("testToggle_1");
21+
await middleware(req as Request, res as Response, next);
22+
23+
expect(next).toHaveBeenCalled();
24+
expect(res.status).not.toHaveBeenCalled();
25+
});
26+
27+
it("should return 403 if expected toggle is disabled", async () => {
28+
const middleware = isToggle("testToggle");
29+
await middleware(req as Request, res as Response, next);
30+
31+
expect(res.status).toHaveBeenCalledWith(403);
32+
expect(res.json).toHaveBeenCalledWith({
33+
message: "Feature is currently disabled",
34+
});
35+
expect(next).not.toHaveBeenCalled();
36+
});
37+
38+
it("should return 403 if expected toggle is not found", async () => {
39+
const middleware = isToggle("testToggle_nonExisting");
40+
await middleware(req as Request, res as Response, next);
41+
42+
expect(res.status).toHaveBeenCalledWith(403);
43+
expect(res.json).toHaveBeenCalledWith({
44+
message: "Feature is currently disabled",
45+
});
46+
expect(next).not.toHaveBeenCalled();
47+
});
48+
49+
it("should return 500 if an exception occurs", async () => {
50+
const toggleName = "testToggle_nonExisting";
51+
const mockError = new Error(`Failed to fetch toggle ${ toggleName }; please try again later`);
52+
53+
const findOneSpy = jest.spyOn(Toggle, 'findOne').mockRejectedValue(mockError);
54+
55+
const middleware = isToggle(toggleName);
56+
await middleware(req as Request, res as Response, next);
57+
58+
expect(res.status).toHaveBeenCalledWith(500);
59+
expect(res.json).toHaveBeenCalledWith({
60+
message: `Failed to fetch toggle ${ toggleName }; please try again later`,
61+
});
62+
expect(next).not.toHaveBeenCalled();
63+
64+
// Restore original method to avoid affecting other tests
65+
findOneSpy.mockRestore();
66+
});
67+
});

test/mockData.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,62 @@
9999
"phone_number": "555-555-5555",
100100
"findme": "deviceGPS",
101101
"trust_meter_rating": 100
102+
},
103+
{
104+
"user_settings_id": "0f0f0f-0f0f-0f0f",
105+
"user_name": "Test Six",
106+
"email": "[email protected]",
107+
"phone_number": "666-666-6666",
108+
"findme": "deviceGPS",
109+
"trust_meter_rating": 50
110+
},
111+
{
112+
"user_settings_id": "0g0g0g-0g0g-0g0g",
113+
"user_name": "Test Seven",
114+
"email": "[email protected]",
115+
"phone_number": "777-777-7777",
116+
"findme": "deviceGPS",
117+
"trust_meter_rating": 50
118+
},
119+
{
120+
"user_settings_id": "0h0h0h-0h0h-0h0h",
121+
"user_name": "Test Eight",
122+
"email": "[email protected]",
123+
"phone_number": "888-888-8888",
124+
"findme": "deviceGPS",
125+
"trust_meter_rating": 50
126+
},
127+
{
128+
"user_settings_id": "0i0i0i-0i0i-0i0i",
129+
"user_name": "Test Nine",
130+
"email": "[email protected]",
131+
"phone_number": "999-999-9999",
132+
"findme": "deviceGPS",
133+
"trust_meter_rating": 50
134+
},
135+
{
136+
"user_settings_id": "0j0j0j-0j0j-0j0j",
137+
"user_name": "Test Ten",
138+
"email": "[email protected]",
139+
"phone_number": "101-101-1010",
140+
"findme": "deviceGPS",
141+
"trust_meter_rating": 50
142+
},
143+
{
144+
"user_settings_id": "0k0k0k-0k0k-0k0k",
145+
"user_name": "Test Eleven",
146+
"email": "[email protected]",
147+
"phone_number": "111-111-1111",
148+
"findme": "deviceGPS",
149+
"trust_meter_rating": 50
150+
},
151+
{
152+
"user_settings_id": "0l0l0l-0l0l-0l0l",
153+
"user_name": "Test Twelve",
154+
"email": "[email protected]",
155+
"phone_number": "121-121-1212",
156+
"findme": "deviceGPS",
157+
"trust_meter_rating": 50
102158
}
103159
],
104160
"sellers": [

test/services/seller.service.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe('getAllSellers function', () => {
3333
const sellersData = await getAllSellers(undefined, undefined, userData.pi_uid);
3434

3535
// filter out inactive sellers and sellers with trust level <= 50.
36-
expect(sellersData).toHaveLength(9);
36+
expect(sellersData).toHaveLength(2);
3737
});
3838

3939
it('should fetch all applicable sellers when search query is provided and bounding box params are empty', async () => {

0 commit comments

Comments
 (0)