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
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
24 changes: 24 additions & 0 deletions cf-proxy/src/extended-promotional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export type ExtendedPromotionalSource = {
basic?: boolean | number | string | null
preferred?: boolean | number | string | null
}

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

export const isExtendedPromotional = (
component: ExtendedPromotionalSource,
): boolean =>
isEnabledFlag(component.preferred) && !isEnabledFlag(component.basic)

export const parseBooleanFilter = (
value: boolean | string | null | undefined,
): boolean | undefined => {
if (value === true || value === false) return value
if (value === undefined || value === null || value === "") return undefined

const normalized = value.toLowerCase()
if (normalized === "true" || normalized === "1") return true
if (normalized === "false" || normalized === "0") return false
return undefined
}
5 changes: 4 additions & 1 deletion cf-proxy/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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 { isExtendedPromotional } from "./extended-promotional"
import { renderD1TablePage, renderHomePage } from "./render"
import { searchIndex } from "./search"

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

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
15 changes: 14 additions & 1 deletion cf-proxy/src/search.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { sql, type Kysely, type RawBuilder } from "kysely"
import { type Kysely, type RawBuilder, sql } from "kysely"
import type { DB } from "./db/types"
import { parseBooleanFilter } from "./extended-promotional"
import { buildSearchTokenGroups } from "./search-query"

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

