Skip to content

Commit c557ba1

Browse files
committed
fix: update collection controller. feat: add regex for collection controller
1 parent 38888f0 commit c557ba1

2 files changed

Lines changed: 35 additions & 16 deletions

File tree

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
11
import Product from "../models/product.model.js";
22
import HttpException from "../utils/exceptions/http.exception.js";
3+
import { tokenize, buildLookaheadRegex } from "../utils/regex.util.js";
34

45
// GET /api/collections/:name
56
export const getCollectionProducts = async (req, res, next) => {
67
try {
78
const name = (req.params?.name || "").toString().trim();
8-
// empty or invalid just returns empty results per spec
99
if (!name) {
1010
return res.status(200).json({ collections: name, products: [] });
1111
}
1212

13-
// Normalize input into a regex that is:
14-
// - case-insensitive
15-
// - tolerant of hyphens vs spaces vs multiple separators
16-
// Escape regex special chars, then allow separators between words
17-
const escapeRegex = (s = "") => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18-
const safe = escapeRegex(name);
19-
// replace runs of hyphens / spaces / underscores with a pattern that matches any of them
20-
const tolerant = safe.replace(/[-_\s]+/g, "[-_\\s]*");
21-
const pattern = new RegExp(`^${tolerant}$`, "i");
13+
// try exact slug match first (fast, indexable if you add normalizedCollections)
14+
const slug = name.toLowerCase();
15+
let products = await Product.find({ collections: slug });
16+
if (products && products.length) {
17+
return res.status(200).json({ collections: name, products });
18+
}
19+
20+
// fallback: tokenize and build safe lookahead regex
21+
const tokens = tokenize(name);
22+
// safety limits
23+
if (tokens.length === 0) {
24+
return res.status(200).json({ collections: name, products: [] });
25+
}
26+
if (tokens.length > 6 || tokens.some(t => t.length > 40)) {
27+
return res.status(400).json({ collections: name, products: [], message: "Query too complex" });
28+
}
2229

23-
// Match any array element using $elemMatch + $regex
24-
const products = await Product.find({
30+
const pattern = buildLookaheadRegex(tokens);
31+
32+
products = await Product.find({
2533
collections: { $elemMatch: { $regex: pattern } },
2634
});
2735

@@ -30,7 +38,4 @@ export const getCollectionProducts = async (req, res, next) => {
3038
console.error('Collection controller error (getCollectionProducts):', err);
3139
next(new HttpException(500, "Failed to fetch collections products"));
3240
}
33-
}
34-
35-
36-
41+
}

src/utils/regex.util.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export const escapeRegex = (s = "") => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2+
3+
export const tokenize = (name = "") =>
4+
(name || "")
5+
.split(/[-_\s]+/)
6+
.map(t => escapeRegex(t.trim()))
7+
.filter(Boolean);
8+
9+
export const buildLookaheadRegex = (tokens = []) => {
10+
// tokens should already be escaped
11+
const lookahead = tokens.map(t => `(?=.*\\b${t}\\b)`).join("");
12+
// keep it anchored (start) to reduce backtracking
13+
return new RegExp(`^${lookahead}.*$`, "i");
14+
};

0 commit comments

Comments
 (0)