#!/usr/bin/env node /** * Directus Tech Stack Migration Script * * Migriert bestehende Tech Stack Daten aus messages/en.json und messages/de.json * nach Directus Collections. * * Usage: * npm install node-fetch@2 dotenv * node scripts/migrate-tech-stack-to-directus.js */ const fetch = require('node-fetch'); const fs = require('fs'); const path = require('path'); 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); } // Lade aktuelle Tech Stack Daten aus messages files const messagesEn = JSON.parse( fs.readFileSync(path.join(__dirname, '../messages/en.json'), 'utf-8') ); const messagesDe = JSON.parse( fs.readFileSync(path.join(__dirname, '../messages/de.json'), 'utf-8') ); const techStackEn = messagesEn.home.about.techStack; const techStackDe = messagesDe.home.about.techStack; // Tech Stack Struktur aus About.tsx const TECH_STACK_DATA = [ { key: 'frontend', icon: 'Globe', nameEn: techStackEn.categories.frontendMobile, nameDe: techStackDe.categories.frontendMobile, items: ['Next.js', 'Tailwind CSS', 'Flutter'] }, { key: 'backend', icon: 'Server', nameEn: techStackEn.categories.backendDevops, nameDe: techStackDe.categories.backendDevops, items: ['Docker', 'PostgreSQL', 'Redis', 'Traefik'] }, { key: 'tools', icon: 'Wrench', nameEn: techStackEn.categories.toolsAutomation, nameDe: techStackDe.categories.toolsAutomation, items: ['Git', 'CI/CD', 'n8n', techStackEn.items.selfHostedServices] }, { key: 'security', icon: 'Shield', nameEn: techStackEn.categories.securityAdmin, nameDe: techStackDe.categories.securityAdmin, items: ['CrowdSec', 'Suricata', 'Proxmox'] } ]; 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); } try { const response = await fetch(url, options); if (!response.ok) { const text = await response.text(); throw new Error(`HTTP ${response.status}: ${text}`); } return await response.json(); } catch (error) { console.error(`Error calling ${method} ${endpoint}:`, error.message); throw error; } } async function ensureLanguagesExist() { console.log('\nšŸŒ Checking Languages...'); try { const { data: languages } = await directusRequest('items/languages'); const hasEnUS = languages.some(l => l.code === 'en-US'); const hasDeDE = languages.some(l => l.code === 'de-DE'); if (!hasEnUS) { console.log(' Creating en-US language...'); await directusRequest('items/languages', 'POST', { code: 'en-US', name: 'English (United States)' }); } if (!hasDeDE) { console.log(' Creating de-DE language...'); await directusRequest('items/languages', 'POST', { code: 'de-DE', name: 'German (Germany)' }); } console.log(' āœ… Languages ready'); } catch (error) { console.log(' āš ļø Languages collection might not exist yet'); } } async function migrateTechStack() { console.log('\nšŸ“¦ Migrating Tech Stack to Directus...\n'); await ensureLanguagesExist(); for (const category of TECH_STACK_DATA) { console.log(`\nšŸ“ Category: ${category.key}`); try { // 1. Create Category console.log(' Creating category...'); const categoryData = { key: category.key, icon: category.icon, status: 'published', sort: TECH_STACK_DATA.indexOf(category) + 1 }; const { data: createdCategory } = await directusRequest( 'items/tech_stack_categories', 'POST', categoryData ); console.log(` āœ… Category created with ID: ${createdCategory.id}`); // 2. Create Translations console.log(' Creating translations...'); // English Translation await directusRequest( 'items/tech_stack_categories_translations', 'POST', { tech_stack_categories_id: createdCategory.id, languages_code: 'en-US', name: category.nameEn } ); // German Translation await directusRequest( 'items/tech_stack_categories_translations', 'POST', { tech_stack_categories_id: createdCategory.id, languages_code: 'de-DE', name: category.nameDe } ); console.log(' āœ… Translations created (en-US, de-DE)'); // 3. Create Items console.log(` Creating ${category.items.length} items...`); for (let i = 0; i < category.items.length; i++) { const itemName = category.items[i]; await directusRequest( 'items/tech_stack_items', 'POST', { category: createdCategory.id, name: itemName, sort: i + 1 } ); console.log(` āœ… ${itemName}`); } } catch (error) { console.error(` āŒ Error migrating ${category.key}:`, error.message); } } console.log('\n✨ Migration complete!\n'); } async function verifyMigration() { console.log('\nšŸ” Verifying Migration...\n'); try { const { data: categories } = await directusRequest( 'items/tech_stack_categories?fields=*,translations.*,items.*' ); console.log(`āœ… Found ${categories.length} categories:`); categories.forEach(cat => { const enTranslation = cat.translations?.find(t => t.languages_code === 'en-US'); const itemCount = cat.items?.length || 0; console.log(` - ${cat.key}: "${enTranslation?.name}" (${itemCount} items)`); }); console.log('\nšŸŽ‰ All data migrated successfully!\n'); console.log('Next steps:'); console.log(' 1. Visit https://cms.dk0.dev/admin/content/tech_stack_categories'); console.log(' 2. Verify data looks correct'); console.log(' 3. Run: npm run dev:directus (to test GraphQL queries)'); console.log(' 4. Update About.tsx to use Directus data\n'); } catch (error) { console.error('āŒ Verification failed:', error.message); } } // Main execution (async () => { try { await migrateTechStack(); await verifyMigration(); } catch (error) { console.error('\nāŒ Migration failed:', error); process.exit(1); } })();