Files
klz-cables.com/scripts/migrate-data.ts
2026-02-01 02:02:03 +01:00

176 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string, string> = {};
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.*?>|<\/ProductTabs>|<ProductTechnicalData.*?\/>/gs, '').trim();
const extract = (c: string) => {
const m = c.match(/technicalData=\{<ProductTechnicalData data=\{(.*?)\}\s*\/>\}/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);