interface SearchRow {
Expand Down Expand Up @@ -110,6 +112,17 @@ export async function searchIndex(
conditions.push(sql`search_index.preferred = 1`)
}

const extendedPromotionalFilter = parseBooleanFilter(
params.is_extended_promotional,
)
if (extendedPromotionalFilter === true) {
conditions.push(sql`search_index.preferred = 1 AND search_index.basic = 0`)
} else if (extendedPromotionalFilter === false) {
conditions.push(
sql`NOT (search_index.preferred = 1 AND search_index.basic = 0)`,
)
}

const raw = params.q?.trim()

if (raw) {
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 @@ -37,6 +37,30 @@ describe("render helpers", () => {
expect(html).toContain("/led_with_ic/list.json?protocol=WS2812B")
})

it("renders the extended promotional components filter and column", () => {
const html = renderD1TablePage(
"/components/list",
{
components: [
{
lcsc: 456,
mfr: "PROMO-PART",
package: "SOT-23",
is_extended_promotional: true,
},
],
},
{ is_extended_promotional: "true" },
"https://jlcsearch-proxy-staging.seve.workers.dev/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',
)
})

it("renders attributes cells inside details with a truncated summary", () => {
const html = renderD1TablePage(
"/ldos/list",
Expand Down
11 changes: 6 additions & 5 deletions lib/db/derivedtables/setup-derived-tables.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { sql } from "kysely"
import { 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 All @@ -10,35 +9,36 @@ import { buckBoostConverterTableSpec } from "lib/db/derivedtables/buck_boost_con
import { capacitorTableSpec } from "lib/db/derivedtables/capacitor"
import { dacTableSpec } from "lib/db/derivedtables/dac"
import { diodeTableSpec } from "lib/db/derivedtables/diode"
import { fpgaTableSpec } from "lib/db/derivedtables/fpga"
import { fpcConnectorTableSpec } from "lib/db/derivedtables/fpc_connector"
import { fpgaTableSpec } from "lib/db/derivedtables/fpga"
import { fuseTableSpec } from "lib/db/derivedtables/fuse"
import { gasSensorTableSpec } from "lib/db/derivedtables/gas_sensor"
import { gyroscopeTableSpec } from "lib/db/derivedtables/gyroscope"
import { headerTableSpec } from "lib/db/derivedtables/header"
import { ioExpanderTableSpec } from "lib/db/derivedtables/io_expander"
import { jstConnectorTableSpec } from "lib/db/derivedtables/jst_connector"
import { lcdDisplayTableSpec } from "lib/db/derivedtables/lcd_display"
import { ldoTableSpec } from "lib/db/derivedtables/ldo"
import { ledTableSpec } from "lib/db/derivedtables/led"
import { ledDotMatrixDisplayTableSpec } from "lib/db/derivedtables/led_dot_matrix_display"
import { ledDriverTableSpec } from "lib/db/derivedtables/led_driver"
import { ledSegmentDisplayTableSpec } from "lib/db/derivedtables/led_segment_display"
import { ledTableSpec } from "lib/db/derivedtables/led"
import { ledWithICTableSpec } from "lib/db/derivedtables/led_with_ic"
import { ldoTableSpec } from "lib/db/derivedtables/ldo"
import { microcontrollerTableSpec } from "lib/db/derivedtables/microcontroller"
import { mosfetTableSpec } from "lib/db/derivedtables/mosfet"
import { oledDisplayTableSpec } from "lib/db/derivedtables/oled_display"
import { pcieM2ConnectorTableSpec } from "lib/db/derivedtables/pcie_m2_connector"
import { potentiometerTableSpec } from "lib/db/derivedtables/potentiometer"
import { relayTableSpec } from "lib/db/derivedtables/relay"
import { resistorArrayTableSpec } from "lib/db/derivedtables/resistor_array"
import { resistorTableSpec } from "lib/db/derivedtables/resistor"
import { resistorArrayTableSpec } from "lib/db/derivedtables/resistor_array"
import { switchTableSpec } from "lib/db/derivedtables/switch"
import type { DerivedTableSpec } from "lib/db/derivedtables/types"
import { usbCConnectorTableSpec } from "lib/db/derivedtables/usb_c_connector"
import { voltageRegulatorTableSpec } from "lib/db/derivedtables/voltage_regulator"
import { wifiModuleTableSpec } from "lib/db/derivedtables/wifi_module"
import { wireToBoardConnectorTableSpec } from "lib/db/derivedtables/wire_to_board_connector"
import { clearDbClientSingleton, getDbClient } from "lib/db/get-db-client"
import type { KyselyDatabaseInstance } from "lib/db/kysely-types"

export const DERIVED_TABLES: DerivedTableSpec<any>[] = [
Expand Down Expand Up @@ -215,6 +215,7 @@ export const setupDerivedTables = async ({
} finally {
if (shouldDestroy) {
await activeDb.destroy()
clearDbClientSingleton(activeDb)
}
}
}
8 changes: 7 additions & 1 deletion lib/db/get-db-client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Database as BunDatabase } from "bun:sqlite"
import Path from "node:path"
import type { Kysely } from "kysely"
import type { BunSqliteDialect } from "kysely-bun-sqlite"
import Path from "node:path"
import type { DB } from "./generated/kysely"

let DatabaseCtor: typeof BunDatabase | undefined
Expand Down Expand Up @@ -83,6 +83,12 @@ export const getDbClient = () => {
return dbClientSingleton
}

export const clearDbClientSingleton = (client?: Kysely<DB>) => {
if (!client || dbClientSingleton === client) {
dbClientSingleton = undefined
}
}

export const getBunDatabaseClient = () => {
const Database = getDatabaseCtor()
return new Database(getResolvedDbPath())
Expand Down
11 changes: 6 additions & 5 deletions lib/db/optimizations/remove-stale-components.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { sql } from "kysely"
import type { DbOptimizationSpec } from "./types"
import type { KyselyDatabaseInstance } from "../kysely-types"
import type { DbOptimizationSpec } from "./types"

export const removeStaleComponents: DbOptimizationSpec = {
name: "remove_stale_components",
description: "Removes components that haven't been in stock for over a year",

async checkIfAdded(db: KyselyDatabaseInstance) {
// Check if any components exist with last_on_stock older than 1 year
const result = await sql<{ count: number }>`
SELECT COUNT(*) as count
// Only need to know whether a stale component exists.
const result = await sql`
SELECT 1
FROM components
WHERE last_on_stock < strftime('%s', 'now', '-1 year')
LIMIT 1
`.execute(db)

// If no stale components exist, consider this optimization as "added"
return result.rows[0]?.count === 0
return result.rows.length === 0
},

async execute(db: KyselyDatabaseInstance) {
Expand Down
24 changes: 24 additions & 0 deletions lib/util/extended-promotional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export type ExtendedPromotionalSource = {
basic?: boolean | number | string | null
preferred?: boolean | number | string | null
}

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

export const isExtendedPromotional = (
component: ExtendedPromotionalSource,
): boolean =>
isEnabledFlag(component.preferred) && !isEnabledFlag(component.basic)

export const parseBooleanFilter = (
value: boolean | string | null | undefined,
): boolean | undefined => {
if (value === true || value === false) return value
if (value === undefined || value === null || value === "") return undefined

const normalized = value.toLowerCase()
if (normalized === "true" || normalized === "1") return true
if (normalized === "false" || normalized === "0") return false
return undefined
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"better-sqlite3": "^11.7.0",
"kysely": "^0.28.3",
"kysely-codegen": "^0.17.0",
"kysely-d1": "^0.4.0",
"winterspec": "^0.0.96"
},
"peerDependencies": {
Expand Down
16 changes: 15 additions & 1 deletion routes/api/search.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { sql } from "kysely"
import {
buildSearchTokenGroups,
isExtendedPromotional,
parseBooleanFilter,
} from "lib/util/extended-promotional"
import {
type SearchTokenGroup,
buildSearchTokenGroups,
tokenizeSearchTerm,
} from "lib/util/search-token-groups"
import { withWinterSpec } from "lib/with-winter-spec"
Expand Down Expand Up @@ -50,6 +54,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 +77,14 @@ export default withWinterSpec({
if (req.query.is_preferred) {
query = query.where("preferred", "=", 1)
}
const extendedPromotionalFilter = parseBooleanFilter(
req.query.is_extended_promotional,
)
if (extendedPromotionalFilter === true) {
query = query.where(sql<boolean>`preferred = 1 AND basic = 0`)
} else if (extendedPromotionalFilter === false) {
query = query.where(sql<boolean>`NOT (preferred = 1 AND basic = 0)`)
}

const baseQuery = query
let fallbackLikeTokens: string[] = []
Expand Down Expand Up @@ -193,6 +206,7 @@ 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),
Expand Down
Loading
Loading