From c6c44a09143378ebf613be8217af1fd60976c7c6 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Tue, 28 Apr 2026 21:34:47 -0400 Subject: [PATCH] fix(api): preserve unstable buckets --- packages/api-server/src/routes/ebay.ts | 29 +++++++------ packages/api-server/src/routes/facebook.ts | 28 +++++++++---- packages/api-server/src/routes/kijiji.ts | 48 ++++++++++++---------- packages/api-server/test/routes.test.ts | 36 ++++++++++++---- 4 files changed, 89 insertions(+), 52 deletions(-) diff --git a/packages/api-server/src/routes/ebay.ts b/packages/api-server/src/routes/ebay.ts index 9b95b8c..1f4b727 100644 --- a/packages/api-server/src/routes/ebay.ts +++ b/packages/api-server/src/routes/ebay.ts @@ -21,7 +21,7 @@ export async function ebayRoute(req: Request): Promise { const minPriceParam = reqUrl.searchParams.get("minPrice"); const minPrice = minPriceParam ? parseInt(minPriceParam, 10) : undefined; - if (minPriceParam && (Number.isNaN(minPrice) || minPrice < 0)) { + if (minPriceParam && (Number.isNaN(minPrice) || (minPrice ?? 0) < 0)) { return Response.json( { message: "Invalid minPrice parameter" }, { status: 400 }, @@ -29,7 +29,7 @@ export async function ebayRoute(req: Request): Promise { } const maxPriceParam = reqUrl.searchParams.get("maxPrice"); const maxPrice = maxPriceParam ? parseInt(maxPriceParam, 10) : undefined; - if (maxPriceParam && (Number.isNaN(maxPrice) || maxPrice < 0)) { + if (maxPriceParam && (Number.isNaN(maxPrice) || (maxPrice ?? 0) < 0)) { return Response.json( { message: "Invalid maxPrice parameter" }, { status: 400 }, @@ -49,7 +49,7 @@ export async function ebayRoute(req: Request): Promise { const maxItemsParam = reqUrl.searchParams.get("maxItems"); const maxItems = maxItemsParam ? parseInt(maxItemsParam, 10) : undefined; - if (maxItemsParam && (Number.isNaN(maxItems) || maxItems < 0)) { + if (maxItemsParam && (Number.isNaN(maxItems) || (maxItems ?? 0) < 0)) { return Response.json( { message: "Invalid maxItems parameter" }, { status: 400 }, @@ -67,22 +67,27 @@ export async function ebayRoute(req: Request): Promise { canadaOnly, maxItems, }; - const items = hideUnstableResults - ? await fetchEbayItems(SEARCH_QUERY, 1, opts, { - hideUnstableResults: true, - }) - : await fetchEbayItems(SEARCH_QUERY, 1, opts); + if (hideUnstableResults) { + const items = await fetchEbayItems(SEARCH_QUERY, 1, opts, { + hideUnstableResults: true, + }); + if (items.results.length === 0 && items.unstableResults.length === 0) { + return Response.json( + { message: "Search didn't return any results!" }, + { status: 404 }, + ); + } + return Response.json(items, { status: 200 }); + } - const isEmpty = hideUnstableResults - ? items.results.length === 0 && items.unstableResults.length === 0 - : !items || items.length === 0; + const items = await fetchEbayItems(SEARCH_QUERY, 1, opts); + const isEmpty = !items || items.length === 0; if (isEmpty) return Response.json( { message: "Search didn't return any results!" }, { status: 404 }, ); - return Response.json(items, { status: 200 }); } catch (error) { console.error("eBay scraping error:", error); diff --git a/packages/api-server/src/routes/facebook.ts b/packages/api-server/src/routes/facebook.ts index ef5d4a2..b3dad14 100644 --- a/packages/api-server/src/routes/facebook.ts +++ b/packages/api-server/src/routes/facebook.ts @@ -30,17 +30,27 @@ export async function facebookRoute(req: Request): Promise { reqUrl.searchParams.get("unstableFilter") === "true"; try { - const items = hideUnstableResults - ? await fetchFacebookItems(SEARCH_QUERY, 1, LOCATION, maxItems, { + if (hideUnstableResults) { + const items = await fetchFacebookItems( + SEARCH_QUERY, + 1, + LOCATION, + maxItems, + { hideUnstableResults: true, - }) - : await fetchFacebookItems(SEARCH_QUERY, 1, LOCATION, maxItems); + }, + ); + if (items.results.length === 0 && items.unstableResults.length === 0) { + return Response.json( + { message: "Search didn't return any results!" }, + { status: 404 }, + ); + } + return Response.json(items, { status: 200 }); + } - const isEmpty = hideUnstableResults - ? items.results.length === 0 && items.unstableResults.length === 0 - : !items || items.length === 0; - - if (isEmpty) + const items = await fetchFacebookItems(SEARCH_QUERY, 1, LOCATION, maxItems); + if (!items || items.length === 0) return Response.json( { message: "Search didn't return any results!" }, { status: 404 }, diff --git a/packages/api-server/src/routes/kijiji.ts b/packages/api-server/src/routes/kijiji.ts index e52d4f3..fabdec6 100644 --- a/packages/api-server/src/routes/kijiji.ts +++ b/packages/api-server/src/routes/kijiji.ts @@ -27,7 +27,7 @@ export async function kijijiRoute(req: Request): Promise { } const priceMinParam = reqUrl.searchParams.get("priceMin"); const priceMin = priceMinParam ? parseInt(priceMinParam, 10) : undefined; - if (priceMinParam && (Number.isNaN(priceMin) || priceMin < 0)) { + if (priceMinParam && (Number.isNaN(priceMin) || (priceMin ?? 0) < 0)) { return Response.json( { message: "Invalid priceMin parameter" }, { status: 400 }, @@ -35,7 +35,7 @@ export async function kijijiRoute(req: Request): Promise { } const priceMaxParam = reqUrl.searchParams.get("priceMax"); const priceMax = priceMaxParam ? parseInt(priceMaxParam, 10) : undefined; - if (priceMaxParam && (Number.isNaN(priceMax) || priceMax < 0)) { + if (priceMaxParam && (Number.isNaN(priceMax) || (priceMax ?? 0) < 0)) { return Response.json( { message: "Invalid priceMax parameter" }, { status: 400 }, @@ -65,28 +65,32 @@ export async function kijijiRoute(req: Request): Promise { }; try { - const items = hideUnstableResults - ? await fetchKijijiItems( - SEARCH_QUERY, - 4, // 4 requests per second for faster scraping - "https://www.kijiji.ca", - searchOptions, - {}, - { hideUnstableResults: true }, - ) - : await fetchKijijiItems( - SEARCH_QUERY, - 4, // 4 requests per second for faster scraping - "https://www.kijiji.ca", - searchOptions, - {}, + if (hideUnstableResults) { + const items = await fetchKijijiItems( + SEARCH_QUERY, + 4, // 4 requests per second for faster scraping + "https://www.kijiji.ca", + searchOptions, + {}, + { hideUnstableResults: true }, + ); + if (items.results.length === 0 && items.unstableResults.length === 0) { + return Response.json( + { message: "Search didn't return any results!" }, + { status: 404 }, ); + } + return Response.json(items, { status: 200 }); + } - const isEmpty = hideUnstableResults - ? items.results.length === 0 && items.unstableResults.length === 0 - : !items || items.length === 0; - - if (isEmpty) + const items = await fetchKijijiItems( + SEARCH_QUERY, + 4, // 4 requests per second for faster scraping + "https://www.kijiji.ca", + searchOptions, + {}, + ); + if (!items || items.length === 0) return Response.json( { message: "Search didn't return any results!" }, { status: 404 }, diff --git a/packages/api-server/test/routes.test.ts b/packages/api-server/test/routes.test.ts index 99258c5..56d348c 100644 --- a/packages/api-server/test/routes.test.ts +++ b/packages/api-server/test/routes.test.ts @@ -1,8 +1,23 @@ import { beforeEach, describe, expect, mock, test } from "bun:test"; -const fetchFacebookItems = mock(() => Promise.resolve([{ title: "item" }])); -const fetchEbayItems = mock(() => Promise.resolve([{ title: "item" }])); -const fetchKijijiItems = mock(() => Promise.resolve([{ title: "item" }])); +const fetchFacebookItems = mock( + (): Promise< + | { title: string }[] + | { results: { title: string }[]; unstableResults: { title: string }[] } + > => Promise.resolve([{ title: "item" }]), +); +const fetchEbayItems = mock( + (): Promise< + | { title: string }[] + | { results: { title: string }[]; unstableResults: { title: string }[] } + > => Promise.resolve([{ title: "item" }]), +); +const fetchKijijiItems = mock( + (): Promise< + | { title: string }[] + | { results: { title: string }[]; unstableResults: { title: string }[] } + > => Promise.resolve([{ title: "item" }]), +); mock.module("@marketplace-scrapers/core", () => ({ fetchFacebookItems, @@ -13,16 +28,19 @@ mock.module("@marketplace-scrapers/core", () => ({ describe("API routes", () => { beforeEach(() => { fetchFacebookItems.mockReset(); - fetchFacebookItems.mockImplementation(() => - Promise.resolve([{ title: "item" }]), + fetchFacebookItems.mockImplementation( + () => + Promise.resolve([{ title: "item" }]) as Promise<{ title: string }[]>, ); fetchEbayItems.mockReset(); - fetchEbayItems.mockImplementation(() => - Promise.resolve([{ title: "item" }]), + fetchEbayItems.mockImplementation( + () => + Promise.resolve([{ title: "item" }]) as Promise<{ title: string }[]>, ); fetchKijijiItems.mockReset(); - fetchKijijiItems.mockImplementation(() => - Promise.resolve([{ title: "item" }]), + fetchKijijiItems.mockImplementation( + () => + Promise.resolve([{ title: "item" }]) as Promise<{ title: string }[]>, ); });