Skip to content
21 changes: 10 additions & 11 deletions .github/workflows/bun-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 75

steps:
- name: Checkout code
Expand All @@ -17,20 +17,19 @@ jobs:
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install dependencies
run: bun install

- name: Cache setup artifacts
id: cache-setup
uses: actions/cache@v4
with:
path: ./db.sqlite3
key: db-sqlite3-${{ hashFiles('scripts/setup-*.ts') }}
- name: Install cf-proxy dependencies
run: cd cf-proxy && bun install

- name: Run setup if cache miss
if: steps.cache-setup.outputs.cache-hit != 'true'
run: bun run setup
- name: Set up test database
env:
JLCSEARCH_DB_PATH: .tmp/test-db.sqlite3
run: bun run scripts/setup-test-db.ts

- name: Run tests
env:
JLCSEARCH_DB_PATH: .tmp/test-db.sqlite3
run: bun test
3 changes: 3 additions & 0 deletions cf-proxy/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface ComponentCatalogQueryParams {
search?: string
is_basic?: string
is_preferred?: string
is_extended_promotional?: string
}

export async function queryComponentCatalog(
Expand All @@ -22,6 +23,7 @@ export async function queryComponentCatalog(
package: string | null
basic: number | null
preferred: number | null
is_extended_promotional: number | null
description: string | null
stock: number | null
price: string | null
Expand All @@ -34,6 +36,7 @@ export async function queryComponentCatalog(
subcategory_name: params.subcategory_name,
is_basic: params.is_basic,
is_preferred: params.is_preferred,
is_extended_promotional: params.is_extended_promotional,
limit: "100",
})

Expand Down
4 changes: 3 additions & 1 deletion cf-proxy/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CacheService, addCorsHeaders, addVaryHeader } from "./cache-service"
import { queryComponentCatalog } from "./components"
import { getD1Client } from "./db/get-d1-client"
import { getD1Handler } from "./d1-routes"
import { getD1Client } from "./db/get-d1-client"
import { renderD1TablePage, renderHomePage } from "./render"
import { searchIndex } from "./search"

Expand Down Expand Up @@ -367,6 +367,7 @@ async function handleD1Search(
package: row.package ?? "",
is_basic: Boolean(row.basic),
is_preferred: Boolean(row.preferred),
is_extended_promotional: Boolean(row.is_extended_promotional),
description: row.description ?? "",
stock: row.stock ?? 0,
price: row.price1 ?? extractSmallQuantityPrice(row.price),
Expand Down Expand Up @@ -491,6 +492,7 @@ async function handleD1ComponentsList(
subcategory: row.subcategory ?? "",
is_basic: Boolean(row.basic),
is_preferred: Boolean(row.preferred),
is_extended_promotional: Boolean(row.is_extended_promotional),
})),
}

Expand Down
8 changes: 6 additions & 2 deletions cf-proxy/src/render.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
type FilterOptions,
type QueryParams,
ROUTE_TO_TABLE,
TABLE_CONFIGS,
TABLE_RESPONSE_KEY,
type QueryParams,
type FilterOptions,
} from "./handlers"

