fix(av-beat): skip_rewrite passthrough — Mike Nann (Writeturn) now publishes verbatim w/ noindex
Mike Nann's distribute.users record has skip_rewrite=true (set when task #19 added the column). distribute-rmp's submit route forwards the flag in its payload (task #20). BB's receive route handles it (task #21). AV Beat's receive route was missing the branch entirely AND was writing to .schema('bb') instead of 'av' — a copy-paste bug from when this file was cloned from BB. So every Mike Nann submission to AV Beat was either landing in BB's schema (wrong) or trying to and failing. Fixed in this commit: src/app/api/submission-receive/route.ts • .schema('bb') → .schema('av') (the copy-paste bug) • Added skip_rewrite branch: when payload has skip_rewrite=true, insert straight into av.wp_imported_posts with status=published, noindex=true, and the contributor's firm/contact_name as byline. Skips the native_articles → AI-rewrite path entirely. Mirrors BB's pattern exactly. Audit row still written to original_submissions. src/lib/articles/types.ts • Article.noindex?: boolean — surfaces the DB column to the renderer. src/lib/articles/legacy-source.ts • SELECT_COLS adds noindex • ImportedPostRow gains noindex field • rowToArticle propagates !!row.noindex src/app/news/[slug]/page.tsx + src/app/articles/[slug]/page.tsx • generateMetadata: when article.noindex, set robots = no-index + no-follow + nocache + googleBot no-image-index + other meta key "googlebot-news": "noindex". Matches BB's pattern. src/app/news-sitemap.xml/route.ts • Adds .or('noindex.is.null,noindex.eq.false') so passthrough rows don't pollute the Google News sitemap. src/app/rss/route.ts • Filters out a.noindex before emitting items. Mike's existing user record is already flagged (DB verified), so the moment this deploys + he submits, the next press release publishes verbatim, gets noindex on every Google/social surface, and shows up on the AV Beat news index without going through the AI rewrite job. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -110,8 +110,52 @@ export async function POST(request: NextRequest) {
|
|||||||
.trim()
|
.trim()
|
||||||
.slice(0, 240);
|
.slice(0, 240);
|
||||||
|
|
||||||
|
// Passthrough mode (skip_rewrite=true): publish the original PR body
|
||||||
|
// VERBATIM with byline = firm + noindex=true on the wp_imported_posts
|
||||||
|
// row. No AI rewrite, no Google News indexing, no sitemap. For clients
|
||||||
|
// flagged in distribute.users.skip_rewrite (e.g. Mike Nann / Writeturn).
|
||||||
|
const skipRewrite = !!body?.skip_rewrite;
|
||||||
|
if (skipRewrite) {
|
||||||
|
const wpId = Math.floor(900_000 + Math.random() * 99_000_000);
|
||||||
|
const wpSlug = `${baseSlug}-pr-${randSuffix()}`;
|
||||||
|
const byline = contributor?.contact_name || contributor?.firm_name || 'PR Submission';
|
||||||
|
const { error: wpErr } = await supabase
|
||||||
|
.schema('av')
|
||||||
|
.from('wp_imported_posts')
|
||||||
|
.insert({
|
||||||
|
wp_id: wpId,
|
||||||
|
wp_slug: wpSlug,
|
||||||
|
title,
|
||||||
|
excerpt,
|
||||||
|
content,
|
||||||
|
author_name: byline,
|
||||||
|
category: 'Industry News',
|
||||||
|
status: 'published',
|
||||||
|
wp_published_at: new Date().toISOString(),
|
||||||
|
noindex: true,
|
||||||
|
});
|
||||||
|
if (wpErr) {
|
||||||
|
return NextResponse.json({ error: wpErr.message, where: 'passthrough wp_imported_posts insert' }, { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the audit trail in original_submissions even for passthrough.
|
||||||
|
await supabase.schema('av').from('original_submissions').insert({
|
||||||
|
submission_id: submissionId,
|
||||||
|
property,
|
||||||
|
title,
|
||||||
|
body: content,
|
||||||
|
featured_image_url: featuredImage,
|
||||||
|
attachments,
|
||||||
|
contributor,
|
||||||
|
contributor_email: contributor?.email || null,
|
||||||
|
raw_payload: body,
|
||||||
|
native_article_id: null,
|
||||||
|
});
|
||||||
|
return NextResponse.json({ ok: true, slug: wpSlug, status: 'published_passthrough', noindex: true }, { status: 201 });
|
||||||
|
}
|
||||||
|
|
||||||
const { data: article, error: artErr } = await supabase
|
const { data: article, error: artErr } = await supabase
|
||||||
.schema('bb')
|
.schema('av')
|
||||||
.from('native_articles')
|
.from('native_articles')
|
||||||
.insert({
|
.insert({
|
||||||
slug,
|
slug,
|
||||||
@@ -129,7 +173,7 @@ export async function POST(request: NextRequest) {
|
|||||||
if (artErr) return NextResponse.json({ error: artErr.message, where: 'native_articles insert' }, { status: 500 });
|
if (artErr) return NextResponse.json({ error: artErr.message, where: 'native_articles insert' }, { status: 500 });
|
||||||
|
|
||||||
const { error: subErr } = await supabase
|
const { error: subErr } = await supabase
|
||||||
.schema('bb')
|
.schema('av')
|
||||||
.from('original_submissions')
|
.from('original_submissions')
|
||||||
.insert({
|
.insert({
|
||||||
submission_id: submissionId,
|
submission_id: submissionId,
|
||||||
|
|||||||
@@ -52,10 +52,15 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
|||||||
if (!article) {
|
if (!article) {
|
||||||
return { title: "Article Not Found", description: "The article you are looking for does not exist." };
|
return { title: "Article Not Found", description: "The article you are looking for does not exist." };
|
||||||
}
|
}
|
||||||
|
const noindex = !!article.noindex;
|
||||||
return {
|
return {
|
||||||
title: article.title,
|
title: article.title,
|
||||||
description: cleanExcerpt(article.excerpt),
|
description: cleanExcerpt(article.excerpt),
|
||||||
alternates: { canonical: `/articles/${article.slug}` },
|
alternates: { canonical: `/articles/${article.slug}` },
|
||||||
|
robots: noindex
|
||||||
|
? { index: false, follow: false, nocache: true, googleBot: { index: false, follow: false, noimageindex: true } }
|
||||||
|
: undefined,
|
||||||
|
other: noindex ? { "googlebot-news": "noindex" } : undefined,
|
||||||
openGraph: {
|
openGraph: {
|
||||||
type: "article",
|
type: "article",
|
||||||
title: article.title,
|
title: article.title,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export async function GET() {
|
|||||||
.select('wp_slug, title, wp_published_at, ai_rewritten_at, author_name, tags')
|
.select('wp_slug, title, wp_published_at, ai_rewritten_at, author_name, tags')
|
||||||
.eq('status', 'published')
|
.eq('status', 'published')
|
||||||
.not('ai_rewritten_at', 'is', null)
|
.not('ai_rewritten_at', 'is', null)
|
||||||
|
.or('noindex.is.null,noindex.eq.false') // exclude noindex=true (passthrough PRs)
|
||||||
.gte('wp_published_at', since)
|
.gte('wp_published_at', since)
|
||||||
.order('wp_published_at', { ascending: false })
|
.order('wp_published_at', { ascending: false })
|
||||||
.limit(1000),
|
.limit(1000),
|
||||||
|
|||||||
@@ -64,10 +64,17 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
|||||||
// contain <p>...</p>, which Next.js then HTML-escapes, surfacing as
|
// contain <p>...</p>, which Next.js then HTML-escapes, surfacing as
|
||||||
// literal `<p>` in page source.
|
// literal `<p>` in page source.
|
||||||
const cleanDesc = cleanExcerpt(article.excerpt);
|
const cleanDesc = cleanExcerpt(article.excerpt);
|
||||||
|
const noindex = !!article.noindex;
|
||||||
return {
|
return {
|
||||||
title: `${article.title} — AV Beat`,
|
title: `${article.title} — AV Beat`,
|
||||||
description: cleanDesc,
|
description: cleanDesc,
|
||||||
alternates: { canonical: `/news/${article.slug}` },
|
alternates: { canonical: `/news/${article.slug}` },
|
||||||
|
// Passthrough PR submissions (Mike Nann / Writeturn etc.) get a hard
|
||||||
|
// noindex on robots + googlebot-news so they don't pollute Google News.
|
||||||
|
robots: noindex
|
||||||
|
? { index: false, follow: false, nocache: true, googleBot: { index: false, follow: false, noimageindex: true } }
|
||||||
|
: undefined,
|
||||||
|
other: noindex ? { "googlebot-news": "noindex" } : undefined,
|
||||||
openGraph: {
|
openGraph: {
|
||||||
type: "article",
|
type: "article",
|
||||||
title: article.title,
|
title: article.title,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export async function GET() {
|
|||||||
try {
|
try {
|
||||||
const articles = await getLegacyArticlesBySection("news", 50);
|
const articles = await getLegacyArticlesBySection("news", 50);
|
||||||
items = articles
|
items = articles
|
||||||
|
.filter((a) => !a.noindex) // exclude passthrough PR submissions
|
||||||
.map((a) => {
|
.map((a) => {
|
||||||
const url = `${SITE_URL}/news/${a.slug}`;
|
const url = `${SITE_URL}/news/${a.slug}`;
|
||||||
const pubDate = a.date ? new Date(a.date).toUTCString() : new Date().toUTCString();
|
const pubDate = a.date ? new Date(a.date).toUTCString() : new Date().toUTCString();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ interface ImportedPostRow {
|
|||||||
featured_image_alt: string | null;
|
featured_image_alt: string | null;
|
||||||
status: string;
|
status: string;
|
||||||
wp_published_at: string | null;
|
wp_published_at: string | null;
|
||||||
|
noindex: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RewriteRow {
|
interface RewriteRow {
|
||||||
@@ -146,6 +147,7 @@ function rowToArticle(row: ImportedPostRow, section: Section = "news"): Article
|
|||||||
readTime: readTime(row.content),
|
readTime: readTime(row.content),
|
||||||
tags: row.tags || [],
|
tags: row.tags || [],
|
||||||
content,
|
content,
|
||||||
|
noindex: !!row.noindex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +195,7 @@ function humanBeat(beat: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SELECT_COLS =
|
const SELECT_COLS =
|
||||||
"wp_id,wp_slug,title,excerpt,content,author_name,category,tags,featured_image,featured_image_alt,status,wp_published_at";
|
"wp_id,wp_slug,title,excerpt,content,author_name,category,tags,featured_image,featured_image_alt,status,wp_published_at,noindex";
|
||||||
|
|
||||||
const REWRITE_SELECT_COLS =
|
const REWRITE_SELECT_COLS =
|
||||||
"id,slug,title,excerpt,body,category,tags,status,published_at,created_at,persona:ai_personas(slug,name,beat,avatar_url)";
|
"id,slug,title,excerpt,body,category,tags,status,published_at,created_at,persona:ai_personas(slug,name,beat,avatar_url)";
|
||||||
|
|||||||
@@ -14,4 +14,8 @@ export interface Article {
|
|||||||
readTime: string;
|
readTime: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
content: string;
|
content: string;
|
||||||
|
/** When true, render robots noindex + googlebot-news noindex and exclude
|
||||||
|
* from sitemap / RSS. Set on passthrough-mode imports from contributors
|
||||||
|
* flagged in distribute.users.skip_rewrite (e.g. Mike Nann / Writeturn). */
|
||||||
|
noindex?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user