diff --git a/apps/backend/package.json b/apps/backend/package.json index d785ad2..46c36d6 100755 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -37,6 +37,7 @@ "drizzle-orm": "^0.44.5", "expo-server-sdk": "^4.0.0", "express": "^5.1.0", + "fuse.js": "^7.1.0", "jsonwebtoken": "^9.0.2", "multer": "^2.0.2", "node-cron": "^4.2.1", @@ -56,4 +57,4 @@ "tsx": "^4.20.5", "typescript": "^5.9.2" } -} \ No newline at end of file +} diff --git a/apps/backend/src/stores/product-store.ts b/apps/backend/src/stores/product-store.ts index a02848b..202303c 100644 --- a/apps/backend/src/stores/product-store.ts +++ b/apps/backend/src/stores/product-store.ts @@ -23,6 +23,7 @@ interface Product { flashPrice: string | null; deliverySlots: Array<{ id: number; deliveryTime: Date; freezeTime: Date }>; specialDeals: Array<{ quantity: string; price: string; validTill: Date }>; + productTags: string[]; } export async function initializeProducts(): Promise { @@ -94,12 +95,27 @@ export async function initializeProducts(): Promise { specialDealsMap.get(deal.productId)!.push(deal); } + // Fetch all product tags + const allProductTags = await db + .select({ + productId: productTags.productId, + tagName: productTagInfo.tagName, + }) + .from(productTags) + .innerJoin(productTagInfo, eq(productTags.tagId, productTagInfo.id)); + const productTagsMap = new Map(); + for (const tag of allProductTags) { + if (!productTagsMap.has(tag.productId)) productTagsMap.set(tag.productId, []); + productTagsMap.get(tag.productId)!.push(tag.tagName); + } + // Store each product in Redis for (const product of productsData) { const signedImages = await generateSignedUrlsFromS3Urls((product.images as string[]) || []); const store = product.storeId ? storeMap.get(product.storeId) || null : null; const deliverySlots = deliverySlotsMap.get(product.id) || []; const specialDeals = specialDealsMap.get(product.id) || []; + const productTags = productTagsMap.get(product.id) || []; const productObj: Product = { id: product.id, @@ -118,6 +134,7 @@ export async function initializeProducts(): Promise { flashPrice: product.flashPrice?.toString() || null, deliverySlots: deliverySlots.map(s => ({ id: s.id, deliveryTime: s.deliveryTime, freezeTime: s.freezeTime })), specialDeals: specialDeals.map(d => ({ quantity: d.quantity.toString(), price: d.price.toString(), validTill: d.validTill })), + productTags: productTags, }; await redisClient.set(`product:${product.id}`, JSON.stringify(productObj)); diff --git a/apps/backend/src/trpc/common-apis/common.ts b/apps/backend/src/trpc/common-apis/common.ts index c3010b2..d27c6c6 100644 --- a/apps/backend/src/trpc/common-apis/common.ts +++ b/apps/backend/src/trpc/common-apis/common.ts @@ -6,6 +6,7 @@ import { generateSignedUrlsFromS3Urls, generateSignedUrlFromS3Url } from '../../ import { z } from 'zod'; import { getAllProducts as getAllProductsFromCache } from '../../stores/product-store'; import { getDashboardTags as getDashboardTagsFromCache } from '../../stores/product-tag-store'; +import Fuse from 'fuse.js'; export const getNextDeliveryDate = async (productId: number): Promise => { const result = await db @@ -66,12 +67,23 @@ export const commonRouter = router({ products = products.filter(product => taggedProductIds.has(product.id)); } - // Apply search filtering if searchQuery is provided + // Apply search filtering if searchQuery is provided using Fuse.js if (searchQuery) { - const searchLower = searchQuery.toLowerCase(); - products = products.filter(product => - product.name.toLowerCase().includes(searchLower) - ); + const fuse = new Fuse(products, { + keys: [ + 'name', + 'shortDescription', + 'longDescription', + 'store.name', // Search in store name too + 'productTags', // Search in product tags too + ], + threshold: 0.3, // Adjust fuzziness (0.0 = exact match, 1.0 = match anything) + includeScore: true, + shouldSort: true, + }); + + const fuseResults = fuse.search(searchQuery); + products = fuseResults.map(result => result.item); } // Get suspended product IDs to filter them out diff --git a/package-lock.json b/package-lock.json index 3956a5a..2af1e5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -167,6 +167,7 @@ "drizzle-orm": "^0.44.5", "expo-server-sdk": "^4.0.0", "express": "^5.1.0", + "fuse.js": "^7.1.0", "jsonwebtoken": "^9.0.2", "multer": "^2.0.2", "node-cron": "^4.2.1", @@ -15489,6 +15490,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fuse.js": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz", + "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",