wire /news/[slug] and /articles/[slug] to read bb.wp_imported_posts via ISR
- new src/lib/articles/legacy-source.ts: server-side Supabase reader (bb schema, anon key, RLS public_read policy) that maps imported rows to the existing Article shape, applies rewriteLegacyImageUrl for the featured image and rewriteLegacyImageUrlsInHtml across the body. - both [slug]/page.tsx routes now: try seed sampleArticles first; on miss fall back to getLegacyArticleBySlug. Related articles match via same-category recent posts when imported. - generateStaticParams pre-renders seed slugs + 50 most recent imported posts. dynamicParams=true means anything else server-renders on first request and ISR caches it for 1h (revalidate=3600). Avoids a 4577-page build. - Hardcoded builtwithrocket.new URLs in JSON-LD replaced with process.env.NEXT_PUBLIC_SITE_URL.
This commit is contained in:
139
src/lib/articles/legacy-source.ts
Normal file
139
src/lib/articles/legacy-source.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import type { Article } from "./sampleArticles";
|
||||
import { rewriteLegacyImageUrl, rewriteLegacyImageUrlsInHtml } from "@/lib/legacy-image";
|
||||
|
||||
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const SUPABASE_ANON = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||
const SCHEMA = process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb";
|
||||
|
||||
function client() {
|
||||
return createClient(SUPABASE_URL, SUPABASE_ANON, {
|
||||
db: { schema: SCHEMA as "public" },
|
||||
auth: { persistSession: false },
|
||||
});
|
||||
}
|
||||
|
||||
interface ImportedPostRow {
|
||||
wp_id: number;
|
||||
wp_slug: string;
|
||||
title: string;
|
||||
excerpt: string | null;
|
||||
content: string | null;
|
||||
author_name: string | null;
|
||||
category: string | null;
|
||||
tags: string[] | null;
|
||||
featured_image: string | null;
|
||||
featured_image_alt: string | null;
|
||||
status: string;
|
||||
wp_published_at: string | null;
|
||||
}
|
||||
|
||||
const FALLBACK_IMAGE = "/legacy/site/no_image.png";
|
||||
|
||||
function authorSlug(name: string | null): string {
|
||||
if (!name) return "staff-reporter";
|
||||
return name
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.slice(0, 60) || "staff-reporter";
|
||||
}
|
||||
|
||||
function formatDate(iso: string | null): string {
|
||||
if (!iso) return "";
|
||||
const d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "";
|
||||
return d.toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function readTime(content: string | null): string {
|
||||
if (!content) return "1 min read";
|
||||
const text = content.replace(/<[^>]+>/g, " ");
|
||||
const words = text.trim().split(/\s+/).filter(Boolean).length;
|
||||
const minutes = Math.max(1, Math.round(words / 200));
|
||||
return `${minutes} min read`;
|
||||
}
|
||||
|
||||
function rowToArticle(row: ImportedPostRow): Article {
|
||||
const image = row.featured_image
|
||||
? rewriteLegacyImageUrl(row.featured_image)
|
||||
: FALLBACK_IMAGE;
|
||||
const content = rewriteLegacyImageUrlsInHtml(row.content || "");
|
||||
const author = row.author_name || "Staff Reporter";
|
||||
return {
|
||||
slug: row.wp_slug,
|
||||
section: "news",
|
||||
title: row.title,
|
||||
excerpt: row.excerpt || "",
|
||||
category: (row.category || "NEWS").toUpperCase(),
|
||||
date: formatDate(row.wp_published_at),
|
||||
author,
|
||||
authorSlug: authorSlug(author),
|
||||
authorTitle: "Contributor",
|
||||
authorAvatar: FALLBACK_IMAGE,
|
||||
image,
|
||||
alt: row.featured_image_alt || row.title,
|
||||
readTime: readTime(row.content),
|
||||
tags: row.tags || [],
|
||||
content,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getLegacyArticleBySlug(slug: string): Promise<Article | null> {
|
||||
try {
|
||||
const { data, error } = await client()
|
||||
.from("wp_imported_posts")
|
||||
.select(
|
||||
"wp_id,wp_slug,title,excerpt,content,author_name,category,tags,featured_image,featured_image_alt,status,wp_published_at",
|
||||
)
|
||||
.eq("wp_slug", slug)
|
||||
.eq("status", "published")
|
||||
.maybeSingle();
|
||||
if (error || !data) return null;
|
||||
return rowToArticle(data as ImportedPostRow);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLegacyRelatedArticles(
|
||||
excludeSlug: string,
|
||||
category: string | null,
|
||||
count: number,
|
||||
): Promise<Article[]> {
|
||||
try {
|
||||
let q = client()
|
||||
.from("wp_imported_posts")
|
||||
.select(
|
||||
"wp_id,wp_slug,title,excerpt,content,author_name,category,tags,featured_image,featured_image_alt,status,wp_published_at",
|
||||
)
|
||||
.eq("status", "published")
|
||||
.neq("wp_slug", excludeSlug)
|
||||
.order("wp_published_at", { ascending: false })
|
||||
.limit(count);
|
||||
if (category) q = q.eq("category", category);
|
||||
const { data, error } = await q;
|
||||
if (error || !data) return [];
|
||||
return (data as ImportedPostRow[]).map(rowToArticle);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLegacyRecentSlugs(limit = 200): Promise<string[]> {
|
||||
try {
|
||||
const { data } = await client()
|
||||
.from("wp_imported_posts")
|
||||
.select("wp_slug")
|
||||
.eq("status", "published")
|
||||
.order("wp_published_at", { ascending: false })
|
||||
.limit(limit);
|
||||
return (data || []).map((r: { wp_slug: string }) => r.wp_slug);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user