Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .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: 45

steps:
- name: Checkout code
Expand All @@ -21,6 +21,9 @@ jobs:
- name: Install dependencies
run: bun install

- name: Install cf-proxy dependencies
run: cd cf-proxy && bun install

- name: Cache setup artifacts
id: cache-setup
uses: actions/cache@v4
Expand Down
2 changes: 2 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 @@ -34,6 +35,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
2 changes: 2 additions & 0 deletions cf-proxy/src/index.ts
Original file line number Diff line number Diff line change
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.preferred) && !Boolean(row.basic),
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.preferred) && !Boolean(row.basic),
})),
}

Expand Down
4 changes: 4 additions & 0 deletions cf-proxy/src/render.ts
Original file line number Diff line number Diff line change
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
9 changes: 9 additions & 0 deletions cf-proxy/src/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface SearchQueryParams {
limit?: string
is_basic?: string
is_preferred?: string
is_extended_promotional?: string
}

interface SearchRow {
Expand Down Expand Up @@ -110,6 +111,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`search_index.basic = 0`)
}

const raw = params.q?.trim()

if (raw) {
Expand Down
4 changes: 2 additions & 2 deletions lib/db/derivedtables/setup-derived-tables.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { sql } from "kysely"
import { getDbClient } from "lib/db/get-db-client"
import { destroyDbClient, getDbClient } from "lib/db/get-db-client"
import { accelerometerTableSpec } from "lib/db/derivedtables/accelerometer"
import { adcTableSpec } from "lib/db/derivedtables/adc"
import { analogMultiplexerTableSpec } from "lib/db/derivedtables/analog_multiplexer"
Expand Down Expand Up @@ -214,7 +214,7 @@ export const setupDerivedTables = async ({
}
} finally {
if (shouldDestroy) {
await activeDb.destroy()
await destroyDbClient()
}
}
}
8 changes: 8 additions & 0 deletions lib/db/get-db-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ export const getDbClient = () => {
return dbClientSingleton
}

export const destroyDbClient = async () => {
const db = dbClientSingleton
if (!db) return

dbClientSingleton = undefined
await db.destroy()
}

export const getBunDatabaseClient = () => {
const Database = getDatabaseCtor()
return new Database(getResolvedDbPath())
Expand Down
4 changes: 4 additions & 0 deletions lib/util/is-extended-promotional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const isExtendedPromotional = (component: {
basic?: boolean | number | null
preferred?: boolean | number | null
}): boolean => Boolean(component.preferred) && !Boolean(component.basic)
14 changes: 13 additions & 1 deletion routes/api/search.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { sql } from "kysely"
import { isExtendedPromotional } from "lib/util/is-extended-promotional"
import {
buildSearchTokenGroups,
type SearchTokenGroup,
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 @@ -193,12 +198,19 @@ export default withWinterSpec({
package: c.package,
is_basic: Boolean(c.basic),
is_preferred: Boolean(c.preferred),
is_extended_promotional: isExtendedPromotional(c),
description: c.description,
stock: c.stock,
price: extractSmallQuantityPrice(c.price),
}))
const fullComponentsWithExtendedPromotional = fullComponents.map((c) => ({
...c,
is_extended_promotional: isExtendedPromotional(c),
}))

return ctx.json({
components: req.query.full ? fullComponents : components,
components: req.query.full
? fullComponentsWithExtendedPromotional
: components,
})
})
32 changes: 30 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 { isExtendedPromotional } from "lib/util/is-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 @@ -111,14 +117,21 @@ export default withWinterSpec({
package: c.package,
is_basic: Boolean(c.basic),
is_preferred: Boolean(c.preferred),
is_extended_promotional: isExtendedPromotional(c),
description: c.description,
stock: c.stock,
price: extractSmallQuantityPrice(c.price),
}))
const fullComponentsWithExtendedPromotional = fullComponents.map((c) => ({
...c,
is_extended_promotional: isExtendedPromotional(c),
}))

if (ctx.isApiRequest) {
return ctx.json({
components: req.query.full ? fullComponents : components,
components: req.query.full
? fullComponentsWithExtendedPromotional
: components,
})
}

Expand Down Expand Up @@ -155,13 +168,28 @@ 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>

{req.query.subcategory_name && (
<div>Filtering by subcategory: {req.query.subcategory_name}</div>
)}
<Table rows={req.query.full ? fullComponents : components} />
<Table
rows={
req.query.full ? fullComponentsWithExtendedPromotional : components
}
/>
</div>,
req.query.search
? `${req.query.search} - JLCPCB Component Search`
Expand Down
8 changes: 4 additions & 4 deletions scripts/setup-7z.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ 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/7z2601-linux-x64.tar.xz",
"linux-arm64": "https://7-zip.org/a/7z2601-linux-arm64.tar.xz",
"darwin-x64": "https://7-zip.org/a/7z2601-mac.tar.xz",
"darwin-arm64": "https://7-zip.org/a/7z2601-mac.tar.xz",
}

async function downloadAndExtract7z() {
Expand Down
10 changes: 10 additions & 0 deletions tests/lib/is-extended-promotional.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { expect, test } from "bun:test"
import { isExtendedPromotional } from "lib/util/is-extended-promotional"

test("isExtendedPromotional is true for preferred non-basic components", () => {
expect(isExtendedPromotional({ preferred: 1, basic: 0 })).toBe(true)
expect(isExtendedPromotional({ preferred: true, basic: false })).toBe(true)
expect(isExtendedPromotional({ preferred: 1, basic: 1 })).toBe(false)
expect(isExtendedPromotional({ preferred: 0, basic: 0 })).toBe(false)
expect(isExtendedPromotional({ preferred: null, basic: null })).toBe(false)
})
17 changes: 17 additions & 0 deletions tests/routes/api/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,20 @@ 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 exposes and filters extended promotional components", async () => {
const { axios } = await getTestServer()
const res = await axios.get(
"/api/search?limit=50&is_extended_promotional=true",
)

expect(res.data).toHaveProperty("components")
expect(Array.isArray(res.data.components)).toBe(true)
expect(res.data.components.length).toBeGreaterThan(0)

for (const component of res.data.components) {
expect(component).toHaveProperty("is_extended_promotional", true)
expect(component).toHaveProperty("is_basic", false)
expect(component).toHaveProperty("is_preferred", true)
}
})
19 changes: 18 additions & 1 deletion tests/routes/components/list.test.ts
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand All @@ -7,3 +7,20 @@ test("GET /components/list with json param returns component data", async () =>
expect(res.data).toHaveProperty("components")
expect(Array.isArray(res.data.components)).toBe(true)
})

test("GET /components/list exposes and filters extended promotional components", async () => {
const { axios } = await getTestServer()
const res = await axios.get(
"/components/list?json=true&is_extended_promotional=true",
)

expect(res.data).toHaveProperty("components")
expect(Array.isArray(res.data.components)).toBe(true)
expect(res.data.components.length).toBeGreaterThan(0)

for (const component of res.data.components) {
expect(component).toHaveProperty("is_extended_promotional", true)
expect(component).toHaveProperty("is_basic", false)
expect(component).toHaveProperty("is_preferred", true)
}
})
Loading