This commit is contained in:
2026-02-01 02:02:03 +01:00
parent 26fc34299e
commit ee04d2422c
840 changed files with 6431 additions and 58 deletions

View File

@@ -0,0 +1,19 @@
import client, { ensureAuthenticated } from '../lib/directus';
import { readCollections, deleteCollection } from '@directus/sdk';
async function cleanup() {
await ensureAuthenticated();
const collections = await (client as any).request(readCollections());
for (const c of collections) {
if (!c.collection.startsWith('directus_')) {
console.log(`Deleting ${c.collection}...`);
try {
await (client as any).request(deleteCollection(c.collection));
} catch (e) {
console.error(`Failed to delete ${c.collection}`);
}
}
}
}
cleanup().catch(console.error);

99
scripts/fix-schema.ts Normal file
View File

@@ -0,0 +1,99 @@
import client, { ensureAuthenticated } from '../lib/directus';
import {
createCollection,
createField,
createItem,
readCollections,
deleteCollection
} from '@directus/sdk';
async function fixSchema() {
console.log('🚑 EXTERNAL RESCUE: Fixing Schema & Data...');
await ensureAuthenticated();
// 1. Reset Products Collection to be 100% Standard
console.log('🗑️ Clearing broken collections...');
try { await client.request(deleteCollection('products')); } catch (e) { }
try { await client.request(deleteCollection('products_translations')); } catch (e) { }
// 2. Create Products (Simple, Standard ID)
console.log('🏗️ Rebuilding Products Schema...');
await client.request(createCollection({
collection: 'products',
schema: {}, // Let Directus decide defaults
meta: {
display_template: '{{sku}}',
archive_field: 'status',
archive_value: 'archived',
unarchive_value: 'published'
},
fields: [
{
field: 'id',
type: 'integer',
schema: { is_primary_key: true, has_auto_increment: true },
meta: { hidden: true }
},
{
field: 'status',
type: 'string',
schema: { default_value: 'published' },
meta: { width: 'full', options: { choices: [{ text: 'Published', value: 'published' }] } }
},
{
field: 'sku',
type: 'string',
meta: { interface: 'input', width: 'half' }
}
]
} as any));
// 3. Create Translation Relation Safely
console.log('🌍 Rebuilding Translations...');
await client.request(createCollection({
collection: 'products_translations',
schema: {},
fields: [
{
field: 'id',
type: 'integer',
schema: { is_primary_key: true, has_auto_increment: true },
meta: { hidden: true }
},
{ field: 'products_id', type: 'integer' },
{ field: 'languages_code', type: 'string' },
{ field: 'name', type: 'string', meta: { interface: 'input', width: 'full' } },
{ field: 'description', type: 'text', meta: { interface: 'input-multiline' } },
{ field: 'technical_items', type: 'json', meta: { interface: 'input-code-json' } }
]
} as any));
// 4. Manually Insert ONE Product to Verify
console.log('📦 Injecting Test Product...');
try {
// We do this in two steps to be absolutely sure permissions don't block us
// Step A: Create User-Facing Product
const product = await client.request(createItem('products', {
sku: 'H1Z2Z2-K-TEST',
status: 'published'
}));
// Step B: Add Translation
await client.request(createItem('products_translations', {
products_id: product.id,
languages_code: 'de-DE',
name: 'H1Z2Z2-K Test Cable',
description: 'This is a verified imported product.',
technical_items: [{ label: 'Test', value: '100%' }]
}));
console.log(`✅ SUCCESS! Product Created with ID: ${product.id}`);
console.log(`verify at: ${process.env.DIRECTUS_URL}/admin/content/products/${product.id}`);
} catch (e: any) {
console.error('❌ Failed to create product:', e);
if (e.errors) console.error(JSON.stringify(e.errors, null, 2));
}
}
fixSchema().catch(console.error);

175
scripts/migrate-data.ts Normal file
View File

@@ -0,0 +1,175 @@
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);

View File

