Files
portfolio/scripts/setup-directus-collections.js
denshooter e431ff50fc feat: Add Directus setup scripts for collections, fields, and relations
- Created setup-directus-collections.js to automate the creation of tech stack collections, fields, and relations in Directus.
- Created setup-directus-hobbies.js for setting up hobbies collection with translations.
- Created setup-directus-projects.js for establishing projects collection with comprehensive fields and translations.
- Added setup-tech-stack-directus.js to populate tech_stack_items with predefined data.
2026-01-23 02:53:31 +01:00

436 lines
12 KiB
JavaScript
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.

#!/usr/bin/env node
/**
* Directus Schema Setup via REST API
*
* Erstellt automatisch alle benötigten Collections, Fields und Relations
* für Tech Stack in Directus via REST API.
*
* Usage:
* npm install node-fetch@2
* node scripts/setup-directus-collections.js
*/
const fetch = require('node-fetch');
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);
}
console.log(`🔗 Connecting to: ${DIRECTUS_URL}`);
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);
const text = await response.text();
if (!response.ok) {
// Ignore "already exists" errors
if (text.includes('already exists') || text.includes('RECORD_NOT_UNIQUE')) {
console.log(` ⚠️ Already exists, skipping...`);
return { data: null, alreadyExists: true };
}
throw new Error(`HTTP ${response.status}: ${text}`);
}
return text ? JSON.parse(text) : {};
} catch (error) {
console.error(`❌ Error calling ${method} ${endpoint}:`, error.message);
throw error;
}
}
async function ensureLanguages() {
console.log('\n🌍 Setting up Languages...');
try {
// Check if languages collection exists
const { data: existing } = await directusRequest('items/languages');
if (!existing) {
console.log(' Creating languages collection...');
await directusRequest('collections', 'POST', {
collection: 'languages',
meta: {
icon: 'translate',
translations: [
{ language: 'en-US', translation: 'Languages' }
]
},
schema: { name: 'languages' }
});
}
// Add en-US
await directusRequest('items/languages', 'POST', {
code: 'en-US',
name: 'English (United States)'
});
// Add de-DE
await directusRequest('items/languages', 'POST', {
code: 'de-DE',
name: 'German (Germany)'
});
console.log(' ✅ Languages ready (en-US, de-DE)');
} catch (error) {
console.log(' ⚠️ Languages might already exist');
}
}
async function createTechStackCollections() {
console.log('\n📦 Creating Tech Stack Collections...\n');
// 1. Create tech_stack_categories collection
console.log('1⃣ Creating tech_stack_categories...');
try {
await directusRequest('collections', 'POST', {
collection: 'tech_stack_categories',
meta: {
icon: 'layers',
display_template: '{{translations.name}}',
hidden: false,
singleton: false,
translations: [
{ language: 'en-US', translation: 'Tech Stack Categories' },
{ language: 'de-DE', translation: 'Tech Stack Kategorien' }
],
sort_field: 'sort'
},
schema: {
name: 'tech_stack_categories'
}
});
console.log(' ✅ Collection created');
} catch (error) {
console.log(' ⚠️ Collection might already exist');
}
// 2. Create tech_stack_categories_translations collection
console.log('\n2⃣ Creating tech_stack_categories_translations...');
try {
await directusRequest('collections', 'POST', {
collection: 'tech_stack_categories_translations',
meta: {
hidden: true,
icon: 'import_export'
},
schema: {
name: 'tech_stack_categories_translations'
}
});
console.log(' ✅ Collection created');
} catch (error) {
console.log(' ⚠️ Collection might already exist');
}
// 3. Create tech_stack_items collection
console.log('\n3⃣ Creating tech_stack_items...');
try {
await directusRequest('collections', 'POST', {
collection: 'tech_stack_items',
meta: {
icon: 'code',
display_template: '{{name}}',
hidden: false,
singleton: false,
sort_field: 'sort'
},
schema: {
name: 'tech_stack_items'
}
});
console.log(' ✅ Collection created');
} catch (error) {
console.log(' ⚠️ Collection might already exist');
}
}
async function createFields() {
console.log('\n🔧 Creating Fields...\n');
// Fields for tech_stack_categories
console.log('1⃣ Fields for tech_stack_categories:');
const categoryFields = [
{
field: 'status',
type: 'string',
meta: {
interface: 'select-dropdown',
options: {
choices: [
{ text: 'Published', value: 'published' },
{ text: 'Draft', value: 'draft' },
{ text: 'Archived', value: 'archived' }
]
}
},
schema: { default_value: 'draft', is_nullable: false }
},
{
field: 'sort',
type: 'integer',
meta: { interface: 'input', hidden: true },
schema: {}
},
{
field: 'key',
type: 'string',
meta: {
interface: 'input',
note: 'Unique identifier (e.g. frontend, backend)'
},
schema: { is_unique: true, is_nullable: false }
},
{
field: 'icon',
type: 'string',
meta: {
interface: 'select-dropdown',
options: {
choices: [
{ text: 'Globe', value: 'Globe' },
{ text: 'Server', value: 'Server' },
{ text: 'Wrench', value: 'Wrench' },
{ text: 'Shield', value: 'Shield' }
]
}
},
schema: { default_value: 'Code' }
},
{
field: 'date_created',
type: 'timestamp',
meta: { special: ['date-created'], interface: 'datetime', readonly: true, hidden: true },
schema: {}
},
{
field: 'date_updated',
type: 'timestamp',
meta: { special: ['date-updated'], interface: 'datetime', readonly: true, hidden: true },
schema: {}
},
{
field: 'translations',
type: 'alias',
meta: {
special: ['translations'],
interface: 'translations',
options: { languageField: 'languages_code' }
}
}
];
for (const field of categoryFields) {
try {
await directusRequest('fields/tech_stack_categories', 'POST', field);
console.log(`${field.field}`);
} catch (error) {
console.log(` ⚠️ ${field.field} (might already exist)`);
}
}
// Fields for tech_stack_categories_translations
console.log('\n2⃣ Fields for tech_stack_categories_translations:');
const translationFields = [
{
field: 'tech_stack_categories_id',
type: 'uuid',
meta: { hidden: true },
schema: {}
},
{
field: 'languages_code',
type: 'string',
meta: { interface: 'select-dropdown-m2o' },
schema: {}
},
{
field: 'name',
type: 'string',
meta: {
interface: 'input',
note: 'Translated category name'
},
schema: {}
}
];
for (const field of translationFields) {
try {
await directusRequest('fields/tech_stack_categories_translations', 'POST', field);
console.log(`${field.field}`);
} catch (error) {
console.log(` ⚠️ ${field.field} (might already exist)`);
}
}
// Fields for tech_stack_items
console.log('\n3⃣ Fields for tech_stack_items:');
const itemFields = [
{
field: 'sort',
type: 'integer',
meta: { interface: 'input', hidden: true },
schema: {}
},
{
field: 'category',
type: 'uuid',
meta: { interface: 'select-dropdown-m2o' },
schema: {}
},
{
field: 'name',
type: 'string',
meta: {
interface: 'input',
note: 'Technology name (e.g. Next.js, Docker)'
},
schema: { is_nullable: false }
},
{
field: 'url',
type: 'string',
meta: {
interface: 'input',
note: 'Official website (optional)'
},
schema: {}
},
{
field: 'icon_url',
type: 'string',
meta: {
interface: 'input',
note: 'Custom icon URL (optional)'
},
schema: {}
},
{
field: 'date_created',
type: 'timestamp',
meta: { special: ['date-created'], interface: 'datetime', readonly: true, hidden: true },
schema: {}
},
{
field: 'date_updated',
type: 'timestamp',
meta: { special: ['date-updated'], interface: 'datetime', readonly: true, hidden: true },
schema: {}
}
];
for (const field of itemFields) {
try {
await directusRequest('fields/tech_stack_items', 'POST', field);
console.log(`${field.field}`);
} catch (error) {
console.log(` ⚠️ ${field.field} (might already exist)`);
}
}
}
async function createRelations() {
console.log('\n🔗 Creating Relations...\n');
const relations = [
{
collection: 'tech_stack_categories_translations',
field: 'tech_stack_categories_id',
related_collection: 'tech_stack_categories',
meta: {
one_field: 'translations',
sort_field: null,
one_deselect_action: 'delete'
},
schema: { on_delete: 'CASCADE' }
},
{
collection: 'tech_stack_categories_translations',
field: 'languages_code',
related_collection: 'languages',
meta: {
one_field: null,
sort_field: null,
one_deselect_action: 'nullify'
},
schema: { on_delete: 'SET NULL' }
},
{
collection: 'tech_stack_items',
field: 'category',
related_collection: 'tech_stack_categories',
meta: {
one_field: 'items',
sort_field: 'sort',
one_deselect_action: 'nullify'
},
schema: { on_delete: 'SET NULL' }
}
];
for (let i = 0; i < relations.length; i++) {
try {
await directusRequest('relations', 'POST', relations[i]);
console.log(` ✅ Relation ${i + 1}/${relations.length}`);
} catch (error) {
console.log(` ⚠️ Relation ${i + 1}/${relations.length} (might already exist)`);
}
}
}
async function main() {
console.log('\n╔════════════════════════════════════════╗');
console.log('║ Directus Tech Stack Setup via API ║');
console.log('╚════════════════════════════════════════╝\n');
try {
await ensureLanguages();
await createTechStackCollections();
await createFields();
await createRelations();
console.log('\n╔════════════════════════════════════════╗');
console.log('║ ✅ Setup Complete! ║');
console.log('╚════════════════════════════════════════╝\n');
console.log('🎉 Tech Stack Collections sind bereit!\n');
console.log('Nächste Schritte:');
console.log(' 1. Besuche: https://cms.dk0.dev/admin/content/tech_stack_categories');
console.log(' 2. Führe aus: node scripts/migrate-tech-stack-to-directus.js');
console.log(' 3. Verifiziere die Daten im Directus Admin Panel\n');
} catch (error) {
console.error('\n❌ Setup failed:', error);
console.error('\nTroubleshooting:');
console.error(' - Überprüfe DIRECTUS_URL und DIRECTUS_STATIC_TOKEN in .env');
console.error(' - Stelle sicher, dass der Token Admin-Rechte hat');
console.error(' - Prüfe ob Directus erreichbar ist: curl ' + DIRECTUS_URL);
process.exit(1);
}
}
main();