#!/usr/bin/env node /** * Migrate Content Pages from PostgreSQL (Prisma) to Directus * * - Copies `content_pages` + translations from Postgres into Directus * - Creates or updates items per (slug, locale) * * Usage: * DATABASE_URL=postgresql://... DIRECTUS_STATIC_TOKEN=... DIRECTUS_URL=... \ * node scripts/migrate-content-pages-to-directus.js */ const fetch = require('node-fetch'); const { PrismaClient } = require('@prisma/client'); require('dotenv').config(); const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; if (!DIRECTUS_TOKEN) { console.error('❌ Error: DIRECTUS_STATIC_TOKEN not found in env'); process.exit(1); } const prisma = new PrismaClient(); const localeMap = { en: 'en-US', de: 'de-DE', }; function toDirectusLocale(locale) { return localeMap[locale] || locale; } async function directusRequest(endpoint, method = 'GET', body = null) { const url = `${DIRECTUS_URL}/${endpoint}`; const options = { method, headers: { Authorization: `Bearer ${DIRECTUS_TOKEN}`, 'Content-Type': 'application/json', }, }; if (body) { options.body = JSON.stringify(body); } const res = await fetch(url, options); if (!res.ok) { const text = await res.text(); throw new Error(`HTTP ${res.status} on ${endpoint}: ${text}`); } return res.json(); } async function upsertContentIntoDirectus({ slug, locale, status, title, content }) { const directusLocale = toDirectusLocale(locale); // allow locale-specific slug variants: base for en, base-locale for others const slugVariant = directusLocale === 'en-US' ? slug : `${slug}-${directusLocale.toLowerCase()}`; const payload = { slug: slugVariant, locale: directusLocale, status: status?.toLowerCase?.() === 'published' ? 'published' : status || 'draft', title: title || slug, content: content || null, }; try { const { data } = await directusRequest('items/content_pages', 'POST', payload); console.log(` ➕ Created ${slugVariant} (${directusLocale}) [id=${data?.id}]`); return data?.id; } catch (error) { const msg = error?.message || ''; if (msg.includes('already exists') || msg.includes('duplicate key') || msg.includes('UNIQUE')) { console.log(` ⚠️ Skipping ${slugVariant} (${directusLocale}) – already exists`); return null; } throw error; } } async function migrateContentPages() { console.log('\n📦 Migrating Content Pages from PostgreSQL to Directus...'); const pages = await prisma.contentPage.findMany({ include: { translations: true }, }); console.log(`Found ${pages.length} pages in PostgreSQL`); for (const page of pages) { const status = page.status || 'PUBLISHED'; for (const tr of page.translations) { await upsertContentIntoDirectus({ slug: page.key, locale: tr.locale, status, title: tr.title, content: tr.content, }); } } console.log('✅ Content page migration finished.'); } async function main() { try { await prisma.$connect(); await migrateContentPages(); } catch (error) { console.error('❌ Migration failed:', error.message); process.exit(1); } finally { await prisma.$disconnect(); } } main();