129 lines
4.4 KiB
TypeScript
129 lines
4.4 KiB
TypeScript
import process from 'node:process';
|
|
import { PrismaClient } from '@prisma/client';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
function stripHtml(html: string): string {
|
|
return html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
}
|
|
|
|
async function runSeoBackfill() {
|
|
console.log('🚀 Starting SEO & Content Backfill Script for Canina Iran...\n');
|
|
|
|
// 1. Backfill Products
|
|
const products = await prisma.product.findMany();
|
|
console.log(`📦 Found ${products.length} products to check...`);
|
|
|
|
let updatedProducts = 0;
|
|
|
|
for (const product of products) {
|
|
const dataToUpdate: Record<string, any> = {};
|
|
|
|
if (!product.metaTitle || product.metaTitle.trim() === '') {
|
|
dataToUpdate.metaTitle = `${product.nameFa} (${product.packageSize} ${product.unit}) | کنینا ایران`;
|
|
}
|
|
|
|
if (!product.metaDescription || product.metaDescription.trim() === '') {
|
|
const baseDesc = product.shortDescription || stripHtml(product.description || '');
|
|
const cleanDesc = baseDesc.substring(0, 150).trim();
|
|
dataToUpdate.metaDescription = cleanDesc
|
|
? `${cleanDesc} | خرید اینترنتی با ضمانت اصالت کنینا آلمان.`
|
|
: `خرید آنلاین ${product.nameFa} با ضمانت ۱۰۰٪ اصالت آلمان و ارسال سریع در کنینا ایران.`;
|
|
}
|
|
|
|
if (!product.featuredImageAlt || product.featuredImageAlt.trim() === '') {
|
|
dataToUpdate.featuredImageAlt = `مکمل دارویی ${product.nameFa} Canina آلمان`;
|
|
}
|
|
|
|
if (!product.keywords || product.keywords.trim() === '') {
|
|
const tags = [
|
|
product.nameFa,
|
|
product.nameEn,
|
|
`مکمل ${product.suitableFor}`,
|
|
'کنینا آلمان',
|
|
'Canina',
|
|
product.categorySlug,
|
|
].filter(Boolean);
|
|
dataToUpdate.keywords = tags.join(', ');
|
|
}
|
|
|
|
if (Object.keys(dataToUpdate).length > 0) {
|
|
await prisma.product.update({
|
|
where: { id: product.id },
|
|
data: dataToUpdate,
|
|
});
|
|
updatedProducts++;
|
|
}
|
|
}
|
|
|
|
console.log(`✅ Products backfill complete: ${updatedProducts} products updated.\n`);
|
|
|
|
// 2. Backfill Blogs & Cross-Linking
|
|
const blogs = await prisma.blog.findMany();
|
|
console.log(`📰 Found ${blogs.length} blog posts to check...`);
|
|
|
|
let updatedBlogs = 0;
|
|
|
|
for (const blog of blogs) {
|
|
const dataToUpdate: Record<string, any> = {};
|
|
|
|
if (!blog.metaTitle || blog.metaTitle.trim() === '') {
|
|
dataToUpdate.metaTitle = `${blog.title} | مجله تخصصی کنینا`;
|
|
}
|
|
|
|
if (!blog.metaDescription || blog.metaDescription.trim() === '') {
|
|
const baseExcerpt = blog.excerpt || stripHtml(blog.content || '');
|
|
dataToUpdate.metaDescription = baseExcerpt.substring(0, 155).trim();
|
|
}
|
|
|
|
if (!blog.imageAlt || blog.imageAlt.trim() === '') {
|
|
dataToUpdate.imageAlt = `تصویر راهنمای تخصصی ${blog.title}`;
|
|
}
|
|
|
|
// Auto Cross-Linking: If relatedProductIds is empty, match top products by category or title keywords
|
|
if (!blog.relatedProductIds || blog.relatedProductIds.length === 0) {
|
|
const keywords = [blog.title, blog.keywords].filter(Boolean).join(' ');
|
|
const matchingProducts = await prisma.product.findMany({
|
|
where: {
|
|
OR: [
|
|
{ categorySlug: { contains: blog.categoryId || '', mode: 'insensitive' } },
|
|
{ nameFa: { contains: blog.title.split(' ')[0] || '', mode: 'insensitive' } },
|
|
{ description: { contains: blog.title.split(' ')[0] || '', mode: 'insensitive' } },
|
|
],
|
|
},
|
|
take: 3,
|
|
select: { id: true },
|
|
});
|
|
|
|
if (matchingProducts.length > 0) {
|
|
dataToUpdate.relatedProductIds = matchingProducts.map((p) => p.id);
|
|
} else {
|
|
// Fallback: pick top 2 featured products
|
|
const topProducts = await prisma.product.findMany({ take: 2, select: { id: true } });
|
|
dataToUpdate.relatedProductIds = topProducts.map((p) => p.id);
|
|
}
|
|
}
|
|
|
|
if (Object.keys(dataToUpdate).length > 0) {
|
|
await prisma.blog.update({
|
|
where: { id: blog.id },
|
|
data: dataToUpdate,
|
|
});
|
|
updatedBlogs++;
|
|
}
|
|
}
|
|
|
|
console.log(`✅ Blog posts backfill complete: ${updatedBlogs} posts updated with SEO & cross-links.\n`);
|
|
|
|
console.log('🎉 SEO Backfill and Internal Cross-Linking finished successfully!');
|
|
}
|
|
|
|
runSeoBackfill()
|
|
.catch((e) => {
|
|
console.error('❌ SEO Backfill failed:', e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|