diff --git a/app/package.json b/app/package.json index e75b00f..6cccba5 100644 --- a/app/package.json +++ b/app/package.json @@ -20,6 +20,7 @@ "@sveltejs/kit": "^2.57.0", "@sveltejs/vite-plugin-svelte": "^7.0.0", "@testing-library/svelte": "^5.3.1", + "@types/node": "^25.6.0", "jsdom": "^29.0.2", "svelte": "^5.55.2", "svelte-check": "^4.4.6", diff --git a/app/src/routes/[...slug]/+page.ts b/app/src/routes/[...slug]/+page.ts index c7b2f51..9272237 100644 --- a/app/src/routes/[...slug]/+page.ts +++ b/app/src/routes/[...slug]/+page.ts @@ -1,21 +1,78 @@ -import { error, redirect } from '@sveltejs/kit'; -import { parseLegacyUrl, canonicalPostPath } from '$lib/url/legacy'; -import type { PageLoad } from './$types'; +import { error, redirect } from '@sveltejs/kit' +import { parseLegacyUrl, canonicalPostPath } from '$lib/url/legacy' +import type { EntryGenerator, PageLoad } from './$types' +import { browser } from '$app/environment' + +export const ssr = true +export const prerender = true +export const trailingSlash = 'always' + +interface SnapshotIndex { + posts: Array<{ slug: string; lang: string; title: string }> +} + +interface PostJson { + slug: string + event_id: string + created_at: number + published_at: number + title: string + summary: string + lang: string + cover_image: { url: string; alt?: string; width?: number; height?: number; mime?: string } | null + content_markdown: string + tags: string[] + naddr: string + habla_url: string + translations: Array<{ lang: string; slug: string; title: string }> +} + +let cachedIndex: SnapshotIndex | undefined +async function readIndex(): Promise { + if (cachedIndex) return cachedIndex + const fs = await import('node:fs/promises') + const path = await import('node:path') + const dir = path.resolve('../snapshot/output') + const text = await fs.readFile(path.join(dir, 'index.json'), 'utf-8') + cachedIndex = JSON.parse(text) as SnapshotIndex + return cachedIndex +} + +async function readPost(slug: string): Promise { + try { + const fs = await import('node:fs/promises') + const path = await import('node:path') + const dir = path.resolve('../snapshot/output') + const text = await fs.readFile(path.join(dir, 'posts', `${slug}.json`), 'utf-8') + return JSON.parse(text) as PostJson + } catch { + return undefined + } +} + +export const entries: EntryGenerator = async () => { + const idx = await readIndex() + return idx.posts.map((p) => ({ slug: p.slug })) +} export const load: PageLoad = async ({ url }) => { - const pathname = url.pathname; + const pathname = url.pathname - // Legacy-Form /YYYY/MM/DD/.html/ → Redirect auf // - const legacyDtag = parseLegacyUrl(pathname); - if (legacyDtag) { - throw redirect(301, canonicalPostPath(legacyDtag)); - } + const legacyDtag = parseLegacyUrl(pathname) + if (legacyDtag) { + throw redirect(301, canonicalPostPath(legacyDtag)) + } - // Kanonisch: // — erster Segment des Pfades. - const segments = pathname.replace(/^\/+|\/+$/g, '').split('/'); - if (segments.length !== 1 || !segments[0]) { - throw error(404, 'Seite nicht gefunden'); - } + const segments = pathname.replace(/^\/+|\/+$/g, '').split('/') + if (segments.length !== 1 || !segments[0]) { + throw error(404, 'Seite nicht gefunden') + } + const dtag = decodeURIComponent(segments[0]) - return { dtag: decodeURIComponent(segments[0]) }; -}; + if (!browser) { + const snapshot = await readPost(dtag) + if (snapshot) return { dtag, snapshot } + } + + return { dtag, snapshot: null } +}