Skip to content

Commit 23d68a7

Browse files
authored
Merge pull request #264 from map-of-pi/feat/expose-sanctioned-regions-to-client
Approved (1); Feat/expose sanctioned regions to client
2 parents 007190a + 684e7e2 commit 23d68a7

File tree

6 files changed

+237
-0
lines changed

6 files changed

+237
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Request, Response } from "express";
2+
import * as restrictionService from "../../services/admin/restriction.service";
3+
4+
import logger from "../../config/loggingConfig";
5+
6+
export const checkSanctionStatus = async (req: Request, res: Response) => {
7+
const { latitude, longitude } = req.body;
8+
9+
if (typeof latitude !== 'number' || typeof longitude !== 'number') {
10+
logger.error(`Invalid coordinates provided as ${latitude}, ${longitude}`);
11+
return res.status(400).json({ error: 'Unexpected coordinates provided' });
12+
}
13+
14+
try {
15+
const result = await restrictionService.validateSellerLocation(longitude, latitude);
16+
const isSanctioned = !!result;
17+
18+
const status = isSanctioned ? 'in a sanctioned zone' : 'not in a sanctioned zone';
19+
logger.info(`User at [${latitude}, ${longitude}] is ${status}.`);
20+
return res.status(200).json({
21+
message: `Sell center is set within a ${isSanctioned ? 'sanctioned' : 'unsanctioned' } zone`,
22+
isSanctioned
23+
});
24+
} catch (error) {
25+
logger.error('Failed to get sanctioned status:', error);
26+
return res.status(500).json({
27+
message: 'An error occurred while checking sanction status; please try again later'
28+
});
29+
}
30+
};

src/routes/restriction.routes.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import {Router} from "express";
2+
import * as restrictionController from "../controllers/admin/restrictionController";
3+
4+
const restrictionRoutes = Router();
5+
6+
/**
7+
* @swagger
8+
* /api/v1/restrictions/check-sanction-status:
9+
* post:
10+
* tags:
11+
* - Restriction
12+
* summary: Check if a [latitude, longitude] coordinate is within sanctioned boundaries.
13+
* requestBody:
14+
* required: true
15+
* content:
16+
* application/json:
17+
* schema:
18+
* type: object
19+
* properties:
20+
* latitude:
21+
* type: number
22+
* example: -1.94995
23+
* longitude:
24+
* type: number
25+
* example: 30.0588
26+
* required:
27+
* - latitude
28+
* - longitude
29+
* responses:
30+
* 200:
31+
* description: Successful response
32+
* content:
33+
* application/json:
34+
* schema:
35+
* type: object
36+
* properties:
37+
* isSanctioned:
38+
* type: boolean
39+
* example: true
40+
* 400:
41+
* description: Bad request
42+
* 500:
43+
* description: Internal server error
44+
*/
45+
restrictionRoutes.post(
46+
"/check-sanction-status",
47+
restrictionController.checkSanctionStatus
48+
);
49+
50+
export default restrictionRoutes;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import SanctionedRegion from "../../models/misc/SanctionedRegion";
2+
3+
export const validateSellerLocation = async (longitude: number, latitude: number) => {
4+
const sellCenter = {
5+
type: 'Point' as const,
6+
coordinates: [longitude, latitude],
7+
};
8+
9+
const isSanctionedLocation = await SanctionedRegion.findOne({
10+
boundary: {
11+
$geoIntersects: {
12+
$geometry: sellCenter
13+
}
14+
}
15+
}).exec();
16+
17+
return isSanctionedLocation;
18+
};

src/utils/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import reviewFeedbackRoutes from "../routes/reviewFeedback.routes";
1616
import mapCenterRoutes from "../routes/mapCenter.routes";
1717
import reportRoutes from "../routes/report.routes";
1818
import toggleRoutes from "../routes/toggle.routes";
19+
import restrictionRoutes from "../routes/restriction.routes";
1920

2021
dotenv.config();
2122

@@ -45,6 +46,7 @@ app.use("/api/v1/review-feedback", reviewFeedbackRoutes);
4546
app.use("/api/v1/map-center", mapCenterRoutes);
4647
app.use("/api/v1/reports", reportRoutes);
4748
app.use("/api/v1/toggles", toggleRoutes);
49+
app.use("/api/v1/restrictions", restrictionRoutes);
4850