const escapeHtml = (value: unknown): string =>
Expand Down Expand Up @@ -147,6 +147,7 @@ const COLUMN_LABELS: Record<string, string> = {
in_stock: "In Stock",
is_basic: "Basic",
is_preferred: "Preferred",
is_extended_promotional: "Extended Promotional",
capacitance_farads: "Capacitance",
tolerance_fraction: "Tolerance",
voltage_rating: "Voltage",
Expand Down Expand Up @@ -546,6 +547,9 @@ const renderComponentsFilters = (
<div>
<label>Preferred Part:<input type="checkbox" name="is_preferred" value="true"${params.is_preferred === "true" ? " checked" : ""} /></label>
</div>
<div>
<label>Extended Promotional:<input type="checkbox" name="is_extended_promotional" value="true"${params.is_extended_promotional === "true" ? " checked" : ""} /></label>
</div>
<button type="submit">Filter</button>
</form>`

Expand Down
18 changes: 17 additions & 1 deletion cf-proxy/src/search.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sql, type Kysely, type RawBuilder } from "kysely"
import { type Kysely, type RawBuilder, sql } from "kysely"
import type { DB } from "./db/types"
import { buildSearchTokenGroups } from "./search-query"

Expand All @@ -9,6 +9,7 @@ export interface SearchQueryParams {
limit?: string
is_basic?: string
is_preferred?: string
is_extended_promotional?: string
}

interface SearchRow {
Expand All @@ -21,6 +22,7 @@ interface SearchRow {
price1: number | null
basic: number | null
preferred: number | null
is_extended_promotional: number | null
category: string | null
subcategory: string | null
}
Expand Down Expand Up @@ -110,6 +112,14 @@ export async function searchIndex(
conditions.push(sql`search_index.preferred = 1`)
}

if (
params.is_extended_promotional === "true" ||
params.is_extended_promotional === "1"
) {
conditions.push(sql`search_index.preferred = 1`)
conditions.push(sql`COALESCE(search_index.basic, 0) = 0`)
}

const raw = params.q?.trim()

if (raw) {
Expand Down Expand Up @@ -151,6 +161,12 @@ export async function searchIndex(
search_index.price1,
search_index.basic,
search_index.preferred,
CASE
WHEN search_index.preferred = 1
AND COALESCE(search_index.basic, 0) = 0
THEN 1
ELSE 0
END AS is_extended_promotional,
search_index.category,
search_index.subcategory
FROM search_index
Expand Down
24 changes: 24 additions & 0 deletions cf-proxy/test/render.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,28 @@ describe("render helpers", () => {
"Feature&quot;:&quot;Overcurrent Protection(OCP)&quot;",
)
})

it("renders the extended promotional components filter", () => {
const html = renderD1TablePage(
"/components/list",
{
components: [
{
lcsc: 123,
mfr: "PROMO-PART",
package: "SMD",
is_extended_promotional: true,
},
],
},
{ is_extended_promotional: "true" },
"https://example.com/components/list?is_extended_promotional=true",
)

expect(html).toContain("Extended Promotional")
expect(html).toContain('name="is_extended_promotional"')
expect(html).toContain(
'name="is_extended_promotional" value="true" checked',
)
})
})
Binary file added demos/extended-promotional-filter-demo.mp4
Binary file not shown.
16 changes: 16 additions & 0 deletions lib/util/extended-promotional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
type ComponentPromotionFlags = {
basic?: boolean | number | string | null
preferred?: boolean | number | string | null
is_basic?: boolean | number | string | null
is_preferred?: boolean | number | string | null
}

const isTruthyFlag = (value: boolean | number | string | null | undefined) =>
value === true || value === 1 || value === "1" || value === "true"

export const isExtendedPromotional = (component: ComponentPromotionFlags) => {
const basic = component.basic ?? component.is_basic
const preferred = component.preferred ?? component.is_preferred

return isTruthyFlag(preferred) && !isTruthyFlag(basic)
}
18 changes: 15 additions & 3 deletions routes/api/search.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { sql } from "kysely"
import { isExtendedPromotional } from "lib/util/extended-promotional"
import {
buildSearchTokenGroups,
type SearchTokenGroup,
buildSearchTokenGroups,
tokenizeSearchTerm,
} from "lib/util/search-token-groups"
import { withWinterSpec } from "lib/with-winter-spec"
Expand Down Expand Up @@ -50,6 +51,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) => {
Expand All @@ -72,6 +74,9 @@ export default withWinterSpec({
if (req.query.is_preferred) {
query = query.where("preferred", "=", 1)
}
if (req.query.is_extended_promotional) {
query = query.where("preferred", "=", 1).where("basic", "=", 0)
}

const baseQuery = query
let fallbackLikeTokens: string[] = []
Expand Down Expand Up @@ -147,7 +152,10 @@ export default withWinterSpec({
}
}

const fullComponents = await query.execute()
const fullComponents = (await query.execute()).map((c) => ({
...c,
is_extended_promotional: isExtendedPromotional(c),
}))

if (fallbackLikeTokens.length > 0 && fullComponents.length === 0) {
let fallbackQuery = baseQuery
Expand Down Expand Up @@ -181,7 +189,10 @@ export default withWinterSpec({

for (const component of fallbackComponents) {
if (seenLcsc.has(component.lcsc)) continue
fullComponents.push(component)
fullComponents.push({
...component,
is_extended_promotional: isExtendedPromotional(component),
})
seenLcsc.add(component.lcsc)
if (fullComponents.length >= limit) break
}
Expand All @@ -193,6 +204,7 @@ export default withWinterSpec({
package: c.package,
is_basic: Boolean(c.basic),
is_preferred: Boolean(c.preferred),
is_extended_promotional: c.is_extended_promotional,
description: c.description,
stock: c.stock,
price: extractSmallQuantityPrice(c.price),
Expand Down
25 changes: 23 additions & 2 deletions routes/components/list.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { sql } from "kysely"
import { Table } from "lib/ui/Table"
import { ExpressionBuilder } from "kysely"
import { Table } from "lib/ui/Table"
import { isExtendedPromotional } from "lib/util/extended-promotional"
import { buildSearchTokenGroups } from "lib/util/search-token-groups"
import { withWinterSpec } from "lib/with-winter-spec"
import { z } from "zod"
Expand Down Expand Up @@ -35,6 +36,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) => {
Expand All @@ -51,6 +53,7 @@ export default withWinterSpec({
"price",
"extra",
"basic",
"preferred",
])
.limit(limit)
.orderBy("stock", "desc")
Expand All @@ -70,6 +73,9 @@ export default withWinterSpec({
if (req.query.is_preferred) {
query = query.where("preferred", "=", 1)
}
if (req.query.is_extended_promotional) {
query = query.where("preferred", "=", 1).where("basic", "=", 0)
}

if (req.query.search) {
const search = req.query.search
Expand Down Expand Up @@ -103,14 +109,18 @@ export default withWinterSpec({
}
}

const fullComponents = await query.execute()
const fullComponents = (await query.execute()).map((c: any) => ({
...c,
is_extended_promotional: isExtendedPromotional(c),
}))

const components = fullComponents.map((c: any) => ({
lcsc: c.lcsc,
mfr: c.mfr,
package: c.package,
is_basic: Boolean(c.basic),
is_preferred: Boolean(c.preferred),
is_extended_promotional: c.is_extended_promotional,
description: c.description,
stock: c.stock,
price: extractSmallQuantityPrice(c.price),
Expand Down Expand Up @@ -155,6 +165,17 @@ export default withWinterSpec({
/>
</label>
</div>
<div>
<label>
Extended Promotional:
<input
type="checkbox"
name="is_extended_promotional"
value="true"
checked={req.query.is_extended_promotional}
/>
</label>
</div>
<button type="submit">Filter</button>
</form>

Expand Down
12 changes: 6 additions & 6 deletions scripts/setup-7z.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { mkdir, chmod } from "node:fs/promises"
import { existsSync } from "node:fs"
import { platform, arch } from "node:os"
import { chmod, mkdir } from "node:fs/promises"
import { arch, platform } from "node:os"

const BINARY_DIR = ".bin"
const BINARY_NAME = "7zz"

// Map of platform-arch combinations to download URLs
const BINARY_URLS: Record<string, string> = {
"linux-x64": "https://7-zip.org/a/7z2408-linux-x64.tar.xz",
"linux-arm64": "https://7-zip.org/a/7z2408-linux-arm64.tar.xz",
"darwin-x64": "https://7-zip.org/a/7z2408-mac.tar.xz",
"darwin-arm64": "https://7-zip.org/a/7z2408-mac.tar.xz",
"linux-x64": "https://7-zip.org/a/7z2501-linux-x64.tar.xz",
"linux-arm64": "https://7-zip.org/a/7z2501-linux-arm64.tar.xz",
"darwin-x64": "https://7-zip.org/a/7z2501-mac.tar.xz",
"darwin-arm64": "https://7-zip.org/a/7z2501-mac.tar.xz",
}

async function downloadAndExtract7z() {
Expand Down
Loading
Loading