trptk/app/api/account/downloads/route.ts
2026-02-24 17:14:07 +01:00

221 lines
6.8 KiB
TypeScript

import { NextResponse } from "next/server";
import { defineQuery } from "next-sanity";
import { getAuthToken, medusaAuthFetch } from "@/lib/auth";
import { getAllProducts } from "@/lib/medusa";
import { sanity } from "@/lib/sanity";
import { formatDisplayName } from "@/lib/variants";
const RELEASES_BY_CATALOG_NOS_QUERY = defineQuery(`
*[_type == "release" && catalogNo in $catalogNos]{
catalogNo,
name,
albumArtist,
"slug": slug.current,
albumCover,
releaseDate,
genre,
instrumentation
}
`);
const MEDUSA_URL = process.env.MEDUSA_URL ?? process.env.NEXT_PUBLIC_MEDUSA_URL ?? "http://localhost:9000";
const API_KEY = process.env.MEDUSA_PUBLISHABLE_KEY ?? process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY ?? "";
type Order = { id: string };
type RawDownload = {
product_title?: string;
variant_title?: string;
sku?: string;
download_url?: string;
};
type RawUpcoming = {
product_title?: string;
variant_title?: string;
sku?: string;
release_date?: string;
};
type SanityRelease = {
catalogNo: string;
name: string;
albumArtist: string;
slug: string;
albumCover: unknown;
releaseDate: string | null;
genre: string[] | null;
instrumentation: string[] | null;
};
export async function GET() {
const token = await getAuthToken();
if (!token) {
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
}
try {
// Fetch all customer orders
const ordersData = await medusaAuthFetch<{ orders: Order[] }>(
"/store/orders",
);
const allDownloads: RawDownload[] = [];
const allUpcoming: RawUpcoming[] = [];
// For each order, fetch digital downloads via the custom Medusa endpoint
for (const order of ordersData.orders ?? []) {
try {
const res = await fetch(
`${MEDUSA_URL}/store/order-downloads?order_id=${encodeURIComponent(order.id)}`,
{
headers: {
"x-publishable-api-key": API_KEY,
Authorization: `Bearer ${token}`,
},
},
);
if (res.ok) {
const data = await res.json();
if (data.downloads?.length) allDownloads.push(...data.downloads);
if (data.upcoming?.length) allUpcoming.push(...data.upcoming);
}
} catch {
// Non-critical — skip individual order failures
}
}
// Fetch grant-based downloads (admin-granted access without an order)
try {
const grantsData = await medusaAuthFetch<{ downloads: RawDownload[] }>(
"/store/customers/me/download-grants",
);
if (grantsData.downloads?.length) allDownloads.push(...grantsData.downloads);
} catch {
// Non-critical — grants may not exist or endpoint may not be available
}
// Deduplicate by SKU
const seenDownloads = new Set<string>();
const uniqueDownloads = allDownloads.filter((d) => {
if (!d.sku || seenDownloads.has(d.sku)) return false;
seenDownloads.add(d.sku);
return true;
});
const seenUpcoming = new Set<string>();
const uniqueUpcoming = allUpcoming.filter((u) => {
if (!u.sku || seenUpcoming.has(u.sku)) return false;
seenUpcoming.add(u.sku);
return true;
});
// Build product_title → catalogue_number map via Medusa products
const titleToCatalogNo = new Map<string, string>();
try {
const products = await getAllProducts();
for (const p of products) {
if (p.metadata?.catalogue_number && typeof p.metadata.catalogue_number === "string") {
titleToCatalogNo.set(p.title, p.metadata.catalogue_number);
}
}
} catch {
// Non-critical — cards will just miss Sanity data
}
// Query Sanity for matching releases
const catalogNos = [...new Set(titleToCatalogNo.values())];
const catalogNoToRelease = new Map<string, SanityRelease>();
if (catalogNos.length > 0) {
try {
const releases = await sanity.fetch<SanityRelease[]>(
RELEASES_BY_CATALOG_NOS_QUERY,
{ catalogNos },
);
for (const r of releases) {
catalogNoToRelease.set(r.catalogNo, r);
}
} catch {
// Non-critical — cards will render without covers
}
}
// Helper to look up Sanity data for a product title
function getSanityData(productTitle?: string) {
if (!productTitle) return null;
const catalogNo = titleToCatalogNo.get(productTitle);
if (!catalogNo) return null;
return catalogNoToRelease.get(catalogNo) ?? null;
}
// Helper to build enriched base fields from Sanity data
function enrichedBase(productTitle: string) {
const release = getSanityData(productTitle);
return {
product_title: productTitle,
albumCover: release?.albumCover ?? null,
albumArtist: release?.albumArtist ?? null,
slug: release?.slug ?? null,
catalogNo: release?.catalogNo ?? null,
releaseDate: release?.releaseDate ?? null,
genre: release?.genre ?? [],
instrumentation: release?.instrumentation ?? [],
};
}
// Group downloads by product_title, enriched with Sanity data
const downloadGroups = new Map<string, ReturnType<typeof enrichedBase> & {
formats: { variant_title: string; sku: string; download_url: string }[];
}>();
for (const dl of uniqueDownloads) {
const title = dl.product_title ?? "Unknown";
const existing = downloadGroups.get(title);
const fmt = {
variant_title: formatDisplayName(dl.sku ?? "") ?? dl.variant_title ?? "",
sku: dl.sku ?? "",
download_url: dl.download_url ?? "",
};
if (existing) {
existing.formats.push(fmt);
} else {
downloadGroups.set(title, { ...enrichedBase(title), formats: [fmt] });
}
}
// Group upcoming by product_title, enriched with Sanity data
const upcomingGroups = new Map<string, ReturnType<typeof enrichedBase> & {
release_date: string;
formats: { variant_title: string; sku: string }[];
}>();
for (const up of uniqueUpcoming) {
const title = up.product_title ?? "Unknown";
const existing = upcomingGroups.get(title);
const fmt = {
variant_title: formatDisplayName(up.sku ?? "") ?? up.variant_title ?? "",
sku: up.sku ?? "",
};
if (existing) {
existing.formats.push(fmt);
} else {
upcomingGroups.set(title, {
...enrichedBase(title),
release_date: up.release_date ?? "",
formats: [fmt],
});
}
}
return NextResponse.json({
downloads: Array.from(downloadGroups.values()),
upcoming: Array.from(upcomingGroups.values()),
});
} catch (e) {
console.error("[account:downloads]", (e as Error).message);
return NextResponse.json(
{ error: "Failed to fetch downloads" },
{ status: 500 },
);
}
}