@@ -0,0 +1,63 @@
import client, { ensureAuthenticated } from '../lib/directus';
import {
updateSettings,
updateCollection,
createItem,
updateItem
} from '@directus/sdk';
import fs from 'fs';
import path from 'path';
async function optimize() {
await ensureAuthenticated();
console.log('🎨 Fixing Branding...');
await client.request(updateSettings({
project_name: 'KLZ Cables',
public_note: '<div style="text-align: center;"><h1>Sustainable Energy.</h1><p>Industrial Reliability.</p></div>',
custom_css: 'body { font-family: Inter, sans-serif !important; } .public-view .v-card { border-radius: 20px !important; }'
}));
console.log('🔧 Fixing List Displays...');
const collections = ['products', 'categories', 'posts', 'pages'];
for (const collection of collections) {
try {
await (client as any).request(updateCollection(collection, {
meta: { display_template: '{{translations.name || translations.title}}' }
}));
} catch (e) {
console.error(`Failed to update ${collection}:`, e);
}
}
console.log('🏛️ Force-Syncing Globals...');
const de = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'messages/de.json'), 'utf8'));
const en = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'messages/en.json'), 'utf8'));
const payload = {
id: 1,
company_name: 'KLZ Cables GmbH',
email: 'info@klz-cables.com',
phone: '+49 711 1234567',
address: de.Contact.info.address,
opening_hours: `${de.Contact.hours.weekdays}: ${de.Contact.hours.weekdaysTime}`,
translations: [
{ languages_code: 'en-US', tagline: en.Footer.tagline },
{ languages_code: 'de-DE', tagline: de.Footer.tagline }
]
};
try {
await client.request(createItem('globals', payload));
} catch (e) {
try {
await client.request(updateItem('globals', 1, payload));
} catch (err) {
console.error('Globals still failing:', (err as any).message);
}
}
console.log('✅ Optimization complete.');
}
optimize().catch(console.error);

208
scripts/revert-and-clean.ts Normal file
View File

