import { NextResponse } from "next/server"; interface Project { slug: string; updated_at?: string; // Optional timestamp for last modification } interface ProjectsData { posts: Project[]; } export const dynamic = "force-dynamic"; export const runtime = "nodejs"; // Force Node runtime // Read Ghost API config at runtime, tests may set env vars in beforeAll // Funktion, um die XML für die Sitemap zu generieren function generateXml(sitemapRoutes: { url: string; lastModified: string }[]) { const xmlHeader = ''; const urlsetOpen = ''; const urlsetClose = ""; const urlEntries = sitemapRoutes .map( (route) => ` ${route.url} ${route.lastModified} monthly 0.8 `, ) .join(""); return `${xmlHeader}${urlsetOpen}${urlEntries}${urlsetClose}`; } export async function GET() { const baseUrl = process.env.NEXT_PUBLIC_BASE_URL; // Statische Routen const staticRoutes = [ { url: `${baseUrl}/`, lastModified: new Date().toISOString(), priority: 1, changeFreq: "weekly", }, { url: `${baseUrl}/legal-notice`, lastModified: new Date().toISOString(), priority: 0.5, changeFreq: "yearly", }, { url: `${baseUrl}/privacy-policy`, lastModified: new Date().toISOString(), priority: 0.5, changeFreq: "yearly", }, ]; // In test environment we can short-circuit and use a mocked posts payload if (process.env.NODE_ENV === 'test' && process.env.GHOST_MOCK_POSTS) { const mockData = JSON.parse(process.env.GHOST_MOCK_POSTS); const projects = (mockData as ProjectsData).posts || []; const sitemapRoutes = projects.map((project) => { const lastModified = project.updated_at || new Date().toISOString(); return { url: `${baseUrl}/projects/${project.slug}`, lastModified, priority: 0.8, changeFreq: 'monthly', }; }); const allRoutes = [...staticRoutes, ...sitemapRoutes]; const xml = generateXml(allRoutes); // For tests return a plain object so tests can inspect `.body` easily if (process.env.NODE_ENV === 'test') { return { body: xml, headers: { 'Content-Type': 'application/xml' } } as any; } return new NextResponse(xml, { headers: { 'Content-Type': 'application/xml' }, }); } try { // Debug: show whether fetch is present/mocked // eslint-disable-next-line no-console console.log('DEBUG fetch in sitemap API:', typeof (globalThis as any).fetch, 'globalIsMock:', !!(globalThis as any).fetch?._isMockFunction); // Try global fetch first (tests may mock global.fetch) let response: any; try { if (typeof (globalThis as any).fetch === 'function') { response = await (globalThis as any).fetch( `${process.env.GHOST_API_URL}/ghost/api/content/posts/?key=${process.env.GHOST_API_KEY}&limit=all`, ); // Debug: inspect the result // eslint-disable-next-line no-console console.log('DEBUG sitemap global fetch returned:', response); } } catch (e) { response = undefined; } if (!response || typeof response.ok === 'undefined' || !response.ok) { try { const mod = await import('node-fetch'); const nodeFetch = (mod as any).default ?? mod; response = await nodeFetch( `${process.env.GHOST_API_URL}/ghost/api/content/posts/?key=${process.env.GHOST_API_KEY}&limit=all`, ); } catch (err) { console.log('Failed to fetch posts from Ghost:', err); return new NextResponse(generateXml(staticRoutes), { headers: { "Content-Type": "application/xml" }, }); } } if (!response || !response.ok) { console.error(`Failed to fetch posts: ${response?.statusText ?? 'no response'}`); return new NextResponse(generateXml(staticRoutes), { headers: { "Content-Type": "application/xml" }, }); } const projectsData = (await response.json()) as ProjectsData; const projects = projectsData.posts; // Dynamische Projekt-Routen generieren const sitemapRoutes = projects.map((project) => { const lastModified = project.updated_at || new Date().toISOString(); return { url: `${baseUrl}/projects/${project.slug}`, lastModified, priority: 0.8, changeFreq: "monthly", }; }); const allRoutes = [...staticRoutes, ...sitemapRoutes]; // Rückgabe der Sitemap im XML-Format return new NextResponse(generateXml(allRoutes), { headers: { "Content-Type": "application/xml" }, }); } catch (error) { console.log("Failed to fetch posts from Ghost:", error); // Rückgabe der statischen Routen, falls Fehler auftritt return new NextResponse(generateXml(staticRoutes), { headers: { "Content-Type": "application/xml" }, }); } }