feat: implement centralized taxonomy for programmatic SEO
- Create taxonomy system with oils.json (5 oils) and concerns.json (9 concerns) - Migrate 10 content files to new data/content/oil-for-concern/ structure - Add scripts: generate-urls.js, validate-taxonomy.js, migrate-content.js - Update dataLoader.ts to use centralized taxonomy - Generate 40 URLs (10 pairs × 4 languages) - Create sitemap-programmatic.xml for SEO - Update by-oil and by-concern directory pages
This commit is contained in:
106
scripts/migrate-content.js
Normal file
106
scripts/migrate-content.js
Normal file
@@ -0,0 +1,106 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const oils = require('../data/taxonomy/oils.json');
|
||||
const concerns = require('../data/taxonomy/concerns.json');
|
||||
|
||||
const LOCALES = ['sr', 'en', 'de', 'fr'];
|
||||
|
||||
const legacyFiles = [
|
||||
{ file: 'najbolje-arganovo-ulje-za-bore.json', oil: 'argan-oil', concern: 'wrinkles' },
|
||||
{ file: 'najbolje-arganovo-ulje-za-suvu-kozu.json', oil: 'argan-oil', concern: 'dry-skin' },
|
||||
{ file: 'najbolje-arganovo-ulje-za-podocnjake.json', oil: 'argan-oil', concern: 'under-eye-bags' },
|
||||
{ file: 'najbolje-ulje-divlje-ruze-za-bore.json', oil: 'rosehip-oil', concern: 'wrinkles' },
|
||||
{ file: 'najbolje-ulje-divlje-ruze-za-tamne-pjege.json', oil: 'rosehip-oil', concern: 'dark-spots' },
|
||||
{ file: 'najbolje-ulje-divlje-ruze-za-oziljke-od-akni.json', oil: 'rosehip-oil', concern: 'acne-scars' },
|
||||
{ file: 'najbolje-jojoba-ulje-za-akne.json', oil: 'jojoba-oil', concern: 'acne' },
|
||||
{ file: 'najbolje-jojoba-ulje-za-masnu-kozu.json', oil: 'jojoba-oil', concern: 'oily-skin' },
|
||||
{ file: 'najbolje-ulje-pasjeg-trna-za-hiperpigmentaciju.json', oil: 'sea-buckthorn-oil', concern: 'hyperpigmentation' },
|
||||
{ file: 'najbolje-ulje-slatkog-badema-za-osetljivu-kozu.json', oil: 'sweet-almond-oil', concern: 'sensitive-skin' }
|
||||
];
|
||||
|
||||
function extractContent(oldData) {
|
||||
return {
|
||||
schema: {
|
||||
version: "1.0.0",
|
||||
type: "oil-for-concern",
|
||||
oilId: oldData.oilSlug,
|
||||
concernId: oldData.concernSlug
|
||||
},
|
||||
content: {
|
||||
whyThisWorks: oldData.whyThisWorks,
|
||||
keyBenefits: oldData.keyBenefits,
|
||||
howToApply: oldData.howToApply,
|
||||
expectedResults: oldData.expectedResults,
|
||||
timeframe: oldData.timeframe
|
||||
},
|
||||
metadata: {
|
||||
productsToShow: oldData.productsToShow || [],
|
||||
complementaryIngredients: oldData.complementaryIngredients || [],
|
||||
customerResults: (oldData.customerResults || []).map(r => ({
|
||||
quote: r.quote,
|
||||
name: r.name,
|
||||
age: r.age,
|
||||
skinType: r.skinType,
|
||||
timeframe: r.timeframe
|
||||
})),
|
||||
faqs: (oldData.faqs || []).map(f => ({
|
||||
question: f.question,
|
||||
answer: f.answer
|
||||
})),
|
||||
seoKeywords: oldData.seoKeywords || {},
|
||||
relatedPages: oldData.relatedPages || {
|
||||
otherOilsForSameConcern: [],
|
||||
sameOilForOtherConcerns: []
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function migrateFiles() {
|
||||
const sourceDir = path.join(__dirname, '../data/oil-for-concern');
|
||||
const targetDir = path.join(__dirname, '../data/content/oil-for-concern');
|
||||
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
}
|
||||
|
||||
let migrated = 0;
|
||||
let errors = [];
|
||||
|
||||
for (const mapping of legacyFiles) {
|
||||
const sourcePath = path.join(sourceDir, mapping.file);
|
||||
const targetFilename = `${mapping.oil}-${mapping.concern}.json`;
|
||||
const targetPath = path.join(targetDir, targetFilename);
|
||||
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
errors.push(`Source file not found: ${mapping.file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const oldData = JSON.parse(fs.readFileSync(sourcePath, 'utf8'));
|
||||
const newData = extractContent(oldData);
|
||||
|
||||
fs.writeFileSync(targetPath, JSON.stringify(newData, null, 2));
|
||||
console.log(`✓ Migrated: ${mapping.file} → ${targetFilename}`);
|
||||
migrated++;
|
||||
} catch (err) {
|
||||
errors.push(`Error migrating ${mapping.file}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n=== MIGRATION COMPLETE ===`);
|
||||
console.log(`Migrated: ${migrated}/${legacyFiles.length} files`);
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.log(`\nErrors (${errors.length}):`);
|
||||
errors.forEach(e => console.log(` - ${e}`));
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
migrateFiles();
|
||||
}
|
||||
|
||||
module.exports = { extractContent, migrateFiles };
|
||||
Reference in New Issue
Block a user