227 lines
8.2 KiB
JavaScript
227 lines
8.2 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
const { Client, Databases, Storage, ID, Query } = require('node-appwrite');
|
||
const { InputFile } = require('node-appwrite/file');
|
||
require('dotenv').config({ path: '.env.local' });
|
||
const { applyTablesDbShim } = require('../src/lib/appwrite-tables-shim');
|
||
|
||
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://nyc.cloud.appwrite.io/v1';
|
||
const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '6aef63d9002c07371b40';
|
||
const API_KEY = process.env.APPWRITE_API_KEY;
|
||
const DB_ID = process.env.APPWRITE_DATABASE_ID || 'almeira';
|
||
const BUCKET_ID = process.env.NEXT_PUBLIC_APPWRITE_MEDIA_BUCKET_ID || 'media_bucket';
|
||
const NOTICIA_TYPE_ID = '69c2b91400362d5d52b6';
|
||
|
||
const WP_DATA_PATH = path.join(__dirname, 'wp_extracted_data.json');
|
||
const IMG_DIR = 'C:/tmp/wp_images';
|
||
|
||
const client = new Client().setEndpoint(ENDPOINT).setProject(PROJECT_ID).setKey(API_KEY);
|
||
applyTablesDbShim(client);
|
||
const databases = new Databases(client);
|
||
const storage = new Storage(client);
|
||
|
||
function safeFileKey(relPath) {
|
||
return relPath.replace(/[^a-zA-Z0-9._-]/g, '_');
|
||
}
|
||
|
||
function stripHtml(html) {
|
||
if (!html) return '';
|
||
return html
|
||
.replace(/<[^>]*>?/gm, ' ')
|
||
.replace(/ /gi, ' ')
|
||
.replace(/&/gi, '&')
|
||
.replace(/"/gi, '"')
|
||
.replace(/“|”/gi, '"')
|
||
.replace(/‘|’/gi, "'")
|
||
.replace(/–|—/gi, '-')
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
}
|
||
|
||
function formatWpContent(raw) {
|
||
if (!raw) return '<p></p>';
|
||
let content = raw.trim();
|
||
if (!content.includes('<p') && !content.includes('<div') && !content.includes('<h')) {
|
||
content = content
|
||
.split(/\r?\n\r?\n/)
|
||
.map((p) => p.trim())
|
||
.filter(Boolean)
|
||
.map((p) => `<p>${p.replace(/\r?\n/g, '<br/>')}</p>`)
|
||
.join('\n');
|
||
}
|
||
return content.substring(0, 95000);
|
||
}
|
||
|
||
async function runPool(items, concurrency, fn) {
|
||
let idx = 0;
|
||
const workers = Array.from({ length: concurrency }, async () => {
|
||
while (true) {
|
||
const current = idx++;
|
||
if (current >= items.length) break;
|
||
await fn(items[current], current);
|
||
}
|
||
});
|
||
await Promise.all(workers);
|
||
}
|
||
|
||
async function run() {
|
||
const wpData = JSON.parse(fs.readFileSync(WP_DATA_PATH, 'utf8'));
|
||
|
||
console.log('===================================================================');
|
||
console.log('🚀 IMPORTACIÓN HISTÓRICA DE WORDPRESS A APPWRITE CLOUD');
|
||
console.log(`Categorías: ${wpData.categories.length} | Posts históricos: ${wpData.posts.length}`);
|
||
console.log('===================================================================\n');
|
||
|
||
// 1. Cargar categorías existentes
|
||
console.log('1️⃣ Sincronizando categorías de WordPress...');
|
||
const existingCatSlugs = new Map();
|
||
let cursor = null;
|
||
while (true) {
|
||
const q = [Query.limit(100)];
|
||
if (cursor) q.push(Query.cursorAfter(cursor));
|
||
const res = await databases.listDocuments(DB_ID, 'categories', q);
|
||
for (const c of res.documents) {
|
||
existingCatSlugs.set(c.slug, c.$id);
|
||
}
|
||
if (res.documents.length < 100) break;
|
||
cursor = res.documents[res.documents.length - 1].$id;
|
||
}
|
||
|
||
let catsCreated = 0;
|
||
await runPool(wpData.categories, 8, async (cat) => {
|
||
const slug = (cat.slug || '').substring(0, 95);
|
||
const nombre = stripHtml(cat.nombre || slug).substring(0, 95);
|
||
if (!slug || existingCatSlugs.has(slug)) return;
|
||
try {
|
||
const doc = await databases.createDocument(DB_ID, 'categories', ID.unique(), {
|
||
nombre,
|
||
slug,
|
||
});
|
||
existingCatSlugs.set(slug, doc.$id);
|
||
catsCreated++;
|
||
} catch (_) {}
|
||
});
|
||
console.log(`✓ Categorías listas (nuevas creadas: ${catsCreated}, total: ${existingCatSlugs.size})\n`);
|
||
|
||
// 2. Cargar slugs de posts existentes para evitar duplicados si se re-ejecuta
|
||
console.log('2️⃣ Verificando posts existentes en Appwrite Cloud...');
|
||
const existingPostSlugs = new Set();
|
||
cursor = null;
|
||
while (true) {
|
||
const q = [Query.limit(100), Query.select(['$id', 'slug'])];
|
||
if (cursor) q.push(Query.cursorAfter(cursor));
|
||
const res = await databases.listDocuments(DB_ID, 'posts', q);
|
||
for (const p of res.documents) {
|
||
if (p.slug) existingPostSlugs.add(p.slug);
|
||
}
|
||
if (res.documents.length < 100) break;
|
||
cursor = res.documents[res.documents.length - 1].$id;
|
||
}
|
||
console.log(`✓ Slugs ya existentes en Appwrite: ${existingPostSlugs.size}\n`);
|
||
|
||
// 3. Ordenar posts del más reciente al más antiguo (2026 -> 2020)
|
||
const sortedPosts = [...wpData.posts].sort((a, b) => new Date(b.date) - new Date(a.date));
|
||
const toImport = sortedPosts.filter((p) => p.slug && !existingPostSlugs.has(p.slug.substring(0, 250)));
|
||
console.log(`3️⃣ Importando ${toImport.length} publicaciones históricas con sus imágenes y links originales...\n`);
|
||
|
||
const mediaCache = new Map(); // imageRelPath -> Promise<mediaDocId>
|
||
let importedPosts = 0;
|
||
let uploadedImages = 0;
|
||
let failedPosts = 0;
|
||
|
||
async function getOrUploadMedia(post) {
|
||
if (!post.imageRelPath) return null;
|
||
const cleanRel = post.imageRelPath.replace(/^\/+/, '');
|
||
if (mediaCache.has(cleanRel)) {
|
||
return mediaCache.get(cleanRel);
|
||
}
|
||
|
||
const promise = (async () => {
|
||
const localImgPath = path.join(IMG_DIR, safeFileKey(cleanRel) + '.jpg');
|
||
if (!fs.existsSync(localImgPath)) return null;
|
||
try {
|
||
const file = await storage.createFile(
|
||
BUCKET_ID,
|
||
ID.unique(),
|
||
InputFile.fromPath(localImgPath, path.posix.basename(cleanRel).replace(/[^a-zA-Z0-9._-]/g, '_') || 'wp_image.jpg')
|
||
);
|
||
const tituloMedia = stripHtml(post.imageTitle || post.title || 'Imagen WordPress').substring(0, 250) || 'Imagen';
|
||
const altMedia = stripHtml(post.imageAlt || post.title || '').substring(0, 490);
|
||
const mediaDoc = await databases.createDocument(DB_ID, 'media', ID.unique(), {
|
||
file_id: file.$id,
|
||
bucket_id: BUCKET_ID,
|
||
titulo: tituloMedia,
|
||
alt_text: altMedia,
|
||
tipo: 'imagen',
|
||
});
|
||
uploadedImages++;
|
||
return mediaDoc.$id;
|
||
} catch (err) {
|
||
return null;
|
||
}
|
||
})();
|
||
|
||
mediaCache.set(cleanRel, promise);
|
||
return promise;
|
||
}
|
||
|
||
await runPool(toImport, 6, async (post, i) => {
|
||
try {
|
||
const mediaDocId = await getOrUploadMedia(post);
|
||
const cleanTitle = stripHtml(post.title || 'Sin título').substring(0, 250) || 'Sin título';
|
||
const plainExcerpt = stripHtml(post.excerpt || post.content || cleanTitle);
|
||
const resumen = plainExcerpt.substring(0, 480) || cleanTitle;
|
||
const seoDesc = plainExcerpt.substring(0, 280) || cleanTitle;
|
||
const slug = post.slug.substring(0, 250);
|
||
|
||
let isoDate;
|
||
try {
|
||
isoDate = new Date(post.date.replace(' ', 'T') + 'Z').toISOString();
|
||
} catch (_) {
|
||
isoDate = new Date().toISOString();
|
||
}
|
||
|
||
const data = {
|
||
titulo: cleanTitle,
|
||
slug,
|
||
tipo: 'noticia',
|
||
tipo_publicacion_id: NOTICIA_TYPE_ID,
|
||
estado: 'publicado',
|
||
fecha_publicacion: isoDate,
|
||
contenido: formatWpContent(post.content),
|
||
resumen_redes: resumen,
|
||
seo_titulo: cleanTitle.substring(0, 115),
|
||
seo_descripcion: seoDesc,
|
||
...(mediaDocId ? { imagen_destacada_id: mediaDocId } : {}),
|
||
};
|
||
|
||
await databases.createDocument(DB_ID, 'posts', ID.unique(), data);
|
||
importedPosts++;
|
||
|
||
if (importedPosts % 50 === 0 || importedPosts === toImport.length) {
|
||
console.log(
|
||
`[Progreso WP] ${importedPosts}/${toImport.length} posts publicados | Imágenes subidas: ${uploadedImages} | Última fecha: ${post.date.slice(0, 10)} (${slug.slice(0, 45)})`
|
||
);
|
||
}
|
||
} catch (err) {
|
||
failedPosts++;
|
||
if (failedPosts <= 20) {
|
||
console.error(`! Error en post WP ${post.id} (${post.slug}):`, err.message);
|
||
}
|
||
}
|
||
});
|
||
|
||
console.log('\n===================================================================');
|
||
console.log(`🎉 ¡MIGRACIÓN HISTÓRICA DE WORDPRESS COMPLETADA!`);
|
||
console.log(` - Publicaciones importadas: ${importedPosts}`);
|
||
console.log(` - Imágenes destacadas subidas: ${uploadedImages}`);
|
||
console.log(` - Fallos: ${failedPosts}`);
|
||
console.log('===================================================================');
|
||
}
|
||
|
||
run().catch((e) => {
|
||
console.error('Fatal error:', e);
|
||
process.exit(1);
|
||
});
|
||
|