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
3 changes: 1 addition & 2 deletions app/(ui)/card/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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 = [
Expand Down
3 changes: 1 addition & 2 deletions app/api/cards/[id]/printings/route.ts
Original file line number Diff line number Diff line change
@@ -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")`,
Expand All @@ -17,5 +16,5 @@ export async function GET(
}

const printings = await getPrintingsForCard(cardId);
return NextResponse.json(serializePrintings(printings));
return NextResponse.json(printings);
}
31 changes: 27 additions & 4 deletions lib/card/__tests__/printing-queries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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 () => {
Expand Down
9 changes: 6 additions & 3 deletions lib/card/printing-queries.ts
Original file line number Diff line number Diff line change
@@ -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<ClientPrinting[]> {
"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);
}
10 changes: 4 additions & 6 deletions lib/card/printing-types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { getPrintingsForCard } from "@/lib/card/printing-queries";

type DbPrinting = Awaited<ReturnType<typeof getPrintingsForCard>>[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"
Expand All @@ -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,
Expand All @@ -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);
}