delete sampleArticles seed; serve all sections from bb.wp_imported_posts (DB)

Eliminates 72 hardcoded rocket.new image refs in one shot. Section pages
(/gear, /technology, /show-coverage, /news) now render from
bb.wp_imported_posts via getLegacyArticlesBySection(section), which maps
each non-news section to a curated tag set (e.g. gear -> Audio, Cameras,
lighting, Blackmagic Design, monitoring; technology -> AI, Streaming,
OTT, IP, Cloud, AV; show-coverage -> NAB, IBC, BroadcastAsia).

Also:
- src/lib/articles/types.ts: Article interface lifted out of the deleted
  seed file. Type-only consumers (NewsArticleDetailClient,
  ArticleDetailClient, legacy-source) re-imported from here.
- /news (client component) now wrapped: server fetches articles from DB
  and passes to NewsPageClient. Filter UI unchanged.
- generateStaticParams on /news/[slug] and /articles/[slug] no longer
  references seed slugs; pre-renders 50 most-recent imported slugs and
  relies on dynamicParams + revalidate=3600 for the rest.
- sitemap.ts now sources article URLs from
  getLegacyRecentSitemapEntries() (up to 5000 most-recent imported posts).
  Default BASE_URL fallback updated from the rocket.new placeholder to
  bb-staging.onsethost.com.
- src/app/api/ai/article-suggestions/route.ts now pulls candidates from
  the DB (top 100 recent news) and resolves AI-returned slugs via DB
  lookup if not in the candidate window.

Inline rocket.new image refs in homepage components (ArticleFeed,
FeaturedBento) are unchanged in this commit; those are inline seed
arrays in the components, not imports of sampleArticles.
This commit is contained in:
Ryan Salazar
2026-05-08 14:31:25 +00:00
parent 08df1ad4c1
commit f1e1d5ea76
14 changed files with 483 additions and 1321 deletions

View File

@@ -1,5 +1,5 @@
import { createClient } from "@supabase/supabase-js";
import type { Article } from "./sampleArticles";
import type { Article } from "./types";
import { rewriteLegacyImageUrl, rewriteLegacyImageUrlsInHtml } from "@/lib/legacy-image";
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
@@ -30,24 +30,46 @@ interface ImportedPostRow {
const FALLBACK_IMAGE = "/legacy/site/no_image.png";
// Tag → section mapping. Source: tag distribution observed in
// bb.wp_imported_posts. Matches against any overlap (lowercased).
type Section = Article["section"];
const SECTION_TAGS: Record<Section, string[]> = {
gear: [
"Audio", "Cameras", "lighting", "Lighting", "Reviews",
"Blackmagic Design", "blackmagic", "Sony", "Canon", "ARRI", "RED",
"monitoring", "Test and Measurement",
],
technology: [
"AI", "Streaming", "OTT", "ip", "IP", "Cloud",
"AV", "audio-over-IP interfaces",
"IP Workflows", "Live Production", "Cloud Production",
"MAM", "Storage", "Encoding",
],
"show-coverage": [
"NAB", "NAB Show", "NAB 2025", "NAB 2026",
"IBC", "IBC 2025", "IBC 2026",
"BroadcastAsia", "BroadcastAsia 2026", "Broadcast Asia 2026",
"InfoComm", "MPTS",
],
news: [], // catch-all — no tag filter
};
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";
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",
});
return d.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" });
}
function readTime(content: string | null): string {
@@ -58,7 +80,7 @@ function readTime(content: string | null): string {
return `${minutes} min read`;
}
function rowToArticle(row: ImportedPostRow): Article {
function rowToArticle(row: ImportedPostRow, section: Section = "news"): Article {
const image = row.featured_image
? rewriteLegacyImageUrl(row.featured_image)
: FALLBACK_IMAGE;
@@ -66,7 +88,7 @@ function rowToArticle(row: ImportedPostRow): Article {
const author = row.author_name || "Staff Reporter";
return {
slug: row.wp_slug,
section: "news",
section,
title: row.title,
excerpt: row.excerpt || "",
category: (row.category || "NEWS").toUpperCase(),
@@ -83,13 +105,14 @@ function rowToArticle(row: ImportedPostRow): Article {
};
}
const SELECT_COLS =
"wp_id,wp_slug,title,excerpt,content,author_name,category,tags,featured_image,featured_image_alt,status,wp_published_at";
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",
)
.select(SELECT_COLS)
.eq("wp_slug", slug)
.eq("status", "published")
.maybeSingle();
@@ -108,9 +131,7 @@ export async function getLegacyRelatedArticles(
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",
)
.select(SELECT_COLS)
.eq("status", "published")
.neq("wp_slug", excludeSlug)
.order("wp_published_at", { ascending: false })
@@ -118,7 +139,32 @@ export async function getLegacyRelatedArticles(
if (category) q = q.eq("category", category);
const { data, error } = await q;
if (error || !data) return [];
return (data as ImportedPostRow[]).map(rowToArticle);
return (data as ImportedPostRow[]).map((r) => rowToArticle(r));
} catch {
return [];
}
}
/** Section-filtered listing. `news` returns all; gear/technology/show-coverage
* filter by tag overlap against SECTION_TAGS. */
export async function getLegacyArticlesBySection(
section: Section,
limit = 200,
): Promise<Article[]> {
try {
let q = client()
.from("wp_imported_posts")
.select(SELECT_COLS)
.eq("status", "published")
.order("wp_published_at", { ascending: false })
.limit(limit);
const tags = SECTION_TAGS[section];
if (tags.length > 0) {
q = q.overlaps("tags", tags);
}
const { data, error } = await q;
if (error || !data) return [];
return (data as ImportedPostRow[]).map((r) => rowToArticle(r, section));
} catch {
return [];
}
@@ -137,3 +183,22 @@ export async function getLegacyRecentSlugs(limit = 200): Promise<string[]> {
return [];
}
}
export async function getLegacyRecentSitemapEntries(
limit = 5000,
): Promise<{ slug: string; date: string }[]> {
try {
const { data } = await client()
.from("wp_imported_posts")
.select("wp_slug,wp_published_at")
.eq("status", "published")
.order("wp_published_at", { ascending: false })
.limit(limit);
return (data || []).map((r: { wp_slug: string; wp_published_at: string | null }) => ({
slug: r.wp_slug,
date: r.wp_published_at || new Date().toISOString(),
}));
} catch {
return [];
}
}