Skip to content
Merged
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
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 6 additions & 14 deletions api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2465,13 +2467,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);
});
8 changes: 7 additions & 1 deletion lib/cards/card-database.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ function buildIndexFromApi(enCards, jaCards, rarityMap = new Map()) {
setCode,
imageUrl: buildImageUrl(card.image),
rarity: rarityMap.get(card.id) || null,
lang: "en",
});
}

Expand All @@ -277,6 +278,7 @@ function buildIndexFromApi(enCards, jaCards, rarityMap = new Map()) {
setCode,
imageUrl: buildImageUrl(card.image),
rarity: rarityMap.get(card.id) || null,
lang: "jp",
});
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand Down
25 changes: 25 additions & 0 deletions test/api-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
7 changes: 7 additions & 0 deletions test/unit-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
Loading