4951
app.use("/", homeRoutes);
5052

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { checkSanctionStatus } from '../../../src/controllers/admin/restrictionController';
2+
import * as restrictionService from '../../../src/services/admin/restriction.service';
3+
import { RestrictedArea } from '../../../src/models/enums/restrictedArea';
4+
5+
jest.mock('../../../src/services/admin/restriction.service', () => ({
6+
validateSellerLocation: jest.fn(),
7+
}));
8+
9+
describe('RestrictionController', () => {
10+
let req: any;
11+
let res: any;
12+
13+
beforeEach(() => {
14+
req = { body: {} };
15+
res = {
16+
status: jest.fn().mockReturnThis(),
17+
json: jest.fn(),
18+
};
19+
});
20+
21+
describe('checkSanctionedStatus function', () => {
22+
const mockSanctionedRegion = {
23+
location: RestrictedArea.NORTH_KOREA,
24+
boundary: {
25+
type: 'Polygon',
26+
coordinates: [[
27+
[123.5, 37.5],
28+
[131.2, 37.5],
29+
[131.2, 43.0],
30+
[123.5, 43.0],
31+
[123.5, 37.5],
32+
]],
33+
},
34+
};
35+
36+
it('should return [200] and isSanctioned true if seller is in a sanctioned zone', async () => {
37+
req.body = { latitude: 123.5, longitude: 40.5 };
38+
(restrictionService.validateSellerLocation as jest.Mock).mockResolvedValue(mockSanctionedRegion);
39+
40+
await checkSanctionStatus(req, res);
41+
expect(restrictionService.validateSellerLocation).toHaveBeenCalled();
42+
expect(res.status).toHaveBeenCalledWith(200);
43+
expect(res.json).toHaveBeenCalledWith({
44+
message: 'Sell center is set within a sanctioned zone',
45+
isSanctioned: true,
46+
});
47+
});
48+
49+
it('should return [200] and isSanctioned false if seller is not in a sanctioned zone', async () => {
50+
req.body = { latitude: 23.5, longitude: 40.5 };
51+
(restrictionService.validateSellerLocation as jest.Mock).mockResolvedValue(null);
52+
53+
await checkSanctionStatus(req, res);
54+
expect(restrictionService.validateSellerLocation).toHaveBeenCalled();
55+
expect(res.status).toHaveBeenCalledWith(200);
56+
expect(res.json).toHaveBeenCalledWith({
57+
message: 'Sell center is set within a unsanctioned zone',
58+
isSanctioned: false,
59+
});
60+
});
61+
62+
it('should return [400] if latitude or longitude is missing or invalid', async () => {
63+
req.body = { latitude: "malformed", longitude: null };
64+
65+
await checkSanctionStatus(req, res);
66+
67+
expect(res.status).toHaveBeenCalledWith(400);
68+
expect(res.json).toHaveBeenCalledWith({ error: "Unexpected coordinates provided" });
69+
});
70+
71+
it('should return appropriate [500] if check sanction status fails', async () => {
72+
const mockError = new Error('An error occurred while checking sanction status; please try again later');
73+
74+
req.body = { latitude: 23.5, longitude: 40.5 };
75+
(restrictionService.validateSellerLocation as jest.Mock).mockRejectedValue(mockError);
76+
77+
await checkSanctionStatus(req, res);
78+
expect(restrictionService.validateSellerLocation).toHaveBeenCalled();
79+
expect(res.status).toHaveBeenCalledWith(500);
80+
expect(res.json).toHaveBeenCalledWith({ message: mockError.message });
81+
});
82+
});
83+
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { validateSellerLocation } from '../../../src/services/admin/restriction.service';
2+
import SanctionedRegion from '../../../src/models/misc/SanctionedRegion';
3+
import { RestrictedArea } from '../../../src/models/enums/restrictedArea';
4+
5+
jest.mock('../../../src/models/misc/SanctionedRegion');
6+
7+
describe('validateSellerLocation function', () => {
8+
const longitude = 123.5;
9+
const latitude = 40.5;
10+
11+
const mockSanctionedRegion = {
12+
location: RestrictedArea.NORTH_KOREA,
13+
boundary: {
14+
type: 'Polygon',
15+
coordinates: [[
16+
[123.5, 37.5],
17+
[131.2, 37.5],
18+
[131.2, 43.0],
19+
[123.5, 43.0],
20+
[123.5, 37.5],
21+
]],
22+
},
23+
};
24+
25+
it('should return a sanctioned region given the coordinates is found', async () => {
26+
(SanctionedRegion.findOne as jest.Mock).mockReturnValue({
27+
exec: jest.fn().mockResolvedValue(mockSanctionedRegion),
28+
});
29+
30+
const result = await validateSellerLocation(longitude, latitude);
31+
32+
expect(SanctionedRegion.findOne).toHaveBeenCalledWith({
33+
boundary: {
34+
$geoIntersects: {
35+
$geometry: {
36+
type: 'Point',
37+
coordinates: [longitude, latitude],
38+
},
39+
},
40+
},
41+
});
42+
43+
expect(result).toEqual(mockSanctionedRegion);
44+
});
45+
46+
it('should return null if no sanctioned region given the coordinates is found', async () => {
47+
(SanctionedRegion.findOne as jest.Mock).mockReturnValue({
48+
exec: jest.fn().mockResolvedValue(null),
49+
});
50+
51+
const result = await validateSellerLocation(longitude, latitude);
52+
expect(result).toBeNull();
53+
});
54+
});

0 commit comments

Comments
 (0)