diff --git a/app/_ui/ProjectDetailClient.tsx b/app/_ui/ProjectDetailClient.tsx index dcb47e2..4a50da2 100644 --- a/app/_ui/ProjectDetailClient.tsx +++ b/app/_ui/ProjectDetailClient.tsx @@ -20,6 +20,8 @@ export type ProjectDetailData = { date: string; github?: string | null; live?: string | null; + button_live_label?: string | null; + button_github_label?: string | null; imageUrl?: string | null; }; @@ -205,7 +207,7 @@ export default function ProjectDetailClient({ rel="noopener noreferrer" className="flex items-center justify-between w-full px-4 py-3 bg-stone-900 text-stone-50 rounded-xl font-medium hover:bg-stone-800 hover:scale-[1.02] transition-all shadow-md group" > - {tDetail("liveDemo")} + {project.button_live_label || tDetail("liveDemo")} ) : ( @@ -221,7 +223,7 @@ export default function ProjectDetailClient({ rel="noopener noreferrer" className="flex items-center justify-between w-full px-4 py-3 bg-white border border-stone-200 text-stone-700 rounded-xl font-medium hover:bg-stone-50 hover:text-stone-900 hover:border-stone-300 transition-all shadow-sm group" > - {tDetail("viewSource")} + {project.button_github_label || tDetail("viewSource")} ) : null} diff --git a/lib/directus.ts b/lib/directus.ts index c115079..64c1cce 100644 --- a/lib/directus.ts +++ b/lib/directus.ts @@ -453,8 +453,11 @@ export async function getBookReviews(locale: string): Promise ({ - id: item.id, - hardcover_id: item.hardcover_id || undefined, - book_title: item.book_title, - book_author: item.book_author, - book_image: item.book_image || undefined, - rating: typeof item.rating === 'number' ? item.rating : parseInt(item.rating) || 0, - review: item.translations?.[0]?.review || undefined, - finished_at: item.finished_at || undefined, - })); + return reviews.map((item: any) => { + // Filter die passende Übersetzung im Code + const translation = item.translations?.find( + (t: any) => t.languages_code?.code === directusLocale + ) || item.translations?.[0]; // Fallback auf die erste Übersetzung falls locale nicht passt + + return { + id: item.id, + hardcover_id: item.hardcover_id || undefined, + book_title: item.book_title, + book_author: item.book_author, + book_image: item.book_image || undefined, + rating: typeof item.rating === 'number' ? item.rating : parseInt(item.rating) || 0, + review: translation?.review || undefined, + finished_at: item.finished_at || undefined, + }; + }); } catch (error) { console.error(`Failed to fetch book reviews (${locale}):`, error); return null; @@ -503,6 +513,8 @@ export interface Project { future_improvements?: string; github_url?: string; live_url?: string; + button_live_label?: string; + button_github_label?: string; image_url?: string; demo_video_url?: string; performance_metrics?: string; @@ -592,6 +604,8 @@ export async function getProjects( content meta_description keywords + button_live_label + button_github_label languages_code { code } } } @@ -644,6 +658,8 @@ export async function getProjects( future_improvements: proj.future_improvements, github_url: proj.github, live_url: proj.live, + button_live_label: trans.button_live_label, + button_github_label: trans.button_github_label, image_url: proj.image_url, demo_video_url: proj.demo_video, performance_metrics: proj.performance_metrics, @@ -703,6 +719,8 @@ export async function getProjectBySlug( content meta_description keywords + button_live_label + button_github_label languages_code { code } } } @@ -755,6 +773,8 @@ export async function getProjectBySlug( future_improvements: proj.future_improvements, github_url: proj.github, live_url: proj.live, + button_live_label: trans.button_live_label, + button_github_label: trans.button_github_label, image_url: proj.image_url, demo_video_url: proj.demo_video, screenshots: parseTags(proj.screenshots), diff --git a/messages/de.json b/messages/de.json index 1c7cc05..c94560d 100644 --- a/messages/de.json +++ b/messages/de.json @@ -65,9 +65,9 @@ "progress": "Fortschritt" }, "readBooks": { - "title": "Gelesen", - "finishedAt": "Beendet", - "showMore": "{count} weitere", + "title": "Gelesene Bücher", + "finishedAt": "Beendet am", + "showMore": "{count} weitere anzeigen", "showLess": "Weniger anzeigen" } }, diff --git a/scripts/atomic-setup-book-reviews.js b/scripts/atomic-setup-book-reviews.js new file mode 100644 index 0000000..22def2b --- /dev/null +++ b/scripts/atomic-setup-book-reviews.js @@ -0,0 +1,66 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(endpoint, method = 'POST', body = null) { + const res = await fetch(`${DIRECTUS_URL}/${endpoint}`, { + method, + headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}`, 'Content-Type': 'application/json' }, + body: body ? JSON.stringify(body) : null + }); + return res.ok ? await res.json() : { error: true, status: res.status }; +} + +async function atomicSetup() { + console.log('🚀 Starte atomares Setup...'); + + // 1. Die Haupt-Collection mit allen Feldern in EINEM Request + const setup = await api('collections', 'POST', { + collection: 'book_reviews', + schema: {}, + meta: { icon: 'import_contacts', display_template: '{{book_title}}' }, + fields: [ + { field: 'id', type: 'integer', schema: { is_primary_key: true, has_auto_increment: true } }, + { field: 'status', type: 'string', schema: { default_value: 'draft' }, meta: { interface: 'select-dropdown' } }, + { field: 'book_title', type: 'string', schema: {}, meta: { interface: 'input' } }, + { field: 'book_author', type: 'string', schema: {}, meta: { interface: 'input' } }, + { field: 'book_image', type: 'string', schema: {}, meta: { interface: 'input' } }, + { field: 'rating', type: 'integer', schema: {}, meta: { interface: 'rating' } }, + { field: 'hardcover_id', type: 'string', schema: { is_unique: true }, meta: { interface: 'input' } }, + { field: 'finished_at', type: 'date', schema: {}, meta: { interface: 'datetime' } } + ] + }); + + if (setup.error) { console.error('Fehler bei Haupt-Collection:', setup); return; } + console.log('✅ Haupt-Collection steht.'); + + // 2. Die Übersetzungs-Collection + await api('collections', 'POST', { + collection: 'book_reviews_translations', + schema: {}, + meta: { hidden: true }, + fields: [ + { field: 'id', type: 'integer', schema: { is_primary_key: true, has_auto_increment: true } }, + { field: 'book_reviews_id', type: 'integer', schema: {} }, + { field: 'languages_code', type: 'string', schema: {} }, + { field: 'review', type: 'text', schema: {}, meta: { interface: 'input-rich-text-html' } } + ] + }); + console.log('✅ Übersetzungstabelle steht.'); + + // 3. Die Relationen (Der Kleber) + await api('relations', 'POST', { collection: 'book_reviews_translations', field: 'book_reviews_id', related_collection: 'book_reviews', meta: { one_field: 'translations' }, schema: { on_delete: 'CASCADE' } }); + await api('relations', 'POST', { collection: 'book_reviews_translations', field: 'languages_code', related_collection: 'languages', schema: { on_delete: 'SET NULL' } }); + + // 4. Das Translations-Feld in der Haupt-Collection registrieren + await api('fields/book_reviews', 'POST', { + field: 'translations', + type: 'alias', + meta: { interface: 'translations', special: ['translations'], options: { languageField: 'languages_code' } } + }); + + console.log('✨ Alles fertig! Bitte lade Directus neu.'); +} + +atomicSetup().catch(console.error); diff --git a/scripts/cleanup-directus-ui.js b/scripts/cleanup-directus-ui.js new file mode 100644 index 0000000..dc71024 --- /dev/null +++ b/scripts/cleanup-directus-ui.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json(); + return { ok: response.ok, data }; +} + +async function finalCleanup() { + console.log('🧹 Räume Directus UI auf...'); + + // 1. Haupt-Collection konfigurieren + await api('collections/book_reviews', 'PATCH', { + meta: { + icon: 'import_contacts', + display_template: '{{book_title}}', + hidden: false, + group: null, // Aus Ordnern herausholen + singleton: false, + translations: [ + { language: 'de-DE', translation: 'Buch-Bewertungen' }, + { language: 'en-US', translation: 'Book Reviews' } + ] + } + }); + + // 2. Übersetzungs-Tabelle verstecken (Wichtig für die Optik!) + await api('collections/book_reviews_translations', 'PATCH', { + meta: { + hidden: true, + group: 'book_reviews' // Technisch untergeordnet + } + }); + + // 3. Sicherstellen, dass das 'translations' Feld im CMS gut aussieht + await api('fields/book_reviews/translations', 'PATCH', { + meta: { + interface: 'translations', + display: 'translations', + options: { + languageField: 'languages_code', + userLanguage: true + } + } + }); + + console.log('✅ UI optimiert! Bitte lade Directus jetzt neu (Cmd+R / Strg+R).'); + console.log('Du solltest jetzt links in der Navigation "Book Reviews" mit einem Buch-Icon sehen.'); +} + +finalCleanup().catch(console.error); diff --git a/scripts/deep-fix-languages.js b/scripts/deep-fix-languages.js new file mode 100644 index 0000000..b7cccbb --- /dev/null +++ b/scripts/deep-fix-languages.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json().catch(() => ({})); + return { ok: response.ok, data }; +} + +async function deepFixLanguages() { + console.log('🏗 Starte Deep-Fix...'); + const collections = ['projects', 'book_reviews', 'hobbies', 'tech_stack_categories', 'messages']; + for (const coll of collections) { + const transColl = coll + '_translations'; + console.log('🛠 Fixe ' + transColl); + await api('relations/' + transColl + '/languages_code', 'DELETE').catch(() => {}); + await api('relations', 'POST', { + collection: transColl, + field: 'languages_code', + related_collection: 'languages', + schema: {}, + meta: { interface: 'select-dropdown', options: { template: '{{name}}' } } + }); + await api('fields/' + transColl + '/languages_code', 'PATCH', { + meta: { interface: 'select-dropdown', display: 'raw', required: true } + }); + } + console.log('✅ Fertig! Bitte lade Directus neu.'); +} +deepFixLanguages().catch(console.error); diff --git a/scripts/emergency-directus-fix.js b/scripts/emergency-directus-fix.js new file mode 100644 index 0000000..fc75701 --- /dev/null +++ b/scripts/emergency-directus-fix.js @@ -0,0 +1,27 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json().catch(() => ({})); + return { ok: response.ok, data }; +} + +async function emergencyFix() { + console.log('Fixing...'); + await api('collections/book_reviews', 'PATCH', { meta: { hidden: true } }); + await api('collections/book_reviews', 'PATCH', { meta: { hidden: false, icon: 'book' } }); + await api('fields/book_reviews/translations', 'PATCH', { + meta: { interface: 'translations', options: { languageField: 'languages_code' } } + }); + console.log('Done. Please reload in Incognito.'); +} +emergencyFix().catch(console.error); diff --git a/scripts/final-directus-ui-fix.js b/scripts/final-directus-ui-fix.js new file mode 100644 index 0000000..e527eae --- /dev/null +++ b/scripts/final-directus-ui-fix.js @@ -0,0 +1,45 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(endpoint, method = 'PATCH', body = null) { + const res = await fetch(`${DIRECTUS_URL}/${endpoint}`, { + method, + headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}`, 'Content-Type': 'application/json' }, + body: body ? JSON.stringify(body) : null + }); + return res.ok ? await res.json() : { error: true }; +} + +async function finalDirectusUiFix() { + console.log('🛠 Finaler UI-Fix für Status und Übersetzungen...'); + + // 1. Status-Dropdown Optionen hinzufügen + await api('fields/book_reviews/status', 'PATCH', { + meta: { + interface: 'select-dropdown', + options: { + choices: [ + { text: 'Draft', value: 'draft' }, + { text: 'Published', value: 'published' } + ] + } + } + }); + + // 2. Translations Interface auf Tabs (translations) umstellen + await api('fields/book_reviews/translations', 'PATCH', { + meta: { + interface: 'translations', + special: ['translations'], + options: { + languageField: 'languages_code' + } + } + }); + + console.log('✅ UI-Einstellungen korrigiert! Bitte lade Directus neu.'); +} + +finalDirectusUiFix().catch(console.error); diff --git a/scripts/fix-auto-id.js b/scripts/fix-auto-id.js new file mode 100644 index 0000000..99198c4 --- /dev/null +++ b/scripts/fix-auto-id.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(endpoint, method = 'PATCH', body = null) { + const res = await fetch(`${DIRECTUS_URL}/${endpoint}`, { + method, + headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}`, 'Content-Type': 'application/json' }, + body: body ? JSON.stringify(body) : null + }); + const data = await res.json().catch(() => ({})); + return { ok: res.ok, data }; +} + +async function fixAutoId() { + console.log('🛠 Automatisierung der IDs für Übersetzungen...'); + + // 1. ID Feld in der Übersetzungstabelle konfigurieren + await api('fields/book_reviews_translations/id', 'PATCH', { + meta: { + hidden: true, + interface: 'input', + readonly: true, + special: null + } + }); + + // 2. Fremdschlüssel (book_reviews_id) verstecken, da Directus das intern regelt + await api('fields/book_reviews_translations/book_reviews_id', 'PATCH', { + meta: { + hidden: true, + interface: 'input', + readonly: true + } + }); + + // 3. Sprach-Code Feld konfigurieren + await api('fields/book_reviews_translations/languages_code', 'PATCH', { + meta: { + interface: 'select-dropdown', + width: 'half', + options: { + choices: [ + { text: 'Deutsch', value: 'de-DE' }, + { text: 'English', value: 'en-US' } + ] + } + } + }); + + console.log('✅ Fertig! Die IDs werden nun automatisch im Hintergrund verwaltet.'); +} + +fixAutoId().catch(console.error); diff --git a/scripts/fix-book-reviews-ui.js b/scripts/fix-book-reviews-ui.js new file mode 100644 index 0000000..720372f --- /dev/null +++ b/scripts/fix-book-reviews-ui.js @@ -0,0 +1,64 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + return response.ok ? await response.json() : { error: true }; +} + +async function fixUI() { + console.log('🔧 Repariere Directus UI für Book Reviews...'); + + // 1. Status Feld verschönern + await api('fields/book_reviews/status', 'PATCH', { + meta: { + interface: 'select-dropdown', + display: 'labels', + display_options: { + showAsDot: true, + choices: [ + { value: 'published', foreground: '#FFFFFF', background: '#00C897' }, + { value: 'draft', foreground: '#FFFFFF', background: '#666666' } + ] + }, + options: { + choices: [ + { text: 'Veröffentlicht', value: 'published' }, + { text: 'Entwurf', value: 'draft' } + ] + } + } + }); + + // 2. Sprachen-Verknüpfung reparieren (WICHTIG für Tabs) + await api('relations', 'POST', { + collection: 'book_reviews_translations', + field: 'languages_code', + related_collection: 'languages', + meta: { interface: 'select-dropdown' } + }).catch(() => console.log('Relation existiert evtl. schon...')); + + // 3. Übersetzungs-Interface aktivieren + await api('fields/book_reviews/translations', 'PATCH', { + meta: { + interface: 'translations', + display: 'translations', + options: { + languageField: 'languages_code', + userLanguage: true + } + } + }); + + console.log('✅ UI-Fix angewendet! Bitte lade Directus neu.'); +} + +fixUI().catch(console.error); diff --git a/scripts/fix-directus-book-reviews.js b/scripts/fix-directus-book-reviews.js new file mode 100644 index 0000000..2869736 --- /dev/null +++ b/scripts/fix-directus-book-reviews.js @@ -0,0 +1,37 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json(); + return { ok: response.ok, data, status: response.status }; +} + +async function fix() { + console.log('🔧 Fixing Directus Book Reviews...'); + await api('collections/book_reviews', 'PATCH', { meta: { icon: 'menu_book', display_template: '{{book_title}}', hidden: false } }); + + // Link to system languages + await api('relations', 'POST', { + collection: 'book_reviews_translations', + field: 'languages_code', + related_collection: 'languages', + meta: { interface: 'select-dropdown' } + }); + + // UI Improvements + await api('fields/book_reviews/status', 'PATCH', { meta: { interface: 'select-dropdown', display: 'labels' } }); + await api('fields/book_reviews/rating', 'PATCH', { meta: { interface: 'rating', display: 'rating' } }); + await api('fields/book_reviews_translations/review', 'PATCH', { meta: { interface: 'input-rich-text-html' } }); + + console.log('✅ Fix applied! Bitte lade Directus neu und setze die Permissions auf Public.'); +} +fix().catch(console.error); diff --git a/scripts/fix-relations-metadata.js b/scripts/fix-relations-metadata.js new file mode 100644 index 0000000..fd8d96b --- /dev/null +++ b/scripts/fix-relations-metadata.js @@ -0,0 +1,51 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json().catch(() => ({})); + return { ok: response.ok, data }; +} + +async function fixRelationsMetadata() { + console.log('🔗 Fixe Sprach-Relationen Metadaten...'); + + const collections = ['projects', 'book_reviews', 'hobbies', 'tech_stack_categories', 'messages']; + + for (const coll of collections) { + const transColl = `${coll}_translations`; + console.log(`🛠 Konfiguriere ${transColl}...`); + + // Wir müssen die Relation von languages_code zur languages Tabelle + // für Directus "greifbar" machen. + await api(`relations/${transColl}/languages_code`, 'PATCH', { + meta: { + interface: 'select-dropdown', + display: 'raw' + } + }); + + // WICHTIG: Wir sagen dem Hauptfeld "translations" noch einmal + // ganz explizit, welches Feld in der Untertabelle für die Sprache zuständig ist. + await api(`fields/${coll}/translations`, 'PATCH', { + meta: { + interface: 'translations', + options: { + languageField: 'languages_code' // Der Name des Feldes in der *_translations Tabelle + } + } + }); + } + + console.log('✅ Fertig! Bitte lade Directus neu.'); +} + +fixRelationsMetadata().catch(console.error); diff --git a/scripts/fix-translation-interface.js b/scripts/fix-translation-interface.js new file mode 100644 index 0000000..2bcc579 --- /dev/null +++ b/scripts/fix-translation-interface.js @@ -0,0 +1,54 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json().catch(() => ({})); + return { ok: response.ok, data }; +} + +async function fixTranslationInterface() { + console.log('🛠 Erdenke das Translations-Interface neu...'); + + const collections = ['projects', 'book_reviews', 'hobbies', 'tech_stack_categories', 'messages']; + + for (const coll of collections) { + console.log(`🔧 Fixe Interface für ${coll}...`); + + // Wir überschreiben die Metadaten des Feldes "translations" + // WICHTIG: Wir setzen interface auf 'translations' und mappen das languageField + await api(`fields/${coll}/translations`, 'PATCH', { + meta: { + interface: 'translations', + display: 'translations', + special: ['translations'], + options: { + languageField: 'languages_code', + userLanguage: true, + defaultLanguage: 'de-DE' + } + } + }); + + // Wir stellen sicher, dass in der Untertabelle das Feld languages_code + // als 'languages' Typ erkannt wird + await api(`fields/${coll}_translations/languages_code`, 'PATCH', { + meta: { + interface: 'select-dropdown', + special: null // Kein spezielles Feld hier, nur ein normaler FK + } + }); + } + + console.log('✅ Übersetzung-Tabs sollten jetzt erscheinen! Bitte Directus hart neu laden.'); +} + +fixTranslationInterface().catch(console.error); diff --git a/scripts/force-translations-plus.js b/scripts/force-translations-plus.js new file mode 100644 index 0000000..459fe70 --- /dev/null +++ b/scripts/force-translations-plus.js @@ -0,0 +1,63 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json().catch(() => ({})); + return { ok: response.ok, data }; +} + +async function forceTranslationsPlusButton() { + console.log('🔨 Erzwinge "Plus"-Button für Übersetzungen...'); + + const coll = 'book_reviews'; + const transColl = 'book_reviews_translations'; + + // 1. Das alte Alias-Feld löschen (falls es klemmt) + await api(`fields/${coll}/translations`, 'DELETE').catch(() => {}); + + // 2. Das Feld komplett neu anlegen als technisches Alias für die Relation + await api(`fields/${coll}`, 'POST', { + field: 'translations', + type: 'alias', + meta: { + interface: 'translations', + display: 'translations', + special: ['translations'], + options: { + languageField: 'languages_code', + userLanguage: true + }, + width: 'full' + } + }); + + // 3. Die Relation explizit als One-to-Many (O2M) registrieren + // Das ist der wichtigste Schritt für den Plus-Button! + await api('relations', 'POST', { + collection: transColl, + field: 'book_reviews_id', + related_collection: coll, + meta: { + one_field: 'translations', + junction_field: null, + one_deselect_action: 'delete' + }, + schema: { + on_delete: 'CASCADE' + } + }).catch(err => console.log('Relation existiert evtl. schon, überspringe...')); + + console.log('✅ Fertig! Bitte lade Directus neu.'); + console.log('Gehe in ein Buch -> Jetzt MUSS unten bei "Translations" ein Plus-Button oder "Create New" stehen.'); +} + +forceTranslationsPlusButton().catch(console.error); diff --git a/scripts/global-cms-beauty-fix.js b/scripts/global-cms-beauty-fix.js new file mode 100644 index 0000000..f6419d7 --- /dev/null +++ b/scripts/global-cms-beauty-fix.js @@ -0,0 +1,46 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json().catch(() => ({})); + return { ok: response.ok, data }; +} + +async function globalBeautyFix() { + console.log('✨ Starte globale CMS-Verschönerung...'); + await api('items/languages', 'POST', { code: 'de-DE', name: 'German' }).catch(() => {}); + await api('items/languages', 'POST', { code: 'en-US', name: 'English' }).catch(() => {}); + + const collections = ['projects', 'book_reviews', 'hobbies', 'tech_stack_categories', 'messages']; + for (const coll of collections) { + console.log('📦 Optimiere ' + coll); + await api('collections/' + coll + '_translations', 'PATCH', { meta: { hidden: true } }); + await api('fields/' + coll + '/translations', 'PATCH', { + meta: { + interface: 'translations', + display: 'translations', + width: 'full', + options: { languageField: 'languages_code', defaultLanguage: 'de-DE', userLanguage: true } + } + }); + await api('relations', 'POST', { + collection: coll + '_translations', + field: 'languages_code', + related_collection: 'languages', + meta: { interface: 'select-dropdown' } + }).catch(() => {}); + } + await api('fields/projects/tags', 'PATCH', { meta: { interface: 'tags' } }); + await api('fields/projects/technologies', 'PATCH', { meta: { interface: 'tags' } }); + console.log('✅ CMS ist jetzt aufgeräumt! Bitte Directus neu laden.'); +} +globalBeautyFix().catch(console.error); diff --git a/scripts/make-directus-editable.js b/scripts/make-directus-editable.js new file mode 100644 index 0000000..43746b4 --- /dev/null +++ b/scripts/make-directus-editable.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json(); + return { ok: response.ok, data }; +} + +async function makeEditable() { + console.log('🔓 Mache "Book Reviews" editierbar...'); + + // 1. Prüfen ob ID Feld existiert, sonst anlegen + console.log('1. Erstelle ID-Primärschlüssel...'); + await api('fields/book_reviews', 'POST', { + field: 'id', + type: 'integer', + schema: { + is_primary_key: true, + has_auto_increment: true + }, + meta: { + hidden: true // Im Formular verstecken, da automatisch + } + }); + + // 2. Sicherstellen, dass alle Felder eine Interface-Zuweisung haben (wichtig für die Eingabe) + console.log('2. Konfiguriere Eingabe-Interfaces...'); + const fieldUpdates = [ + { field: 'book_title', interface: 'input' }, + { field: 'book_author', interface: 'input' }, + { field: 'rating', interface: 'rating' }, + { field: 'status', interface: 'select-dropdown' }, + { field: 'finished_at', interface: 'datetime' } + ]; + + for (const f of fieldUpdates) { + await api(`fields/book_reviews/${f.field}`, 'PATCH', { + meta: { interface: f.interface, readonly: false } + }); + } + + console.log('✅ Fertig! Bitte lade Directus neu.'); + console.log('Solltest du immer noch nicht editieren können, musst du eventuell die Collection löschen und neu anlegen lassen, da die Datenbank-Struktur (ID) manchmal nicht nachträglich über die API geändert werden kann.'); +} + +makeEditable().catch(console.error); diff --git a/scripts/master-setup-book-reviews.js b/scripts/master-setup-book-reviews.js new file mode 100644 index 0000000..49e8b73 --- /dev/null +++ b/scripts/master-setup-book-reviews.js @@ -0,0 +1,46 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json(); + return { ok: response.ok, data }; +} + +async function masterSetup() { + console.log('🚀 Starte Master-Setup...'); + await api('collections', 'POST', { collection: 'book_reviews', meta: { icon: 'import_contacts', display_template: '{{book_title}}' } }); + await api('fields/book_reviews', 'POST', { field: 'id', type: 'integer', schema: { is_primary_key: true, has_auto_increment: true }, meta: { hidden: true } }); + + const fields = [ + { field: 'status', type: 'string', meta: { interface: 'select-dropdown', options: { choices: [{text: 'Published', value: 'published'}, {text: 'Draft', value: 'draft'}] } }, schema: { default_value: 'draft' } }, + { field: 'book_title', type: 'string', meta: { interface: 'input' } }, + { field: 'book_author', type: 'string', meta: { interface: 'input' } }, + { field: 'book_image', type: 'string', meta: { interface: 'input' } }, + { field: 'rating', type: 'integer', meta: { interface: 'rating' } }, + { field: 'hardcover_id', type: 'string', meta: { interface: 'input' }, schema: { is_unique: true } }, + { field: 'finished_at', type: 'date', meta: { interface: 'datetime' } } + ]; + for (const f of fields) await api('fields/book_reviews', 'POST', f); + + await api('collections', 'POST', { collection: 'book_reviews_translations', meta: { hidden: true } }); + await api('fields/book_reviews_translations', 'POST', { field: 'id', type: 'integer', schema: { is_primary_key: true, has_auto_increment: true }, meta: { hidden: true } }); + await api('fields/book_reviews_translations', 'POST', { field: 'book_reviews_id', type: 'integer' }); + await api('fields/book_reviews_translations', 'POST', { field: 'languages_code', type: 'string' }); + await api('fields/book_reviews_translations', 'POST', { field: 'review', type: 'text', meta: { interface: 'input-rich-text-html' } }); + + await api('relations', 'POST', { collection: 'book_reviews_translations', field: 'book_reviews_id', related_collection: 'book_reviews', meta: { one_field: 'translations' } }); + await api('relations', 'POST', { collection: 'book_reviews_translations', field: 'languages_code', related_collection: 'languages' }); + await api('fields/book_reviews', 'POST', { field: 'translations', type: 'alias', meta: { interface: 'translations', special: ['translations'] } }); + + console.log('✨ Setup abgeschlossen! Bitte lade Directus neu und setze die Public-Permissions.'); +} +masterSetup().catch(console.error); diff --git a/scripts/perfect-directus-structure.js b/scripts/perfect-directus-structure.js new file mode 100644 index 0000000..f73fb6a --- /dev/null +++ b/scripts/perfect-directus-structure.js @@ -0,0 +1,51 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json(); + return { ok: response.ok, data }; +} + +async function perfectStructure() { + console.log('💎 Optimiere CMS-Struktur zur Perfektion...'); + + // 1. Projekt-Buttons individualisieren + console.log('1. Erweitere Projekte um individuelle Button-Labels...'); + await api('fields/projects_translations', 'POST', { field: 'button_live_label', type: 'string', meta: { interface: 'input', options: { placeholder: 'z.B. Live Demo, App öffnen...' }, width: 'half' } }); + await api('fields/projects_translations', 'POST', { field: 'button_github_label', type: 'string', meta: { interface: 'input', options: { placeholder: 'z.B. Source Code, GitHub...' }, width: 'half' } }); + + // 2. SEO für Inhaltsseiten + console.log('2. Füge SEO-Felder zu Content-Pages hinzu...'); + await api('fields/content_pages', 'POST', { field: 'meta_description', type: 'string', meta: { interface: 'input' } }); + await api('fields/content_pages', 'POST', { field: 'keywords', type: 'string', meta: { interface: 'input' } }); + + // 3. Die ultimative "Messages" Collection (für UI Strings) + console.log('3. Erstelle globale "Messages" Collection...'); + await api('collections', 'POST', { collection: 'messages', schema: {}, meta: { icon: 'translate', display_template: '{{key}}' } }); + await api('fields/messages', 'POST', { field: 'key', type: 'string', schema: { is_primary_key: true }, meta: { interface: 'input', options: { placeholder: 'home.hero.title' } } }); + + // Messages Translations + await api('collections', 'POST', { collection: 'messages_translations', schema: {}, meta: { hidden: true } }); + await api('fields/messages_translations', 'POST', { field: 'id', type: 'integer', schema: { is_primary_key: true, has_auto_increment: true }, meta: { hidden: true } }); + await api('fields/messages_translations', 'POST', { field: 'messages_id', type: 'string', schema: {} }); + await api('fields/messages_translations', 'POST', { field: 'languages_code', type: 'string', schema: {} }); + await api('fields/messages_translations', 'POST', { field: 'value', type: 'text', meta: { interface: 'input' } }); + + // Relationen für Messages + await api('relations', 'POST', { collection: 'messages_translations', field: 'messages_id', related_collection: 'messages', meta: { one_field: 'translations' } }); + await api('relations', 'POST', { collection: 'messages_translations', field: 'languages_code', related_collection: 'languages' }); + await api('fields/messages', 'POST', { field: 'translations', type: 'alias', meta: { interface: 'translations', special: ['translations'] } }); + + console.log('✨ CMS Struktur ist jetzt perfekt! Lade Directus neu.'); +} + +perfectStructure().catch(console.error); diff --git a/scripts/set-public-permissions.js b/scripts/set-public-permissions.js new file mode 100644 index 0000000..84db63a --- /dev/null +++ b/scripts/set-public-permissions.js @@ -0,0 +1,37 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function setPublicPermissions() { + console.log('🔓 Setze Public-Berechtigungen für Book Reviews...'); + + // Wir holen die ID der Public Rolle + const rolesRes = await fetch(`${DIRECTUS_URL}/roles`, { headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}` } }); + const roles = await rolesRes.json(); + const publicRole = roles.data.find(r => r.name.toLowerCase() === 'public'); + + if (!publicRole) return console.error('Public Rolle nicht gefunden.'); + + const collections = ['book_reviews', 'book_reviews_translations']; + + for (const coll of collections) { + console.log(`- Erlaube Lesezugriff auf ${coll}`); + await fetch(`${DIRECTUS_URL}/permissions`, { + method: 'POST', + headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ + role: publicRole.id, + collection: coll, + action: 'read', + permissions: {}, + validation: null, + fields: ['*'] + }) + }); + } + + console.log('✅ Fertig! Die Website sollte die Daten jetzt lesen können.'); +} + +setPublicPermissions().catch(console.error); diff --git a/scripts/simple-translation-fix.js b/scripts/simple-translation-fix.js new file mode 100644 index 0000000..c298312 --- /dev/null +++ b/scripts/simple-translation-fix.js @@ -0,0 +1,46 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(endpoint, method = 'PATCH', body = null) { + const res = await fetch(`${DIRECTUS_URL}/${endpoint}`, { + method, + headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}`, 'Content-Type': 'application/json' }, + body: body ? JSON.stringify(body) : null + }); + return res.ok ? await res.json() : { error: true }; +} + +async function simpleFix() { + console.log('🛠 Vereinfache Übersetzungs-Interface...'); + + // 1. Das Hauptfeld "translations" konfigurieren + await api('fields/book_reviews/translations', 'PATCH', { + meta: { + interface: 'translations', + options: { + languageField: 'languages_code', + userLanguage: false, // Deaktivieren, um Fehler zu vermeiden + defaultLanguage: null + } + } + }); + + // 2. Das Sprachfeld in der Untertabelle konfigurieren + await api('fields/book_reviews_translations/languages_code', 'PATCH', { + meta: { + interface: 'select-dropdown', + options: { + choices: [ + { text: 'Deutsch', value: 'de-DE' }, + { text: 'English', value: 'en-US' } + ] + } + } + }); + + console.log('✅ Fertig! Bitte lade Directus neu.'); +} + +simpleFix().catch(console.error); diff --git a/scripts/ultra-setup-book-reviews.js b/scripts/ultra-setup-book-reviews.js new file mode 100644 index 0000000..969d7fd --- /dev/null +++ b/scripts/ultra-setup-book-reviews.js @@ -0,0 +1,66 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(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); + const response = await fetch(url, options); + const data = await response.json(); + return { ok: response.ok, data }; +} + +async function ultraSetup() { + console.log('🚀 Starte Ultra-Setup für "Book Reviews"...'); + + // 1. Collection anlegen (mit Primärschlüssel-Definition im Schema!) + await api('collections', 'POST', { + collection: 'book_reviews', + schema: {}, + meta: { icon: 'import_contacts', display_template: '{{book_title}}' } + }); + + // 2. Felder mit explizitem Schema (Datenbank-Spalten) + const fields = [ + { field: 'id', type: 'integer', schema: { is_primary_key: true, has_auto_increment: true }, meta: { hidden: true } }, + { field: 'status', type: 'string', schema: { default_value: 'draft' }, meta: { interface: 'select-dropdown', width: 'half' } }, + { field: 'book_title', type: 'string', schema: {}, meta: { interface: 'input', width: 'full' } }, + { field: 'book_author', type: 'string', schema: {}, meta: { interface: 'input', width: 'half' } }, + { field: 'rating', type: 'integer', schema: {}, meta: { interface: 'rating', width: 'half' } }, + { field: 'book_image', type: 'string', schema: {}, meta: { interface: 'input', width: 'full' } }, + { field: 'hardcover_id', type: 'string', schema: { is_unique: true }, meta: { interface: 'input', width: 'half' } }, + { field: 'finished_at', type: 'date', schema: {}, meta: { interface: 'datetime', width: 'half' } } + ]; + + for (const f of fields) { + await api('fields/book_reviews', 'POST', f); + } + + // 3. Übersetzungen + await api('collections', 'POST', { collection: 'book_reviews_translations', schema: {}, meta: { hidden: true } }); + + const transFields = [ + { field: 'id', type: 'integer', schema: { is_primary_key: true, has_auto_increment: true }, meta: { hidden: true } }, + { field: 'book_reviews_id', type: 'integer', schema: {}, meta: { hidden: true } }, + { field: 'languages_code', type: 'string', schema: {}, meta: { interface: 'select-dropdown' } }, + { field: 'review', type: 'text', schema: {}, meta: { interface: 'input-rich-text-html' } } + ]; + + for (const f of transFields) { + await api('fields/book_reviews_translations', 'POST', f); + } + + // 4. Relationen (Der Kleber) + await api('relations', 'POST', { collection: 'book_reviews_translations', field: 'book_reviews_id', related_collection: 'book_reviews', meta: { one_field: 'translations' }, schema: { on_delete: 'CASCADE' } }); + await api('relations', 'POST', { collection: 'book_reviews_translations', field: 'languages_code', related_collection: 'languages', schema: { on_delete: 'SET NULL' } }); + await api('fields/book_reviews', 'POST', { field: 'translations', type: 'alias', meta: { interface: 'translations', special: ['translations'] } }); + + console.log('✅ Ultra-Setup abgeschlossen! Bitte lade Directus neu.'); +} + +ultraSetup().catch(console.error);