diff --git a/app/(ui)/card/[slug]/page.tsx b/app/(ui)/card/[slug]/page.tsx index 628367a..b4da8ad 100644 --- a/app/(ui)/card/[slug]/page.tsx +++ b/app/(ui)/card/[slug]/page.tsx @@ -8,7 +8,6 @@ import { OracleText } from "@/app/_components/card/oracle-text"; import Link from "@/app/_components/link"; import { PrintingCarousel } from "@/app/_components/builder/printing-carousel"; import { getPrintingsForCard } from "@/lib/card/printing-queries"; -import { serializePrintings } from "@/lib/card/printing-types"; import { getCardBySlug, getDecksContainingCard } from "@/lib/card/queries"; import { getSession } from "@/lib/auth/session"; @@ -62,7 +61,7 @@ async function CardContent({ slug, from }: { slug: string; from: string | undefi const card = await getCardBySlug(slug); if (!card) notFound(); - const printings = serializePrintings(await getPrintingsForCard(card.id)); + const printings = await getPrintingsForCard(card.id); const back = resolveBackLink(from); const metaItems = [ diff --git a/app/api/cards/[id]/printings/route.ts b/app/api/cards/[id]/printings/route.ts index 81b8005..65d22bd 100644 --- a/app/api/cards/[id]/printings/route.ts +++ b/app/api/cards/[id]/printings/route.ts @@ -1,6 +1,5 @@ import { type NextRequest, NextResponse } from "next/server"; import { getPrintingsForCard } from "@/lib/card/printing-queries"; -import { serializePrintings } from "@/lib/card/printing-types"; // Public read endpoint — printings are reference data with no per-user gating. // The underlying query is `'use cache'` with `cacheTag` + `cacheLife("hours")`, @@ -17,5 +16,5 @@ export async function GET( } const printings = await getPrintingsForCard(cardId); - return NextResponse.json(serializePrintings(printings)); + return NextResponse.json(printings); } diff --git a/lib/card/__tests__/printing-queries.test.ts b/lib/card/__tests__/printing-queries.test.ts index ae6dbe6..3fc390a 100644 --- a/lib/card/__tests__/printing-queries.test.ts +++ b/lib/card/__tests__/printing-queries.test.ts @@ -24,10 +24,20 @@ beforeEach(() => { }); describe("getPrintingsForCard", () => { - it("returns printings for the given cardId", async () => { + it("returns printings for the given cardId, coercing Decimal prices to number", async () => { const printings = [ - { id: 10, cardId: 5, setCode: "MH3", setName: "Modern Horizons 3" }, - { id: 9, cardId: 5, setCode: "MH2", setName: "Modern Horizons 2" }, + { + id: 10, + cardId: 5, + setCode: "MH3", + setName: "Modern Horizons 3", + priceUsd: { toString: () => "12.50" }, // Prisma Decimal-like + priceUsdFoil: null, + priceUsdEtched: null, + priceEur: null, + priceEurFoil: null, + priceEurEtched: null, + }, ]; mockPrintingFindMany.mockResolvedValue(printings as never); @@ -37,7 +47,20 @@ describe("getPrintingsForCard", () => { where: { cardId: 5 }, orderBy: { id: "desc" }, }); - expect(result).toEqual(printings); + expect(result).toEqual([ + { + id: 10, + cardId: 5, + setCode: "MH3", + setName: "Modern Horizons 3", + priceUsd: 12.5, + priceUsdFoil: null, + priceUsdEtched: null, + priceEur: null, + priceEurFoil: null, + priceEurEtched: null, + }, + ]); }); it("returns an empty array when the card has no printings", async () => { diff --git a/lib/card/printing-queries.ts b/lib/card/printing-queries.ts index aee7df8..ac8586d 100644 --- a/lib/card/printing-queries.ts +++ b/lib/card/printing-queries.ts @@ -1,15 +1,18 @@ import { cacheLife, cacheTag } from "next/cache"; import { prisma } from "@/lib/db"; +import { serializePrintings, type ClientPrinting } from "@/lib/card/printing-types"; // Returns all printings for a card ordered by id desc (newest ingested first). -// Cached aggressively — printings rarely change. -export async function getPrintingsForCard(cardId: number) { +// Cached aggressively — printings rarely change. Decimal price columns are coerced +// to number *inside* the cache boundary; Prisma Decimal can't cross `'use cache'`. +export async function getPrintingsForCard(cardId: number): Promise { "use cache"; cacheLife("hours"); cacheTag(`card-printings:${cardId}`); - return prisma.printing.findMany({ + const rows = await prisma.printing.findMany({ where: { cardId }, orderBy: { id: "desc" }, }); + return serializePrintings(rows); } diff --git a/lib/card/printing-types.ts b/lib/card/printing-types.ts index 283c48f..72444e7 100644 --- a/lib/card/printing-types.ts +++ b/lib/card/printing-types.ts @@ -1,10 +1,8 @@ -import { getPrintingsForCard } from "@/lib/card/printing-queries"; - -type DbPrinting = Awaited>[number]; +import type { Printing } from "@/lib/generated/prisma/browser"; // Client-safe shape of a Printing — Decimal columns coerced to number for serialization. export type ClientPrinting = Omit< - DbPrinting, + Printing, | "priceUsd" | "priceUsdFoil" | "priceUsdEtched" @@ -20,7 +18,7 @@ export type ClientPrinting = Omit< priceEurEtched: number | null; }; -function serializePrinting(printing: DbPrinting): ClientPrinting { +function serializePrinting(printing: Printing): ClientPrinting { return { ...printing, priceUsd: printing.priceUsd ? Number(printing.priceUsd) : null, @@ -32,6 +30,6 @@ function serializePrinting(printing: DbPrinting): ClientPrinting { }; } -export function serializePrintings(printings: readonly DbPrinting[]): ClientPrinting[] { +export function serializePrintings(printings: readonly Printing[]): ClientPrinting[] { return printings.map(serializePrinting); }