#!/usr/bin/env node /** * Räumt Directus nach dem Plakat-Redesign auf. * * Erst ausführen, wenn das Redesign auf production (dk0.dev) live ist — die alte * Seite liest diese Einträge noch. * * node scripts/directus-cleanup-redesign.mjs # Probelauf, ändert nichts * node scripts/directus-cleanup-redesign.mjs --apply # sichert, dann ändern * * Was passiert (mit --apply): * - hobbies: alle auf status "archived" (die neue Seite zeigt keine Hobby-Karten) * - tech_stack_categories: alle auf "archived" (Tech-Stack steht jetzt im Laufband) * - messages: hero.badge, hero.line1, hero.line2, about.quote.idle löschen * (überschrieben alte Hero-Texte, werden nicht mehr gelesen) * - content_pages: home-hero und home-about (beide Sprachen) auf "archived" * * Vorher wird alles Betroffene vollständig nach directus-backup-redesign-.json gesichert. * Archivieren lässt sich im Directus-Admin jederzeit rückgängig machen. */ import fs from "node:fs"; import path from "node:path"; const APPLY = process.argv.includes("--apply"); function loadEnv(file) { if (!fs.existsSync(file)) return {}; return Object.fromEntries( fs .readFileSync(file, "utf8") .split("\n") .filter((l) => /^[A-Z_]+=/.test(l)) .map((l) => { const i = l.indexOf("="); return [l.slice(0, i), l.slice(i + 1).replace(/^["']|["']$/g, "")]; }), ); } const env = { ...loadEnv(path.join(process.cwd(), ".env")), ...process.env }; const BASE = env.DIRECTUS_URL || "https://cms.dk0.dev"; const TOKEN = env.DIRECTUS_STATIC_TOKEN; if (!TOKEN) { console.error("DIRECTUS_STATIC_TOKEN fehlt (.env oder Umgebung)."); process.exit(1); } const MESSAGE_KEYS = ["hero.badge", "hero.line1", "hero.line2", "about.quote.idle"]; const PAGE_SLUGS = ["home-hero", "home-hero-de-de", "home-about", "home-about-de-de"]; async function rest(method, p, body) { const res = await fetch(BASE + p, { method, headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" }, body: body ? JSON.stringify(body) : undefined, }); if (!res.ok) throw new Error(`${method} ${p}: HTTP ${res.status} ${await res.text()}`); return res.status === 204 ? null : res.json(); } async function gql(query) { const res = await fetch(BASE + "/graphql", { method: "POST", headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" }, body: JSON.stringify({ query }), }); const json = await res.json(); if (json.errors) throw new Error(JSON.stringify(json.errors)); return json.data; } const hobbies = (await rest("GET", "/items/hobbies?fields=*,translations.*&limit=-1")).data; const categories = (await rest("GET", "/items/tech_stack_categories?fields=*,translations.*&limit=-1")).data; const messages = (await rest("GET", `/items/messages?fields=*,translations.*&limit=-1`)).data.filter((m) => MESSAGE_KEYS.includes(m.key), ); const pages = ( await gql(`{ content_pages(limit: -1) { slug locale status title content meta_description keywords } }`) ).content_pages.filter((p) => PAGE_SLUGS.includes(p.slug)); const activeHobbies = hobbies.filter((h) => h.status !== "archived"); const activeCategories = categories.filter((c) => c.status !== "archived"); const activePages = pages.filter((p) => p.status !== "archived"); console.log(`${APPLY ? "AUSFÜHREN" : "PROBELAUF"} gegen ${BASE}\n`); console.log(`hobbies archivieren: ${activeHobbies.length} (${activeHobbies.map((h) => h.key).join(", ") || "-"})`); console.log(`tech_stack_categories archivieren: ${activeCategories.length} (${activeCategories.map((c) => c.key).join(", ") || "-"})`); console.log(`messages löschen: ${messages.length} (${messages.map((m) => m.key).join(", ") || "-"})`); console.log(`content_pages archivieren: ${activePages.length} (${activePages.map((p) => p.slug).join(", ") || "-"})`); if (!APPLY) { console.log("\nNichts geändert. Mit --apply ausführen, wenn das Redesign auf production live ist."); process.exit(0); } const backupFile = path.join(process.cwd(), `directus-backup-redesign-${new Date().toISOString().replace(/[:.]/g, "-")}.json`); fs.writeFileSync(backupFile, JSON.stringify({ hobbies, categories, messages, pages }, null, 2)); console.log(`\nSicherung: ${backupFile}`); for (const h of activeHobbies) await rest("PATCH", `/items/hobbies/${h.id}`, { status: "archived" }); for (const c of activeCategories) await rest("PATCH", `/items/tech_stack_categories/${c.id}`, { status: "archived" }); for (const m of messages) await rest("DELETE", `/items/messages/${m.id}`); for (const p of activePages) { try { await rest("PATCH", `/items/content_pages/${encodeURIComponent(p.slug)}`, { status: "archived" }); } catch { // REST ist für content_pages teils gesperrt, GraphQL geht await gql(`mutation { update_content_pages_item(id: "${p.slug}", data: { status: "archived" }) { slug } }`); } } console.log("Fertig.");