- 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.
504 lines
12 KiB
JavaScript
504 lines
12 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Directus Projects Collection Setup via REST API
|
||
*
|
||
* Erstellt die komplette Projects Collection mit allen Feldern und Translations
|
||
*
|
||
* Usage:
|
||
* node scripts/setup-directus-projects.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) {
|
||
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 createProjectsCollections() {
|
||
console.log('\n📦 Creating Projects Collections...\n');
|
||
|
||
// 1. Create projects collection
|
||
console.log('1️⃣ Creating projects...');
|
||
try {
|
||
await directusRequest('collections', 'POST', {
|
||
collection: 'projects',
|
||
meta: {
|
||
icon: 'folder',
|
||
display_template: '{{title}}',
|
||
hidden: false,
|
||
singleton: false,
|
||
translations: [
|
||
{ language: 'en-US', translation: 'Projects' },
|
||
{ language: 'de-DE', translation: 'Projekte' }
|
||
],
|
||
sort_field: 'sort'
|
||
},
|
||
schema: {
|
||
name: 'projects'
|
||
}
|
||
});
|
||
console.log(' ✅ Collection created');
|
||
} catch (error) {
|
||
console.log(' ⚠️ Collection might already exist');
|
||
}
|
||
|
||
// 2. Create projects_translations collection
|
||
console.log('\n2️⃣ Creating projects_translations...');
|
||
try {
|
||
await directusRequest('collections', 'POST', {
|
||
collection: 'projects_translations',
|
||
meta: {
|
||
hidden: true,
|
||
icon: 'import_export'
|
||
},
|
||
schema: {
|
||
name: 'projects_translations'
|
||
}
|
||
});
|
||
console.log(' ✅ Collection created');
|
||
} catch (error) {
|
||
console.log(' ⚠️ Collection might already exist');
|
||
}
|
||
}
|
||
|
||
async function createProjectFields() {
|
||
console.log('\n🔧 Creating Project Fields...\n');
|
||
|
||
const projectFields = [
|
||
{
|
||
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: 'slug',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'URL-friendly identifier (e.g. my-portfolio-website)',
|
||
required: true
|
||
},
|
||
schema: { is_unique: true, is_nullable: false }
|
||
},
|
||
{
|
||
field: 'featured',
|
||
type: 'boolean',
|
||
meta: {
|
||
interface: 'boolean',
|
||
note: 'Show on homepage'
|
||
},
|
||
schema: { default_value: false }
|
||
},
|
||
{
|
||
field: 'category',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'select-dropdown',
|
||
options: {
|
||
choices: [
|
||
{ text: 'Web Application', value: 'Web Application' },
|
||
{ text: 'Mobile App', value: 'Mobile App' },
|
||
{ text: 'Backend Development', value: 'Backend Development' },
|
||
{ text: 'DevOps', value: 'DevOps' },
|
||
{ text: 'AI/ML', value: 'AI/ML' },
|
||
{ text: 'Other', value: 'Other' }
|
||
]
|
||
}
|
||
},
|
||
schema: { default_value: 'Web Application' }
|
||
},
|
||
{
|
||
field: 'difficulty',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'select-dropdown',
|
||
options: {
|
||
choices: [
|
||
{ text: 'Beginner', value: 'BEGINNER' },
|
||
{ text: 'Intermediate', value: 'INTERMEDIATE' },
|
||
{ text: 'Advanced', value: 'ADVANCED' },
|
||
{ text: 'Expert', value: 'EXPERT' }
|
||
]
|
||
}
|
||
},
|
||
schema: { default_value: 'INTERMEDIATE' }
|
||
},
|
||
{
|
||
field: 'date',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'Project date (e.g. "2024" or "2023-2024")'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'time_to_complete',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'e.g. "4-6 weeks"',
|
||
placeholder: '4-6 weeks'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'github',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'GitHub repository URL',
|
||
placeholder: 'https://github.com/...'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'live',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'Live demo URL',
|
||
placeholder: 'https://...'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'image_url',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'Main project image URL'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'demo_video',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'Demo video URL (YouTube, Vimeo, etc.)'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'color_scheme',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'e.g. "Dark theme with blue accents"'
|
||
},
|
||
schema: { default_value: 'Dark' }
|
||
},
|
||
{
|
||
field: 'accessibility',
|
||
type: 'boolean',
|
||
meta: {
|
||
interface: 'boolean',
|
||
note: 'Is the project accessible?'
|
||
},
|
||
schema: { default_value: true }
|
||
},
|
||
{
|
||
field: 'tags',
|
||
type: 'json',
|
||
meta: {
|
||
interface: 'tags',
|
||
note: 'Technology tags (e.g. React, Node.js, Docker)'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'technologies',
|
||
type: 'json',
|
||
meta: {
|
||
interface: 'tags',
|
||
note: 'Detailed tech stack'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'challenges',
|
||
type: 'json',
|
||
meta: {
|
||
interface: 'list',
|
||
note: 'Challenges faced during development'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'lessons_learned',
|
||
type: 'json',
|
||
meta: {
|
||
interface: 'list',
|
||
note: 'What you learned from this project'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'future_improvements',
|
||
type: 'json',
|
||
meta: {
|
||
interface: 'list',
|
||
note: 'Planned improvements'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'screenshots',
|
||
type: 'json',
|
||
meta: {
|
||
interface: 'list',
|
||
note: 'Array of screenshot URLs'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'performance',
|
||
type: 'json',
|
||
meta: {
|
||
interface: 'input-code',
|
||
options: {
|
||
language: 'json'
|
||
},
|
||
note: 'Performance metrics (lighthouse, bundle size, load time)'
|
||
},
|
||
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: {}
|
||
},
|
||
{
|
||
field: 'translations',
|
||
type: 'alias',
|
||
meta: {
|
||
special: ['translations'],
|
||
interface: 'translations',
|
||
options: { languageField: 'languages_code' }
|
||
}
|
||
}
|
||
];
|
||
|
||
console.log('Adding fields to projects:');
|
||
for (const field of projectFields) {
|
||
try {
|
||
await directusRequest('fields/projects', 'POST', field);
|
||
console.log(` ✅ ${field.field}`);
|
||
} catch (error) {
|
||
console.log(` ⚠️ ${field.field} (might already exist)`);
|
||
}
|
||
}
|
||
|
||
// Translation fields
|
||
console.log('\nAdding fields to projects_translations:');
|
||
const translationFields = [
|
||
{
|
||
field: 'projects_id',
|
||
type: 'uuid',
|
||
meta: { hidden: true },
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'languages_code',
|
||
type: 'string',
|
||
meta: { interface: 'select-dropdown-m2o' },
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'title',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'Project title',
|
||
required: true
|
||
},
|
||
schema: { is_nullable: false }
|
||
},
|
||
{
|
||
field: 'description',
|
||
type: 'text',
|
||
meta: {
|
||
interface: 'input-multiline',
|
||
note: 'Short description (1-2 sentences)'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'content',
|
||
type: 'text',
|
||
meta: {
|
||
interface: 'input-rich-text-md',
|
||
note: 'Full project content (Markdown)'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'meta_description',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'SEO meta description'
|
||
},
|
||
schema: {}
|
||
},
|
||
{
|
||
field: 'keywords',
|
||
type: 'string',
|
||
meta: {
|
||
interface: 'input',
|
||
note: 'SEO keywords (comma separated)'
|
||
},
|
||
schema: {}
|
||
}
|
||
];
|
||
|
||
for (const field of translationFields) {
|
||
try {
|
||
await directusRequest('fields/projects_translations', 'POST', field);
|
||
console.log(` ✅ ${field.field}`);
|
||
} catch (error) {
|
||
console.log(` ⚠️ ${field.field} (might already exist)`);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function createProjectRelations() {
|
||
console.log('\n🔗 Creating Relations...\n');
|
||
|
||
const relations = [
|
||
{
|
||
collection: 'projects_translations',
|
||
field: 'projects_id',
|
||
related_collection: 'projects',
|
||
meta: {
|
||
one_field: 'translations',
|
||
sort_field: null,
|
||
one_deselect_action: 'delete'
|
||
},
|
||
schema: { on_delete: 'CASCADE' }
|
||
},
|
||
{
|
||
collection: 'projects_translations',
|
||
field: 'languages_code',
|
||
related_collection: 'languages',
|
||
meta: {
|
||
one_field: null,
|
||
sort_field: null,
|
||
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 Projects Setup via API ║');
|
||
console.log('╚════════════════════════════════════════╝\n');
|
||
|
||
try {
|
||
await createProjectsCollections();
|
||
await createProjectFields();
|
||
await createProjectRelations();
|
||
|
||
console.log('\n╔════════════════════════════════════════╗');
|
||
console.log('║ ✅ Setup Complete! ║');
|
||
console.log('╚════════════════════════════════════════╝\n');
|
||
|
||
console.log('🎉 Projects Collection ist bereit!\n');
|
||
console.log('Nächste Schritte:');
|
||
console.log(' 1. Besuche: https://cms.dk0.dev/admin/content/projects');
|
||
console.log(' 2. Führe aus: node scripts/migrate-projects-to-directus.js');
|
||
console.log(' 3. Verifiziere die Daten im Directus Admin Panel\n');
|
||
|
||
} catch (error) {
|
||
console.error('\n❌ Setup failed:', error);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
main();
|