initial commit: rocket.new export of broadcastbeat
This commit is contained in:
126
src/app/api/content/audit/route.ts
Normal file
126
src/app/api/content/audit/route.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { createClient } from '@/lib/supabase/server';
|
||||
|
||||
const PRESS_RELEASE_PATTERNS = [
|
||||
/for immediate release/i,
|
||||
/is pleased to announce/i,
|
||||
/announced today/i,
|
||||
/press release/i,
|
||||
/contact:\s/i,
|
||||
];
|
||||
|
||||
function isPressRelease(content: string): boolean {
|
||||
return PRESS_RELEASE_PATTERNS.filter((p) => p.test(content)).length >= 2;
|
||||
}
|
||||
|
||||
function countWords(text: string): number {
|
||||
return (text || '').split(/\s+/).filter(Boolean).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/content/audit
|
||||
* Returns a full content audit across all published articles.
|
||||
*/
|
||||
export async function GET() {
|
||||
try {
|
||||
const supabase = await createClient();
|
||||
|
||||
const [{ data: imported }, { data: native }] = await Promise.all([
|
||||
supabase
|
||||
.from('wp_imported_posts')
|
||||
.select('id, title, content, status, featured_image, meta_title, meta_description, created_at')
|
||||
.order('created_at', { ascending: false }),
|
||||
supabase
|
||||
.from('native_articles')
|
||||
.select('id, title, content, status, featured_image, meta_title, meta_description, created_at')
|
||||
.order('created_at', { ascending: false }),
|
||||
]);
|
||||
|
||||
const allArticles = [
|
||||
...(imported || []).map((a) => ({ ...a, source: 'imported' })),
|
||||
...(native || []).map((a) => ({ ...a, source: 'native' })),
|
||||
];
|
||||
|
||||
const published = allArticles.filter((a) => a.status === 'published');
|
||||
const total = allArticles.length;
|
||||
const totalPublished = published.length;
|
||||
|
||||
// Thin content: under 300 words
|
||||
const thinContent = published.filter((a) => countWords(a.content) < 300);
|
||||
|
||||
// Missing featured image
|
||||
const missingFeaturedImage = published.filter(
|
||||
(a) => !a.featured_image || a.featured_image === ''
|
||||
);
|
||||
|
||||
// Missing SEO meta
|
||||
const missingSeoTitle = published.filter((a) => !a.meta_title || a.meta_title === '');
|
||||
const missingSeoDescription = published.filter(
|
||||
(a) => !a.meta_description || a.meta_description === ''
|
||||
);
|
||||
const missingSeoAny = published.filter(
|
||||
(a) => !a.meta_title || !a.meta_description || a.meta_title === '' || a.meta_description === ''
|
||||
);
|
||||
|
||||
// Press releases (unedited)
|
||||
const pressReleases = published.filter((a) => isPressRelease(a.content || ''));
|
||||
|
||||
// Word count distribution
|
||||
const wordCounts = published.map((a) => countWords(a.content));
|
||||
const avgWordCount =
|
||||
wordCounts.length > 0
|
||||
? Math.round(wordCounts.reduce((s, c) => s + c, 0) / wordCounts.length)
|
||||
: 0;
|
||||
|
||||
// Launch checklist
|
||||
const launchChecklist = {
|
||||
zeroPagesWithMissingFeaturedImages: missingFeaturedImage.length === 0,
|
||||
zeroPagesWithThinContent: thinContent.length === 0,
|
||||
zeroPagesMissingSeoMeta: missingSeoAny.length === 0,
|
||||
zeroUnrewrittenPressReleases: pressReleases.length === 0,
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
summary: {
|
||||
totalArticles: total,
|
||||
totalPublished,
|
||||
thinContentCount: thinContent.length,
|
||||
missingFeaturedImageCount: missingFeaturedImage.length,
|
||||
missingSeoTitleCount: missingSeoTitle.length,
|
||||
missingSeoDescriptionCount: missingSeoDescription.length,
|
||||
missingSeoAnyCount: missingSeoAny.length,
|
||||
pressReleaseCount: pressReleases.length,
|
||||
averageWordCount: avgWordCount,
|
||||
},
|
||||
launchChecklist,
|
||||
details: {
|
||||
thinContent: thinContent.map((a) => ({
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
wordCount: countWords(a.content),
|
||||
source: a.source,
|
||||
})),
|
||||
missingFeaturedImage: missingFeaturedImage.map((a) => ({
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
source: a.source,
|
||||
})),
|
||||
missingSeoMeta: missingSeoAny.map((a) => ({
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
missingTitle: !a.meta_title,
|
||||
missingDescription: !a.meta_description,
|
||||
source: a.source,
|
||||
})),
|
||||
pressReleases: pressReleases.map((a) => ({
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
source: a.source,
|
||||
created_at: a.created_at,
|
||||
})),
|
||||
},
|
||||
});
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: err.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user