import 'server-only'; import { SITE_URL, NAV, MORE_NAV, FOOTER_NAV } from '@/lib/site'; import { cancerSlugsForSitemap, countActiveCancers } from '@/lib/queries/cancers'; import { geneSymbolsForSitemap, countGenes } from '@/lib/queries/genomics'; import { drugSlugsForSitemap, countDrugs } from '@/lib/queries/drugs'; import { trialNctForSitemap, countTrials } from '@/lib/queries/trials'; import { listSources } from '@/lib/queries/sources'; import { listMetrics } from '@/lib/queries/rankings'; import { biomarkerSlugsForSitemap } from '@/lib/queries/biomarkers'; import { toDate } from '@/lib/format'; /** * Chunked sitemaps: chunk 0 = static pages + sources + rankings; then one chunk per 20k entities of * each kind (cancers, genes, drugs, trials) so the index stays under the 50k-URL limit per file. */ export const PAGE = 20_000; type Kind = 'cancer' | 'gene' | 'drug' | 'trial'; export interface Chunk { id: number; kind: Kind; offset: number; } export interface Entry { url: string; lastModified: Date; changeFrequency: 'daily' | 'weekly'; priority: number; } export async function sitemapChunks(): Promise { const [c, g, d, t] = await Promise.all([countActiveCancers(), countGenes(), countDrugs(), countTrials()]); const out: Chunk[] = []; let id = 1; for (const [kind, n] of [ ['cancer', c], ['gene', g], ['drug', d], ['trial', t], ] as Array<[Kind, number]>) { for (let off = 0; off < Math.max(n, 1); off += PAGE) out.push({ id: id++, kind, offset: off }); } return out; } export async function sitemapEntries(id: number): Promise { const now = new Date(); if (id === 0) { const [sources, metrics, biomarkers] = await Promise.all([listSources(), listMetrics(), biomarkerSlugsForSitemap()]); return [ { url: SITE_URL, lastModified: now, changeFrequency: 'daily', priority: 1 }, ...NAV.map((n) => ({ url: `${SITE_URL}${n.href}`, lastModified: now, changeFrequency: 'daily' as const, priority: 0.8 })), ...MORE_NAV.map((n) => ({ url: `${SITE_URL}${n.href}`, lastModified: now, changeFrequency: 'daily' as const, priority: 0.7 })), ...FOOTER_NAV.filter((f) => !(NAV as readonly { href: string }[]).some((n) => n.href === f.href) && !(MORE_NAV as readonly { href: string }[]).some((m) => m.href === f.href)).map((n) => ({ url: `${SITE_URL}${n.href}`, lastModified: now, changeFrequency: 'weekly' as const, priority: 0.5 })), ...sources.map((s) => ({ url: `${SITE_URL}/source/${s.slug}`, lastModified: toDate(s.updated_at) ?? now, changeFrequency: 'weekly' as const, priority: 0.6 })), ...metrics.filter((m) => m.snapshot_count > 0).map((m) => ({ url: `${SITE_URL}/rankings/${m.slug}`, lastModified: now, changeFrequency: 'daily' as const, priority: 0.7 })), ...biomarkers.map((b) => ({ url: `${SITE_URL}/biomarker/${b.slug}`, lastModified: toDate(b.updated_at) ?? now, changeFrequency: 'weekly' as const, priority: 0.6 })), ]; } const chunk = (await sitemapChunks()).find((c) => c.id === id); if (!chunk) return []; switch (chunk.kind) { case 'cancer': return (await cancerSlugsForSitemap(chunk.offset, PAGE)).map((r) => ({ url: `${SITE_URL}/cancer/${r.slug}`, lastModified: toDate(r.updated_at) ?? now, changeFrequency: 'weekly' as const, priority: 0.7 })); case 'gene': return (await geneSymbolsForSitemap(chunk.offset, PAGE)).map((r) => ({ url: `${SITE_URL}/gene/${r.symbol}`, lastModified: toDate(r.updated_at) ?? now, changeFrequency: 'weekly' as const, priority: 0.5 })); case 'drug': return (await drugSlugsForSitemap(chunk.offset, PAGE)).map((r) => ({ url: `${SITE_URL}/drug/${r.slug}`, lastModified: toDate(r.updated_at) ?? now, changeFrequency: 'weekly' as const, priority: 0.5 })); case 'trial': return (await trialNctForSitemap(chunk.offset, PAGE)).map((r) => ({ url: `${SITE_URL}/trial/${r.nct_id}`, lastModified: toDate(r.updated_at) ?? now, changeFrequency: 'weekly' as const, priority: 0.4 })); } } const esc = (s: string) => s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); export function urlsetXml(entries: Entry[]): string { return `\n\n${entries.map((e) => ` ${esc(e.url)}${e.lastModified.toISOString()}${e.changeFrequency}${e.priority}`).join('\n')}\n\n`; } export function indexXml(ids: number[]): string { const now = new Date().toISOString(); return `\n\n${ids.map((id) => ` ${SITE_URL}/sitemap/${id}${now}`).join('\n')}\n\n`; }