diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml index f562cc5a..3f230cc8 100644 --- a/.github/workflows/bun-test.yml +++ b/.github/workflows/bun-test.yml @@ -7,12 +7,20 @@ on: jobs: test: runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 60 steps: - name: Checkout code uses: actions/checkout@v4 + - name: Free runner disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/hostedtoolcache/CodeQL + df -h + - name: Setup bun uses: oven-sh/setup-bun@v2 with: @@ -21,12 +29,18 @@ jobs: - name: Install dependencies run: bun install + - name: Install cf-proxy dependencies + run: bun install + working-directory: cf-proxy + - name: Cache setup artifacts id: cache-setup uses: actions/cache@v4 with: path: ./db.sqlite3 key: db-sqlite3-${{ hashFiles('scripts/setup-*.ts') }} + restore-keys: | + db-sqlite3- - name: Run setup if cache miss if: steps.cache-setup.outputs.cache-hit != 'true' diff --git a/cf-proxy/src/components.ts b/cf-proxy/src/components.ts index 6b25a12a..d169ade1 100644 --- a/cf-proxy/src/components.ts +++ b/cf-proxy/src/components.ts @@ -8,6 +8,7 @@ export interface ComponentCatalogQueryParams { search?: string is_basic?: string is_preferred?: string + is_extended_promotional?: string } export async function queryComponentCatalog( @@ -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 @@ -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", }) diff --git a/cf-proxy/src/index.ts b/cf-proxy/src/index.ts index 36b6160c..09f14386 100644 --- a/cf-proxy/src/index.ts +++ b/cf-proxy/src/index.ts @@ -370,6 +370,7 @@ async function handleD1Search( description: row.description ?? "", stock: row.stock ?? 0, price: row.price1 ?? extractSmallQuantityPrice(row.price), + is_extended_promotional: Boolean(row.is_extended_promotional), })) const headers = new Headers({ @@ -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), })), } diff --git a/cf-proxy/src/render.ts b/cf-proxy/src/render.ts index 11ce224c..679522bc 100644 --- a/cf-proxy/src/render.ts +++ b/cf-proxy/src/render.ts @@ -147,6 +147,7 @@ const COLUMN_LABELS: Record = { in_stock: "In Stock", is_basic: "Basic", is_preferred: "Preferred", + is_extended_promotional: "Extended Promotional", capacitance_farads: "Capacitance", tolerance_fraction: "Tolerance", voltage_rating: "Voltage", @@ -546,6 +547,9 @@ const renderComponentsFilters = (
+
+ +
` diff --git a/cf-proxy/src/search.ts b/cf-proxy/src/search.ts index f1b60d3c..908f8a38 100644 --- a/cf-proxy/src/search.ts +++ b/cf-proxy/src/search.ts @@ -9,6 +9,7 @@ export interface SearchQueryParams { limit?: string is_basic?: string is_preferred?: string + is_extended_promotional?: string } interface SearchRow { @@ -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 } @@ -36,6 +38,12 @@ const isMissingFtsReadinessTable = (error: unknown): boolean => error instanceof Error && /no such table: search_index_fts_meta/i.test(error.message) +const parseBooleanFilter = (value: string | undefined): boolean | undefined => { + if (value === "true" || value === "1") return true + if (value === "false" || value === "0") return false + return undefined +} + async function isFtsSearchReady(db: Kysely): Promise { try { const result = await sql` @@ -110,6 +118,18 @@ export async function searchIndex( conditions.push(sql`search_index.preferred = 1`) } + const isExtendedPromotional = parseBooleanFilter( + params.is_extended_promotional, + ) + if (isExtendedPromotional === true) { + conditions.push(sql`search_index.preferred = 1`) + conditions.push(sql`search_index.basic = 0`) + } else if (isExtendedPromotional === false) { + conditions.push( + sql`NOT (search_index.preferred = 1 AND search_index.basic = 0)`, + ) + } + const raw = params.q?.trim() if (raw) { @@ -151,6 +171,10 @@ export async function searchIndex( search_index.price1, search_index.basic, search_index.preferred, + CASE + WHEN search_index.preferred = 1 AND search_index.basic = 0 THEN 1 + ELSE 0 + END AS is_extended_promotional, search_index.category, search_index.subcategory FROM search_index diff --git a/cf-proxy/test/render.test.ts b/cf-proxy/test/render.test.ts index 844b04a2..7218aa69 100644 --- a/cf-proxy/test/render.test.ts +++ b/cf-proxy/test/render.test.ts @@ -59,4 +59,27 @@ describe("render helpers", () => { "Feature":"Overcurrent Protection(OCP)"", ) }) + + it("renders the components extended promotional filter", () => { + const html = renderD1TablePage( + "/components/list", + { + components: [ + { + lcsc: 1, + mfr: "ABC123", + is_extended_promotional: true, + }, + ], + }, + { is_extended_promotional: "true" }, + "https://example.com/components/list?is_extended_promotional=true", + ) + + expect(html).toContain('name="is_extended_promotional"') + expect(html).toContain("Extended Promotional") + expect(html).toContain( + 'name="is_extended_promotional" value="true" checked', + ) + }) }) diff --git a/lib/db/derivedtables/setup-derived-tables.ts b/lib/db/derivedtables/setup-derived-tables.ts index caba7f36..2a382e20 100644 --- a/lib/db/derivedtables/setup-derived-tables.ts +++ b/lib/db/derivedtables/setup-derived-tables.ts @@ -191,15 +191,17 @@ export const setupDerivedTables = async ({ resetAll = false, resetTable = null, logger = () => {}, + destroyOnComplete = false, }: { db?: KyselyDatabaseInstance populate?: boolean resetAll?: boolean resetTable?: string | null logger?: Logger + destroyOnComplete?: boolean } = {}) => { const activeDb = db ?? getDbClient() - const shouldDestroy = !db + const shouldDestroy = destroyOnComplete try { for (const tableSpec of DERIVED_TABLES) { diff --git a/routes/api/search.tsx b/routes/api/search.tsx index a95d0da2..a14b2bdf 100644 --- a/routes/api/search.tsx +++ b/routes/api/search.tsx @@ -50,6 +50,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) => { @@ -72,6 +73,14 @@ export default withWinterSpec({ if (req.query.is_preferred) { query = query.where("preferred", "=", 1) } + if (typeof req.query.is_extended_promotional === "boolean") { + query = + req.query.is_extended_promotional === true + ? query.where("preferred", "=", 1).where("basic", "=", 0) + : query.where((eb) => + eb.or([eb("preferred", "!=", 1), eb("basic", "=", 1)]), + ) + } const baseQuery = query let fallbackLikeTokens: string[] = [] @@ -193,6 +202,7 @@ export default withWinterSpec({ package: c.package, is_basic: Boolean(c.basic), is_preferred: Boolean(c.preferred), + is_extended_promotional: Boolean(c.preferred) && !Boolean(c.basic), description: c.description, stock: c.stock, price: extractSmallQuantityPrice(c.price), diff --git a/routes/components/list.tsx b/routes/components/list.tsx index 640785d1..ab59825e 100644 --- a/routes/components/list.tsx +++ b/routes/components/list.tsx @@ -35,6 +35,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) => { @@ -51,6 +52,7 @@ export default withWinterSpec({ "price", "extra", "basic", + "preferred", ]) .limit(limit) .orderBy("stock", "desc") @@ -70,6 +72,14 @@ export default withWinterSpec({ if (req.query.is_preferred) { query = query.where("preferred", "=", 1) } + if (typeof req.query.is_extended_promotional === "boolean") { + query = + req.query.is_extended_promotional === true + ? query.where("preferred", "=", 1).where("basic", "=", 0) + : query.where((eb) => + eb.or([eb("preferred", "!=", 1), eb("basic", "=", 1)]), + ) + } if (req.query.search) { const search = req.query.search @@ -111,6 +121,7 @@ export default withWinterSpec({ package: c.package, is_basic: Boolean(c.basic), is_preferred: Boolean(c.preferred), + is_extended_promotional: Boolean(c.preferred) && !Boolean(c.basic), description: c.description, stock: c.stock, price: extractSmallQuantityPrice(c.price), @@ -155,6 +166,17 @@ export default withWinterSpec({ /> +
+ +
diff --git a/scripts/setup-7z.ts b/scripts/setup-7z.ts index 75f1b7c0..51a94b2e 100644 --- a/scripts/setup-7z.ts +++ b/scripts/setup-7z.ts @@ -7,10 +7,10 @@ const BINARY_NAME = "7zz" // Map of platform-arch combinations to download URLs const BINARY_URLS: Record = { - "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() { diff --git a/scripts/setup-derived-tables.ts b/scripts/setup-derived-tables.ts index 465c9ed4..3bd1e884 100644 --- a/scripts/setup-derived-tables.ts +++ b/scripts/setup-derived-tables.ts @@ -9,6 +9,7 @@ async function main() { resetAll, resetTable, logger: console.log, + destroyOnComplete: true, }) }