From afa4f6de1894d3c5f31582e7620ed4b97a496419 Mon Sep 17 00:00:00 2001 From: yuliuyi717-ux <264093635+yuliuyi717-ux@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:19:40 +0800 Subject: [PATCH] Add is_extended_promotional filter to components and search --- routes/api/search.tsx | 10 ++++++++ routes/components/list.tsx | 23 ++++++++++++++++- tests/routes/api/search.test.ts | 31 +++++++++++++++++++++++ tests/routes/components/list.test.ts | 38 +++++++++++++++++++++++++++- 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/routes/api/search.tsx b/routes/api/search.tsx index 4bf0cad..84428cd 100644 --- a/routes/api/search.tsx +++ b/routes/api/search.tsx @@ -32,6 +32,8 @@ const broadSearchTokens = new Set([ "chip", ]) +const isExtendedPromotionalExpr = sql`CASE WHEN preferred = 1 AND basic = 0 THEN 1 ELSE 0 END` + export default withWinterSpec({ auth: "none", methods: ["GET"], @@ -42,6 +44,7 @@ export default withWinterSpec({ limit: z.string().optional(), is_basic: z.boolean().optional(), is_preferred: z.boolean().optional(), + is_extended_promotional: z.boolean().optional(), }), jsonResponse: z.any(), } as const)(async (req, ctx) => { @@ -50,6 +53,7 @@ export default withWinterSpec({ let query = ctx.db .selectFrom("components") .selectAll() + .select(isExtendedPromotionalExpr.as("is_extended_promotional")) .limit(limit) .orderBy("stock", "desc") .where("stock", ">", 0) @@ -64,6 +68,11 @@ export default withWinterSpec({ if (req.query.is_preferred) { query = query.where("preferred", "=", 1) } + if (req.query.is_extended_promotional) { + query = query.where((eb) => + eb.and([eb("preferred", "=", 1), eb("basic", "=", 0)]), + ) + } const baseQuery = query let fallbackLikeTokens: string[] = [] @@ -189,6 +198,7 @@ export default withWinterSpec({ package: c.package, is_basic: Boolean(c.basic), is_preferred: Boolean(c.preferred), + is_extended_promotional: Boolean(c.is_extended_promotional), description: c.description, stock: c.stock, price: extractSmallQuantityPrice(c.price), diff --git a/routes/components/list.tsx b/routes/components/list.tsx index 8d26b56..ccfb486 100644 --- a/routes/components/list.tsx +++ b/routes/components/list.tsx @@ -1,6 +1,5 @@ import { sql } from "kysely" import { Table } from "lib/ui/Table" -import { ExpressionBuilder } from "kysely" import { withWinterSpec } from "lib/with-winter-spec" import { z } from "zod" @@ -13,6 +12,8 @@ const extractSmallQuantityPrice = (price: string | null) => { } } +const isExtendedPromotionalExpr = sql`CASE WHEN preferred = 1 AND basic = 0 THEN 1 ELSE 0 END` + export default withWinterSpec({ auth: "none", methods: ["GET"], @@ -23,6 +24,7 @@ export default withWinterSpec({ search: z.string().optional(), is_basic: z.boolean().optional(), is_preferred: z.boolean().optional(), + is_extended_promotional: z.boolean().optional(), }), jsonResponse: z.any(), } as const)(async (req, ctx) => { @@ -39,6 +41,8 @@ export default withWinterSpec({ "price", "extra", "basic", + "preferred", + isExtendedPromotionalExpr.as("is_extended_promotional"), ]) .limit(limit) .orderBy("stock", "desc") @@ -58,6 +62,11 @@ export default withWinterSpec({ if (req.query.is_preferred) { query = query.where("preferred", "=", 1) } + if (req.query.is_extended_promotional) { + query = query.where((eb) => + eb.and([eb("preferred", "=", 1), eb("basic", "=", 0)]), + ) + } if (req.query.search) { const search = req.query.search // TypeScript now knows this is defined within this block @@ -82,6 +91,7 @@ export default withWinterSpec({ package: c.package, is_basic: Boolean(c.basic), is_preferred: Boolean(c.preferred), + is_extended_promotional: Boolean(c.is_extended_promotional), description: c.description, stock: c.stock, price: extractSmallQuantityPrice(c.price), @@ -126,6 +136,17 @@ export default withWinterSpec({ /> +
+ +
diff --git a/tests/routes/api/search.test.ts b/tests/routes/api/search.test.ts index 90f3794..a707065 100644 --- a/tests/routes/api/search.test.ts +++ b/tests/routes/api/search.test.ts @@ -18,6 +18,7 @@ test("GET /api/search with search query 'STM32F401RCT6' returns expected compone expect(component).toHaveProperty("package") expect(component).toHaveProperty("price") expect(component).toHaveProperty("stock") + expect(component).toHaveProperty("is_extended_promotional") }) test("GET /api/search with search query '555 Timer' returns expected components", async () => { @@ -107,3 +108,33 @@ test("GET /api/search supports '0402 LED'", async () => { expect(res.data.components.every((c: any) => c.package === "0402")).toBe(true) expect(res.data.components.some((c: any) => c.lcsc === 965793)).toBe(true) }) + +test("GET /api/search supports is_extended_promotional filter", async () => { + const { axios } = await getTestServer() + const allRes = await axios.get("/api/search?full=true&limit=200") + const filteredRes = await axios.get( + "/api/search?full=true&limit=200&is_extended_promotional=true", + ) + + const allComponents = allRes.data.components as Array<{ + basic: number + preferred: number + is_extended_promotional: number + }> + const filteredComponents = filteredRes.data.components as Array<{ + basic: number + preferred: number + is_extended_promotional: number + }> + + for (const component of allComponents) { + const expected = component.preferred === 1 && component.basic === 0 ? 1 : 0 + expect(component.is_extended_promotional).toBe(expected) + } + + for (const component of filteredComponents) { + expect(component.is_extended_promotional).toBe(1) + expect(component.preferred).toBe(1) + expect(component.basic).toBe(0) + } +}) diff --git a/tests/routes/components/list.test.ts b/tests/routes/components/list.test.ts index 061f1c3..3a7182b 100644 --- a/tests/routes/components/list.test.ts +++ b/tests/routes/components/list.test.ts @@ -1,4 +1,4 @@ -import { test, expect } from "bun:test" +import { expect, test } from "bun:test" import { getTestServer } from "tests/fixtures/get-test-server" test("GET /components/list with json param returns component data", async () => { @@ -6,4 +6,40 @@ test("GET /components/list with json param returns component data", async () => const res = await axios.get("/components/list?json=true") expect(res.data).toHaveProperty("components") expect(Array.isArray(res.data.components)).toBe(true) + if (res.data.components.length > 0) { + expect(res.data.components[0]).toHaveProperty("is_extended_promotional") + expect(typeof res.data.components[0].is_extended_promotional).toBe( + "boolean", + ) + } +}) + +test("GET /components/list supports is_extended_promotional filter", async () => { + const { axios } = await getTestServer() + const allRes = await axios.get("/components/list?json=true&full=true") + const filteredRes = await axios.get( + "/components/list?json=true&full=true&is_extended_promotional=true", + ) + + const allComponents = allRes.data.components as Array<{ + basic: number + preferred: number + is_extended_promotional: number + }> + const filteredComponents = filteredRes.data.components as Array<{ + basic: number + preferred: number + is_extended_promotional: number + }> + + for (const component of allComponents) { + const expected = component.preferred === 1 && component.basic === 0 ? 1 : 0 + expect(component.is_extended_promotional).toBe(expected) + } + + for (const component of filteredComponents) { + expect(component.is_extended_promotional).toBe(1) + expect(component.preferred).toBe(1) + expect(component.basic).toBe(0) + } })