From 335f421b492a2f5938a4339816d6cc3291c36be8 Mon Sep 17 00:00:00 2001 From: Pyronewbic Date: Mon, 25 May 2026 18:25:02 +0530 Subject: [PATCH 1/2] fix: init card database before accepting connections Move eBay token + card DB init before app.listen so Cloud Run only routes traffic after data is ready. Eliminates the 503 window on cold start. --- api.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/api.js b/api.js index af09ac9..751b5e9 100644 --- a/api.js +++ b/api.js @@ -2465,13 +2465,3 @@ process.on("uncaughtException", (err) => { logError("uncaughtException", err.message, err.stack?.split("\n")[1]?.trim()); setTimeout(() => process.exit(1), 1000); }); - -process.on("unhandledRejection", (reason) => { - const msg = reason instanceof Error ? reason.message : String(reason); - logError("unhandledRejection", msg, reason instanceof Error ? reason.stack?.split("\n")[1]?.trim() : ""); -}); - -process.on("uncaughtException", (err) => { - logError("uncaughtException", err.message, err.stack?.split("\n")[1]?.trim()); - setTimeout(() => process.exit(1), 1000); -}); From dac577a54d6788bb5b9d3800e398b721365aed71 Mon Sep 17 00:00:00 2001 From: Pyronewbic Date: Mon, 25 May 2026 18:43:48 +0530 Subject: [PATCH 2/2] feat: add lang filter to /api/sets (en, jp, all) Tag cards with lang field at index build time. Sets get lang field (en, jp, both) based on card composition. /api/sets?lang=en returns sets with English cards, ?lang=jp for Japanese, no param for all. --- CLAUDE.md | 1 + api.js | 10 ++++++---- lib/cards/card-database.js | 8 +++++++- test/api-test.js | 25 +++++++++++++++++++++++++ test/unit-test.js | 7 +++++++ 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 19245f9..c102ad0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,7 @@ Casecomp — Pokemon TCG card research tool. API at api.casecomp.xyz (Cloud Run - **No Co-Authored-By lines** in commits. No "Generated with Claude Code" in PR descriptions or any public text. - **Branch flow:** push to dev or main → CI runs → deploy on main push. - **Commits:** concise message, no attribution trailers. +- **Before each commit:** consider whether new unit, API, or smoke tests are needed for the change. Add them in the same commit. - **PR template:** .github/PULL_REQUEST_TEMPLATE.md ## Code style diff --git a/api.js b/api.js index 751b5e9..3d3ac92 100644 --- a/api.js +++ b/api.js @@ -890,10 +890,12 @@ app.get("/api/autocomplete", requireCardDb, (req, res) => { // GET /api/sets app.get("/api/sets", requireCardDb, (req, res) => { - const sets = getAllSets(); - const era = req.query.era; - const filtered = era ? sets.filter(s => s.era === era) : sets; - res.json({ sets: filtered, count: filtered.length }); + let sets = getAllSets(); + if (req.query.era) sets = sets.filter(s => s.era === req.query.era); + const lang = req.query.lang; + if (lang === "en") sets = sets.filter(s => s.lang === "en" || s.lang === "both"); + else if (lang === "jp") sets = sets.filter(s => s.lang === "jp" || s.lang === "both"); + res.json({ sets, count: sets.length }); }); // GET /api/sets/:setCode diff --git a/lib/cards/card-database.js b/lib/cards/card-database.js index 39b045d..9a7f73d 100644 --- a/lib/cards/card-database.js +++ b/lib/cards/card-database.js @@ -262,6 +262,7 @@ function buildIndexFromApi(enCards, jaCards, rarityMap = new Map()) { setCode, imageUrl: buildImageUrl(card.image), rarity: rarityMap.get(card.id) || null, + lang: "en", }); } @@ -277,6 +278,7 @@ function buildIndexFromApi(enCards, jaCards, rarityMap = new Map()) { setCode, imageUrl: buildImageUrl(card.image), rarity: rarityMap.get(card.id) || null, + lang: "jp", }); } @@ -428,10 +430,12 @@ export function getAllSets() { if (!card.setCode) continue; const code = card.setCode.toLowerCase(); if (!setMap.has(code)) { - setMap.set(code, { count: 0, bestImage: null, bestNum: 0 }); + setMap.set(code, { count: 0, enCount: 0, jpCount: 0, bestImage: null, bestNum: 0 }); } const entry = setMap.get(code); entry.count++; + if (card.lang === "en") entry.enCount++; + else if (card.lang === "jp") entry.jpCount++; if (card.imageUrl) { const num = parseInt(card.localId, 10) || 0; if (num > entry.bestNum) { @@ -446,11 +450,13 @@ export function getAllSets() { const meta = tcgdexSetMeta.get(code); const officialCards = meta?.officialCards ?? null; const metaTotal = meta?.totalCards ?? null; + const lang = data.enCount > 0 && data.jpCount > 0 ? "both" : data.enCount > 0 ? "en" : "jp"; sets.push({ setCode: code, name: resolveSetName(code), era: deriveEra(code), totalCards: data.count, + lang, logo: meta?.logo || null, officialCards, secretCards: officialCards != null && metaTotal != null ? metaTotal - officialCards : null, diff --git a/test/api-test.js b/test/api-test.js index 59f3bb0..e845b70 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -922,6 +922,31 @@ async function run() { assert(res.status === 404, `expected 404, got ${res.status}`); }); + await test("GET /api/sets includes lang field on each set", async () => { + const { body } = await jsonNoAuth("/api/sets"); + for (const s of body.sets) { + assert(["en", "jp", "both"].includes(s.lang), `unexpected lang ${s.lang} on set ${s.setCode}`); + } + }); + + await test("GET /api/sets?lang=en filters to EN sets", async () => { + const { body: all } = await jsonNoAuth("/api/sets"); + const { body: en } = await jsonNoAuth("/api/sets?lang=en"); + assert(en.count <= all.count, "en count should be <= total"); + for (const s of en.sets) { + assert(s.lang === "en" || s.lang === "both", `expected en/both, got ${s.lang}`); + } + }); + + await test("GET /api/sets?lang=jp filters to JP sets", async () => { + const { body: all } = await jsonNoAuth("/api/sets"); + const { body: jp } = await jsonNoAuth("/api/sets?lang=jp"); + assert(jp.count <= all.count, "jp count should be <= total"); + for (const s of jp.sets) { + assert(s.lang === "jp" || s.lang === "both", `expected jp/both, got ${s.lang}`); + } + }); + // ── Price trend ── console.log("\n\x1b[1m=== price trend ===\x1b[0m"); diff --git a/test/unit-test.js b/test/unit-test.js index 6c41403..38a62a6 100644 --- a/test/unit-test.js +++ b/test/unit-test.js @@ -1070,6 +1070,13 @@ test("getAllSets: returns empty array when no cards loaded", () => { eq(Array.isArray(sets), true); }); +test("getAllSets: lang field is en, jp, or both", () => { + const sets = getAllSets(); + for (const s of sets) { + eq(["en", "jp", "both", undefined].includes(s.lang), true); + } +}); + test("getSetWithCards: returns null for nonexistent set", () => { eq(getSetWithCards("zzz999"), null); });