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.
This commit is contained in:
2026-01-23 02:53:31 +01:00
parent 7604e00e0f
commit e431ff50fc
28 changed files with 5253 additions and 23 deletions

View File

@@ -0,0 +1,435 @@
#!/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();