176 lines
7.5 KiB
TypeScript
176 lines
7.5 KiB
TypeScript
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);
|