#!/usr/bin/env node /** * Setup tech stack items in Directus * Creates tech_stack_items collection and populates it with data */ const https = require('https'); const DIRECTUS_URL = 'https://cms.dk0.dev'; const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN || ''; if (!DIRECTUS_TOKEN) { console.error('āŒ DIRECTUS_STATIC_TOKEN not set'); process.exit(1); } // Tech stack items to create const techStackItems = [ // Frontend & Mobile (category 1) { category: '1', name: 'Next.js', sort: 1 }, { category: '1', name: 'Tailwind CSS', sort: 2 }, { category: '1', name: 'Flutter', sort: 3 }, // Backend & DevOps (category 2) { category: '2', name: 'Docker Swarm', sort: 1 }, { category: '2', name: 'Traefik', sort: 2 }, { category: '2', name: 'Nginx Proxy Manager', sort: 3 }, { category: '2', name: 'Redis', sort: 4 }, // Tools & Automation (category 3) { category: '3', name: 'Git', sort: 1 }, { category: '3', name: 'CI/CD', sort: 2 }, { category: '3', name: 'n8n', sort: 3 }, { category: '3', name: 'Self-hosted Services', sort: 4 }, // Security & Admin (category 4) { category: '4', name: 'CrowdSec', sort: 1 }, { category: '4', name: 'Suricata', sort: 2 }, { category: '4', name: 'Mailcow', sort: 3 }, ]; async function makeRequest(method, endpoint, body = null) { return new Promise((resolve, reject) => { const url = new URL(endpoint, DIRECTUS_URL); const options = { hostname: url.hostname, port: 443, path: url.pathname + url.search, method: method, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${DIRECTUS_TOKEN}`, }, }; const req = https.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { const parsed = JSON.parse(data); if (res.statusCode >= 400) { reject(new Error(`HTTP ${res.statusCode}: ${data}`)); } else { resolve(parsed); } } catch (e) { resolve(data); } }); }); req.on('error', reject); if (body) req.write(JSON.stringify(body)); req.end(); }); } async function checkCollectionExists() { try { const response = await makeRequest('GET', '/api/items/tech_stack_items?limit=1'); if (response.data !== undefined) { console.log('āœ… Collection tech_stack_items already exists'); return true; } } catch (e) { if (e.message.includes('does not exist') || e.message.includes('ROUTE_NOT_FOUND')) { console.log('ā„¹ļø Collection tech_stack_items does not exist yet'); return false; } throw e; } return false; } async function addTechStackItems() { console.log(`šŸ“ Adding ${techStackItems.length} tech stack items...`); let created = 0; for (const item of techStackItems) { try { const response = await makeRequest('POST', '/api/items/tech_stack_items', { category: item.category, name: item.name, sort: item.sort, status: 'published' }); if (response.data) { created++; console.log(` āœ… Created: ${item.name} (category ${item.category})`); } } catch (error) { console.error(` āŒ Failed to create "${item.name}":`, error.message); } } console.log(`\nāœ… Successfully created ${created}/${techStackItems.length} items`); return created === techStackItems.length; } async function main() { try { console.log('šŸš€ Setting up Tech Stack in Directus...\n'); const exists = await checkCollectionExists(); if (exists) { // Count existing items const response = await makeRequest('GET', '/api/items/tech_stack_items?limit=1000'); const count = response.data?.length || 0; if (count > 0) { console.log(`āœ… Tech stack already populated with ${count} items`); return; } } // Add items await addTechStackItems(); console.log('\nāœ… Tech stack setup complete!'); } catch (error) { console.error('āŒ Error setting up tech stack:', error.message); process.exit(1); } } main();