-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathplaceController.js
More file actions
71 lines (59 loc) · 2.75 KB
/
placeController.js
File metadata and controls
71 lines (59 loc) · 2.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
* Place Controller
* Handles place details, reviews, and search functionality
* @module controllers/placeController
*/
import db from '../config/db.js';
import buildHateoasLinks from '../utils/hateoasBuilder.js';
import R from '../utils/responseBuilder.js';
import { requirePlace } from '../utils/controllerValidators.js';
import placeWrite from './placeWrite.js';
/** GET /places/:placeId - Retrieve place details with reviews */
const getPlace = async (req, res, next) => {
try {
const placeId = parseInt(req.params.placeId);
const place = await requirePlace(res, placeId);
if (!place) return;
const placeWithReviews = {
...(place.toObject ? place.toObject() : place),
reviews: await db.getReviewsForPlace(placeId)
};
return R.success(res, { place: placeWithReviews, links: buildHateoasLinks.placeWithWebsite(placeId, place.website) }, 'Place details retrieved successfully');
} catch (error) { next(error); }
};
const getReviews = async (req, res, next) => {
try {
const placeId = parseInt(req.params.placeId);
const place = await requirePlace(res, placeId);
if (!place) return;
return R.success(res, { reviews: await db.getReviewsForPlace(placeId), links: buildHateoasLinks.reviews(placeId) }, 'Reviews retrieved successfully');
} catch (error) { next(error); }
};
const performSearch = async (req, res, next) => {
try {
const keywords = req.query.keywords;
if (!keywords || keywords.length === 0) {
return R.success(res, { results: [], searchTerms: [], totalResults: 0, links: buildHateoasLinks.search() }, 'No keywords provided');
}
const searchTerms = Array.isArray(keywords) ? keywords : [keywords];
// Input validation: Reject potential injection characters
// This prevents SQL/NoSQL injection attacks and ZAP false positives
const injectionPattern = /['"();${}]/;
for (const term of searchTerms) {
if (injectionPattern.test(term)) {
return R.badRequest(res, 'INVALID_INPUT', 'Search keywords contain invalid characters');
}
}
const results = await db.searchPlaces(searchTerms);
const resultsWithDetails = await Promise.all(results.map(async (place) => {
const placeObj = place.toObject ? place.toObject() : place;
return {
...placeObj,
reviews: await db.getReviewsForPlace(placeObj.placeId),
links: buildHateoasLinks.selectLink(placeObj.placeId)
};
}));
return R.success(res, { results: resultsWithDetails, searchTerms, totalResults: resultsWithDetails.length, links: buildHateoasLinks.search() }, 'Search completed successfully');
} catch (error) { next(error); }
};
export default { getPlace, getReviews, submitReview: placeWrite.submitReview, createReport: placeWrite.createReport, performSearch };