import client, { ensureAuthenticated } from '../lib/directus'; import { createCollection, createField, createRelation, uploadFiles, createItem, updateSettings, readFolders, createFolder } from '@directus/sdk'; import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); async function run() { console.log('🚀 CLEAN SLATE MIGRATION 🚀'); await ensureAuthenticated(); // 1. Folders console.log('📂 Creating Folders...'); const folders: any = {}; const folderNames = ['Products', 'Blog', 'Pages', 'Technical']; for (const name of folderNames) { try { const res = await client.request(createFolder({ name })); folders[name] = res.id; } catch (e) { const existing = await client.request(readFolders({ filter: { name: { _eq: name } } })); folders[name] = existing[0].id; } } // 2. Assets const assetMap: Record = {}; const uploadDir = async (dir: string, folderId: string) => { if (!fs.existsSync(dir)) return; const files = fs.readdirSync(dir, { withFileTypes: true }); for (const file of files) { const fullPath = path.join(dir, file.name); if (file.isDirectory()) { await uploadDir(fullPath, folderId); } else { const relPath = '/' + path.relative(path.join(process.cwd(), 'public'), fullPath).split(path.sep).join('/'); try { const form = new FormData(); form.append('folder', folderId); form.append('file', new Blob([fs.readFileSync(fullPath)]), file.name); const res = await client.request(uploadFiles(form)); assetMap[relPath] = res.id; console.log(`✅ Asset: ${relPath}`); } catch (e) { } } } }; await uploadDir(path.join(process.cwd(), 'public/uploads'), folders.Products); // 3. Collections (Minimalist) const collections = [ 'categories', 'products', 'posts', 'pages', 'globals', 'categories_translations', 'products_translations', 'posts_translations', 'pages_translations', 'globals_translations', 'categories_link' ]; console.log('đŸ—ī¸ Creating Collections...'); for (const name of collections) { try { const isSingleton = name === 'globals'; await client.request(createCollection({ collection: name, schema: {}, meta: { singleton: isSingleton } } as any)); // Add ID field await client.request(createField(name, { field: 'id', type: 'integer', meta: { hidden: true }, schema: { is_primary_key: true, has_auto_increment: name !== 'globals' } })); console.log(`✅ Collection: ${name}`); } catch (e: any) { console.log(`â„šī¸ Collection ${name} exists or error: ${e.message}`); } } // 4. Fields & Relations console.log('🔧 Configuring Schema...'); const safeAdd = async (col: string, f: any) => { try { await client.request(createField(col, f)); } catch (e) { } }; // Products await safeAdd('products', { field: 'sku', type: 'string' }); await safeAdd('products', { field: 'image', type: 'uuid', meta: { interface: 'file' } }); // Translations Generic for (const col of ['categories', 'products', 'posts', 'pages', 'globals']) { const transTable = `${col}_translations`; await safeAdd(transTable, { field: `${col}_id`, type: 'integer' }); await safeAdd(transTable, { field: 'languages_code', type: 'string' }); // Link to Parent try { await client.request(createRelation({ collection: transTable, field: `${col}_id`, related_collection: col, meta: { one_field: 'translations' } })); } catch (e) { } } // Specific Fields await safeAdd('products_translations', { field: 'name', type: 'string' }); await safeAdd('products_translations', { field: 'slug', type: 'string' }); await safeAdd('products_translations', { field: 'description', type: 'text' }); await safeAdd('products_translations', { field: 'content', type: 'text', meta: { interface: 'input-rich-text-html' } }); await safeAdd('products_translations', { field: 'technical_items', type: 'json' }); await safeAdd('products_translations', { field: 'voltage_tables', type: 'json' }); await safeAdd('categories_translations', { field: 'name', type: 'string' }); await safeAdd('posts_translations', { field: 'title', type: 'string' }); await safeAdd('posts_translations', { field: 'slug', type: 'string' }); await safeAdd('posts_translations', { field: 'content', type: 'text' }); await safeAdd('globals', { field: 'company_name', type: 'string' }); await safeAdd('globals_translations', { field: 'tagline', type: 'string' }); // M2M Link await safeAdd('categories_link', { field: 'products_id', type: 'integer' }); await safeAdd('categories_link', { field: 'categories_id', type: 'integer' }); try { await client.request(createRelation({ collection: 'categories_link', field: 'products_id', related_collection: 'products', meta: { one_field: 'categories_link' } })); await client.request(createRelation({ collection: 'categories_link', field: 'categories_id', related_collection: 'categories' })); } catch (e) { } // 5. Data Import console.log('đŸ“Ĩ Importing Data...'); const deDir = path.join(process.cwd(), 'data/products/de'); const files = fs.readdirSync(deDir).filter(f => f.endsWith('.mdx')); for (const file of files) { const doc = matter(fs.readFileSync(path.join(deDir, file), 'utf8')); const enPath = path.join(process.cwd(), `data/products/en/${file}`); const enDoc = fs.existsSync(enPath) ? matter(fs.readFileSync(enPath, 'utf8')) : doc; const clean = (c: string) => c.replace(/|<\/ProductTabs>|/gs, '').trim(); const extract = (c: string) => { const m = c.match(/technicalData=\{\}/s); try { return m ? JSON.parse(m[1]) : {}; } catch (e) { return {}; } }; try { await client.request(createItem('products', { sku: doc.data.sku, image: assetMap[doc.data.images?.[0]] || null, translations: [ { languages_code: 'de-DE', name: doc.data.title, slug: file.replace('.mdx', ''), description: doc.data.description, content: clean(doc.content), technical_items: extract(doc.content).technicalItems, voltage_tables: extract(doc.content).voltageTables }, { languages_code: 'en-US', name: enDoc.data.title, slug: file.replace('.mdx', ''), description: enDoc.data.description, content: clean(enDoc.content), technical_items: extract(enDoc.content).technicalItems, voltage_tables: extract(enDoc.content).voltageTables } ] })); console.log(`✅ Product: ${doc.data.sku}`); } catch (e: any) { console.error(`❌ Product ${file}: ${e.message}`); } } console.log('✨ DONE!'); } run().catch(console.error);