@@ -0,0 +1,208 @@
import client, { ensureAuthenticated } from '../lib/directus';
import {
deleteCollection,
deleteFile,
readFiles,
updateSettings,
uploadFiles
} from '@directus/sdk';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// Helper for ESM __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function revertAndRestoreBranding() {
console.log('🚨 REVERTING EVERYTHING - RESTORING BRANDING ONLY 🚨');
await ensureAuthenticated();
// 1. DELETE ALL COLLECTIONS
const collectionsToDelete = [
'categories_link',
'categories_translations', 'categories',
'products_translations', 'products',
'posts_translations', 'posts',
'pages_translations', 'pages',
'globals_translations', 'globals'
];
console.log('🗑️ Deleting custom collections...');
for (const col of collectionsToDelete) {
try {
await client.request(deleteCollection(col));
console.log(`✅ Deleted collection: ${col}`);
} catch (e: any) {
console.log(` Collection ${col} not found or already deleted.`);
}
}
// 2. DELETE ALL FILES
console.log('🗑️ Deleting ALL files...');
try {
const files = await client.request(readFiles({ limit: -1 }));
if (files && files.length > 0) {
const ids = files.map(f => f.id);
await client.request(deleteFile(ids)); // Batch delete if supported by SDK version, else loop
console.log(`✅ Deleted ${ids.length} files.`);
} else {
console.log(' No files to delete.');
}
} catch (e: any) {
// Fallback to loop if batch fails
try {
const files = await client.request(readFiles({ limit: -1 }));
for (const f of files) {
await client.request(deleteFile(f.id));
}
console.log(`✅ Deleted files individually.`);
} catch (err) { }
}
// 3. RESTORE BRANDING (Exact copy of setup-directus-branding.ts logic)
console.log('🎨 Restoring Premium Branding...');
try {
const getMimeType = (filePath: string) => {
const ext = path.extname(filePath).toLowerCase();
switch (ext) {
case '.svg': return 'image/svg+xml';
case '.png': return 'image/png';
case '.jpg':
case '.jpeg': return 'image/jpeg';
case '.ico': return 'image/x-icon';
default: return 'application/octet-stream';
}
};
const uploadAsset = async (filePath: string, title: string) => {
if (!fs.existsSync(filePath)) {
console.warn(`⚠️ File not found: ${filePath}`);
return null;
}
const mimeType = getMimeType(filePath);
const form = new FormData();
const fileBuffer = fs.readFileSync(filePath);
const blob = new Blob([fileBuffer], { type: mimeType });
form.append('file', blob, path.basename(filePath));
form.append('title', title);
const res = await client.request(uploadFiles(form));
return res.id;
};
const logoWhiteId = await uploadAsset(path.resolve(__dirname, '../public/logo-white.svg'), 'Logo White');
const logoBlueId = await uploadAsset(path.resolve(__dirname, '../public/logo-blue.svg'), 'Logo Blue');
const faviconId = await uploadAsset(path.resolve(__dirname, '../public/favicon.ico'), 'Favicon');
// Smoother Background SVG
const bgSvgPath = path.resolve(__dirname, '../public/login-bg.svg');
fs.writeFileSync(bgSvgPath, `<svg width="1920" height="1080" viewBox="0 0 1920 1080" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1920" height="1080" fill="#001a4d"/>
<ellipse cx="960" cy="540" rx="800" ry="600" fill="url(#paint0_radial_premium)"/>
<defs>
<radialGradient id="paint0_radial_premium" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(960 540) rotate(90) scale(600 800)">
<stop stop-color="#003d82" stop-opacity="0.8"/>
<stop offset="1" stop-color="#001a4d" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>`);
const backgroundId = await uploadAsset(bgSvgPath, 'Login Bg');
if (fs.existsSync(bgSvgPath)) fs.unlinkSync(bgSvgPath);
// Update Settings
const COLOR_PRIMARY = '#001a4d';
const COLOR_ACCENT = '#82ed20';
const COLOR_SECONDARY = '#003d82';
const cssInjection = `
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
body, .v-app {
font-family: 'Inter', sans-serif !important;
-webkit-font-smoothing: antialiased;
}
.public-view .v-card {
background: rgba(255, 255, 255, 0.95) !important;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3) !important;
border-radius: 32px !important;
box-shadow: 0 50px 100px -20px rgba(0, 0, 0, 0.4) !important;
padding: 40px !important;
}
.public-view .v-button {
border-radius: 9999px !important;
height: 56px !important;
font-weight: 600 !important;
letter-spacing: -0.01em !important;
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1) !important;
}
.public-view .v-button:hover {
transform: translateY(-2px);
box-shadow: 0 15px 30px rgba(130, 237, 32, 0.2) !important;
}
.public-view .v-input {
--v-input-border-radius: 12px !important;
--v-input-background-color: #f8f9fa !important;
}
</style>
<div style="font-family: 'Inter', sans-serif; text-align: center; margin-top: 24px;">
<p style="color: rgba(255,255,255,0.7); font-size: 14px; margin-bottom: 4px; font-weight: 500;">KLZ INFRASTRUCTURE ENGINE</p>
<h1 style="color: #ffffff; font-size: 18px; font-weight: 700; margin: 0;">Sustainable Energy. <span style="color: #82ed20;">Industrial Reliability.</span></h1>
</div>
`;
await client.request(updateSettings({
project_name: 'KLZ Cables',
project_url: 'https://klz-cables.com',
project_color: COLOR_ACCENT,
project_descriptor: 'Sustainable Energy Infrastructure',
project_owner: 'KLZ Cables',
project_logo: logoWhiteId as any,
public_foreground: logoWhiteId as any,
public_background: backgroundId as any,
public_note: cssInjection,
public_favicon: faviconId as any,
theme_light_overrides: {
"primary": COLOR_ACCENT,
"secondary": COLOR_SECONDARY,
"background": "#f1f3f7",
"backgroundNormal": "#ffffff",
"backgroundAccent": "#eef2ff",
"navigationBackground": COLOR_PRIMARY,
"navigationForeground": "#ffffff",
"navigationBackgroundHover": "rgba(255,255,255,0.05)",
"navigationForegroundHover": "#ffffff",
"navigationBackgroundActive": "rgba(130, 237, 32, 0.15)",
"navigationForegroundActive": COLOR_ACCENT,
"moduleBarBackground": "#000d26",
"moduleBarForeground": "#ffffff",
"moduleBarForegroundActive": COLOR_ACCENT,
"borderRadius": "16px",
"borderWidth": "1px",
"borderColor": "#e2e8f0",
"formFieldHeight": "48px"
} as any,
theme_dark_overrides: {
"primary": COLOR_ACCENT,
"background": "#0a0a0a",
"navigationBackground": "#000000",
"moduleBarBackground": COLOR_PRIMARY,
"borderRadius": "16px",
"formFieldHeight": "48px"
} as any
}));
console.log('✨ System Cleaned & Branding Restored Successfully');
} catch (error: any) {
console.error('❌ Error restoring branding:', JSON.stringify(error, null, 2));
}
}
revertAndRestoreBranding().catch(console.error);