From c54762532ca575abb65caacb60a88d582b059fbd Mon Sep 17 00:00:00 2001 From: Daniel Heim Date: Fri, 5 Jun 2026 05:25:41 +0000 Subject: [PATCH] Migrate CMS from Sanity to self-hosted Directus, add Impressum + Datenschutz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace src/lib/sanity.ts with src/lib/directus.ts (REST API client) - Update all 9 pages to use Directus field names and imageUrl() - Add Impressum (§5 TMG) and Datenschutz (DSGVO) pages - Update .env.example for Directus URL + token Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 8 +- src/lib/directus.ts | 79 +++++++++++++++ src/pages/aktuelles/[slug].astro | 17 ++-- src/pages/aktuelles/index.astro | 17 ++-- src/pages/datenschutz.astro | 166 +++++++++++++++++++++++++++++++ src/pages/impressum.astro | 80 +++++++++++++++ src/pages/index.astro | 34 +++---- src/pages/kontakt.astro | 22 ++-- src/pages/mitglied-werden.astro | 22 ++-- src/pages/presse.astro | 9 +- src/pages/projekte/[slug].astro | 15 ++- src/pages/projekte/index.astro | 16 +-- src/pages/ueber-uns.astro | 6 +- src/pages/unterstuetzen.astro | 18 ++-- 14 files changed, 408 insertions(+), 101 deletions(-) create mode 100644 src/lib/directus.ts create mode 100644 src/pages/datenschutz.astro create mode 100644 src/pages/impressum.astro diff --git a/.env.example b/.env.example index cff04ce..31826a2 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ -# Sanity-Projekt-ID — aus sanity.io/manage -PUBLIC_SANITY_PROJECT_ID=xxxxxxxx +# Directus CMS URL +DIRECTUS_URL=https://cms.kitafreunde-regenbogen.de -# Dataset — in der Regel "production" -PUBLIC_SANITY_DATASET=production +# Directus API token (read-only, used at build time) +DIRECTUS_TOKEN=your-token-here diff --git a/src/lib/directus.ts b/src/lib/directus.ts new file mode 100644 index 0000000..651bcfa --- /dev/null +++ b/src/lib/directus.ts @@ -0,0 +1,79 @@ +const DIRECTUS_URL = import.meta.env.DIRECTUS_URL ?? "https://cms.kitafreunde-regenbogen.de"; +const DIRECTUS_TOKEN = import.meta.env.DIRECTUS_TOKEN ?? ""; + +async function fetchDirectus(path: string): Promise { + try { + const res = await fetch(`${DIRECTUS_URL}${path}`, { + headers: DIRECTUS_TOKEN ? { Authorization: `Bearer ${DIRECTUS_TOKEN}` } : {}, + }); + if (!res.ok) return null; + const json = await res.json(); + return json.data ?? null; + } catch { + return null; + } +} + +export function imageUrl(fileId: string | null | undefined, width = 800): string { + if (!fileId) return ""; + return `${DIRECTUS_URL}/assets/${fileId}?width=${width}&format=webp`; +} + +export async function getSettings() { + return fetchDirectus<{ + contact_email: string; contact_phone: string; + iban: string; bic: string; bank_name: string; + member_fee_active: number; member_fee_supporting: number; member_fee_company: number; + stats_members: number; stats_funded: number; + social_instagram: string; social_facebook: string; + }>("/items/site_settings"); +} + +export async function getFeaturedProjects() { + const data = await fetchDirectus( + "/items/projects?filter[featured][_eq]=true&filter[status][_neq]=archived&sort=-date&limit=6" + ); + return data ?? []; +} + +export async function getAllProjects() { + const data = await fetchDirectus( + "/items/projects?filter[status][_neq]=archived&sort[]=sort&sort[]=-date" + ); + return data ?? []; +} + +export async function getProject(slug: string) { + const data = await fetchDirectus( + `/items/projects?filter[slug][_eq]=${encodeURIComponent(slug)}&limit=1` + ); + return data?.[0] ?? null; +} + +export async function getLatestPosts(limit = 6) { + const data = await fetchDirectus( + `/items/posts?filter[status][_eq]=published&sort=-published_at&limit=${limit}` + ); + return data ?? []; +} + +export async function getAllPosts() { + const data = await fetchDirectus( + "/items/posts?filter[status][_eq]=published&sort=-published_at" + ); + return data ?? []; +} + +export async function getPost(slug: string) { + const data = await fetchDirectus( + `/items/posts?filter[slug][_eq]=${encodeURIComponent(slug)}&limit=1` + ); + return data?.[0] ?? null; +} + +export async function getTeam() { + const data = await fetchDirectus( + "/items/team_members?sort[]=sort&sort[]=name" + ); + return data ?? []; +} diff --git a/src/pages/aktuelles/[slug].astro b/src/pages/aktuelles/[slug].astro index ed0d38c..ba3fde0 100644 --- a/src/pages/aktuelles/[slug].astro +++ b/src/pages/aktuelles/[slug].astro @@ -1,11 +1,10 @@ --- import Layout from "../../layouts/Layout.astro"; -import { getAllPosts, getPost } from "../../lib/sanity"; -import { toHTML } from "@portabletext/to-html"; +import { getAllPosts, getPost, imageUrl } from "../../lib/directus"; export async function getStaticPaths() { const posts = await getAllPosts(); - return posts.map((p: any) => ({ params: { slug: p.slug.current } })); + return posts.map((p: any) => ({ params: { slug: p.slug } })); } const { slug } = Astro.params; @@ -13,8 +12,8 @@ const post = await getPost(slug!); if (!post) return Astro.redirect("/aktuelles"); -const bodyHtml = post.body ? toHTML(post.body) : ""; -const dateFormatted = new Date(post.publishedAt).toLocaleDateString("de-DE", { +const bodyHtml = post.body ?? ""; +const dateFormatted = new Date(post.published_at).toLocaleDateString("de-DE", { day: "numeric", month: "long", year: "numeric", }); --- @@ -22,22 +21,22 @@ const dateFormatted = new Date(post.publishedAt).toLocaleDateString("de-DE", { - {post.coverImage?.asset?.url && ( + {post.cover_image && (
- {post.title} + {post.title}
)}
- +

{post.title}

{post.excerpt &&

{post.excerpt}

} {bodyHtml && ( diff --git a/src/pages/aktuelles/index.astro b/src/pages/aktuelles/index.astro index e1d226d..81fb451 100644 --- a/src/pages/aktuelles/index.astro +++ b/src/pages/aktuelles/index.astro @@ -1,14 +1,13 @@ --- import Layout from "../../layouts/Layout.astro"; -import { getAllPosts } from "../../lib/sanity"; +import { getAllPosts, imageUrl } from "../../lib/directus"; const posts = await getAllPosts(); const categoryLabel: Record = { news: "Vereinsnews", - projects: "Projekte", - events: "Veranstaltungen", - press: "Presse", + event: "Veranstaltung", + report: "Bericht", }; --- @@ -29,10 +28,10 @@ const categoryLabel: Record = { ) : (

{post.title}

diff --git a/src/pages/datenschutz.astro b/src/pages/datenschutz.astro new file mode 100644 index 0000000..e4e6252 --- /dev/null +++ b/src/pages/datenschutz.astro @@ -0,0 +1,166 @@ +--- +import Layout from "../layouts/Layout.astro"; +import { getSettings } from "../lib/directus"; + +const settings = await getSettings(); +--- + + +
+

Rechtliches

+

Datenschutz

+ +
+ + + +
+

2. Allgemeines zur Datenverarbeitung

+

+ Wir nehmen den Schutz deiner persönlichen Daten sehr ernst. Diese Website erhebt + und verarbeitet personenbezogene Daten nur im technisch notwendigen Umfang und + ausschließlich auf der Grundlage der gesetzlichen Bestimmungen (DSGVO, TMG). +

+

+ Wir verwenden keine Tracking- oder Analysetools, + keine Werbenetzwerke und keine Social-Media-Plugins, die automatisch Daten + erheben. Es werden keine Cookies gesetzt. +

+
+ +
+

3. Hosting

+

+ Diese Website wird auf Servern der Hetzner Online GmbH (Industriestr. 25, 91710 Gunzenhausen) + in Deutschland betrieben. Hetzner ist als Auftragsverarbeiter nach Art. 28 DSGVO vertraglich + gebunden. Beim Aufruf der Website werden durch den Webserver automatisch folgende Daten + in Server-Logfiles gespeichert: +

+
    +
  • IP-Adresse des anfragenden Rechners (anonymisiert nach kurzer Zeit)
  • +
  • Datum und Uhrzeit des Zugriffs
  • +
  • Aufgerufene Seite / Datei
  • +
  • HTTP-Statuscode
  • +
  • Übertragene Datenmenge
  • +
  • Referrer-URL
  • +
  • Browser und Betriebssystem (User-Agent)
  • +
+

+ Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse am sicheren + und fehlerfreien Betrieb der Website). Die Daten werden nicht mit anderen Datenquellen + zusammengeführt und nach spätestens 7 Tagen gelöscht. +

+
+ +
+

4. Google Fonts

+

+ Diese Website lädt Schriftarten (Pacifico, Inter) von Google Fonts + (fonts.googleapis.com). + Dabei wird deine IP-Adresse an Server von Google LLC in den USA übermittelt. + Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO; unser berechtigtes Interesse + liegt in der einheitlichen Darstellung der Website. +

+

+ Google ist unter dem EU-US Data Privacy Framework zertifiziert. Weitere + Informationen: + policies.google.com/privacy. +

+

+ Hinweis: Wir planen, alle Schriftarten künftig lokal zu betreiben, + sodass keine Daten mehr an Google übertragen werden. +

+
+ +
+

5. Kontaktaufnahme per E-Mail

+

+ Wenn du uns per E-Mail kontaktierst, werden deine Angaben (E-Mail-Adresse, + ggf. Name und Nachrichteninhalt) zum Zweck der Bearbeitung deiner Anfrage + und für den Fall von Anschlussfragen bei uns gespeichert. +

+

+ Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO (Vertragsanbahnung oder + berechtigtes Interesse). Die Daten werden nicht an Dritte weitergegeben + und nach Abschluss der Kommunikation gelöscht, sofern keine gesetzlichen + Aufbewahrungspflichten entgegenstehen. +

+

+ Das Kontaktformular auf dieser Website funktioniert als mailto-Link — es werden + keine Formulardaten auf unseren Servern gespeichert oder verarbeitet. +

+
+ +
+

6. Mitgliedschaft

+

+ Wenn du Mitglied des Kitafreunde Regenbogen e.V. wirst, verarbeiten wir die + in der Beitrittserklärung angegebenen Daten (Name, Anschrift, E-Mail, ggf. + IBAN für den Beitragseinzug) zur Verwaltung der Mitgliedschaft. +

+

+ Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung) sowie + § 26 BDSG (Vereinsmitgliedschaft). Die Daten werden für die Dauer der + Mitgliedschaft und anschließend entsprechend den gesetzlichen + Aufbewahrungsfristen gespeichert. +

+
+ +
+

7. Deine Rechte

+

Du hast jederzeit das Recht auf:

+
    +
  • Auskunft über die zu deiner Person gespeicherten Daten (Art. 15 DSGVO)
  • +
  • Berichtigung unrichtiger Daten (Art. 16 DSGVO)
  • +
  • Löschung deiner Daten (Art. 17 DSGVO)
  • +
  • Einschränkung der Verarbeitung (Art. 18 DSGVO)
  • +
  • Datenübertragbarkeit (Art. 20 DSGVO)
  • +
  • Widerspruch gegen die Verarbeitung (Art. 21 DSGVO)
  • +
+

+ Zur Ausübung deiner Rechte wende dich an: + {settings?.contact_email ? ( + {settings.contact_email} + ) : ( + [E-Mail eintragen] + )} +

+

+ Du hast außerdem das Recht, dich bei der zuständigen Datenschutz-Aufsichtsbehörde + zu beschweren. In Berlin ist das die Berliner Beauftragte für Datenschutz und + Informationsfreiheit ( + datenschutz-berlin.de). +

+
+ +
+

8. Aktualität dieser Erklärung

+

+ Diese Datenschutzerklärung ist aktuell gültig und hat den Stand Juni 2026. + Durch die Weiterentwicklung unserer Website oder aufgrund geänderter gesetzlicher + Vorgaben kann es notwendig werden, diese Datenschutzerklärung anzupassen. +

+
+ +
+
+ diff --git a/src/pages/impressum.astro b/src/pages/impressum.astro new file mode 100644 index 0000000..aba740f --- /dev/null +++ b/src/pages/impressum.astro @@ -0,0 +1,80 @@ +--- +import Layout from "../layouts/Layout.astro"; +import { getSettings } from "../lib/directus"; + +const settings = await getSettings(); +--- + + +
+

Rechtliches

+

Impressum

+ +
+ +
+

Angaben gemäß § 5 TMG

+

Kitafreunde Regenbogen e.V.

+

c/o Integrationskindertagesstätte Regenbogen

+

Keilerstraße 23

+

13503 Berlin

+
+ +
+

Vereinsregister

+

Registergericht: Amtsgericht Charlottenburg

+

Vereinsregisternummer: [VR-Nummer eintragen]

+
+ +
+

Vertreten durch den Vorstand

+

1. Vorsitzende/r: [Name eintragen]

+

Stellvertretung: [Name eintragen]

+

Kassenwart/in: [Name eintragen]

+
+ +
+

Kontakt

+ {settings?.contact_email ? ( +

E-Mail: {settings.contact_email}

+ ) : ( +

E-Mail: [E-Mail eintragen]

+ )} + {settings?.contact_phone && ( +

Telefon: {settings.contact_phone}

+ )} +
+ +
+

Gemeinnützigkeit

+

+ Der Kitafreunde Regenbogen e.V. ist als gemeinnützig anerkannt. Der letzte + Freistellungsbescheid wurde erteilt vom Finanzamt [Finanzamt eintragen], + Steuernummer [Steuernummer eintragen]. +

+

Spenden sind steuerlich absetzbar.

+
+ +
+

Inhaltlich verantwortlich

+

Inhaltlich verantwortlich gemäß § 18 Abs. 2 MStV:

+

+ [Vorstandsvorsitzende/r, Name und Anschrift] +

+
+ +
+

Haftungshinweis

+

+ Trotz sorgfältiger inhaltlicher Kontrolle übernehmen wir keine Haftung für die Inhalte + externer Links. Für den Inhalt der verlinkten Seiten sind ausschließlich deren Betreiber + verantwortlich. +

+
+ +
+
+
diff --git a/src/pages/index.astro b/src/pages/index.astro index 94ff724..7efad07 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,12 +1,10 @@ --- import Layout from "../layouts/Layout.astro"; -import { getSettings, getFeaturedProjects, getLatestPosts } from "../lib/sanity"; +import { getSettings, getFeaturedProjects, getLatestPosts, imageUrl } from "../lib/directus"; const settings = await getSettings(); const featuredProjects = await getFeaturedProjects(); const latestPosts = await getLatestPosts(3); - -const stats = settings?.stats; --- - {stats && (stats.members || stats.fundsPerYear || stats.projectsTotal) && ( + {settings && (settings.stats_members || settings.stats_funded) && (
-
- {stats.members && ( +
+ {settings.stats_members && (
-

{stats.members}

+

{settings.stats_members}

Mitglieder unterstützen den Verein

)} - {stats.fundsPerYear && ( + {settings.stats_funded && (
-

{stats.fundsPerYear.toLocaleString("de")} €

-

jährlich für die Kinder

-
- )} - {stats.projectsTotal && ( -
-

{stats.projectsTotal}

-

Projekte seit Vereinsgründung

+

{settings.stats_funded.toLocaleString("de")} €

+

geförderte Projekte

)}
@@ -115,10 +107,10 @@ const stats = settings?.stats;

Was wir gerade machen und was geplant ist.

- {(social?.instagram || social?.facebook) && ( + {(settings?.social_instagram || settings?.social_facebook) && (

Social Media

- {social.instagram && ( - Instagram + {settings.social_instagram && ( + Instagram )} - {social.facebook && ( - Facebook + {settings.social_facebook && ( + Facebook )}
@@ -60,7 +58,7 @@ const social = settings?.social; Dieses Formular öffnet deinen E-Mail-Client.

Aktives Mitglied

- ab {fees?.active ?? 12} €/Jahr + ab {settings?.member_fee_active ?? 12} €/Jahr

-

zzgl. einmalig {fees?.admissionFee ?? 5} € Aufnahmegebühr

+

zzgl. einmalig 5 € Aufnahmegebühr

  • Stimmrecht auf der Mitgliederversammlung
  • Mitgestaltung von Projekten und Zielen
  • @@ -53,7 +51,7 @@ const bank = settings?.bank;

    Fördermitglied

    - ab {fees?.supporting ?? 6} €/Jahr + ab {settings?.member_fee_supporting ?? 6} €/Jahr

    keine Aufnahmegebühr

      @@ -83,7 +81,7 @@ const bank = settings?.bank;
      {[ { step: "1", title: "Beitrittserklärung ausfüllen", text: "Als PDF ausdrucken und unterschreiben — oder im Kitabüro abholen." }, - { step: "2", title: "Beitrag überweisen", text: `Jahresbeitrag + einmalige Aufnahmegebühr (${fees?.admissionFee ?? 5} €) auf unser Vereinskonto.` }, + { step: "2", title: "Beitrag überweisen", text: "Jahresbeitrag + einmalige Aufnahmegebühr (5 €) auf unser Vereinskonto." }, { step: "3", title: "Fertig — du bist dabei.", text: "Willkommen bei den Kitafreunden. Wir freuen uns." }, ].map(({ step, title, text }) => (
      @@ -101,15 +99,15 @@ const bank = settings?.bank;
- {bank?.iban && ( + {settings?.iban && (

Bankverbindung

-
Empfänger
{bank.accountHolder}
-
IBAN
{bank.iban}
- {bank.bic &&
BIC
{bank.bic}
} - {bank.bank &&
Bank
{bank.bank}
} +
Empfänger
Kitafreunde Regenbogen e.V.
+
IBAN
{settings.iban}
+ {settings.bic &&
BIC
{settings.bic}
} + {settings.bank_name &&
Bank
{settings.bank_name}
}
Verwendung
Mitgliedsbeitrag [Dein Name]
diff --git a/src/pages/presse.astro b/src/pages/presse.astro index 2988d9d..d505628 100644 --- a/src/pages/presse.astro +++ b/src/pages/presse.astro @@ -1,9 +1,8 @@ --- import Layout from "../layouts/Layout.astro"; -import { getSettings } from "../lib/sanity"; +import { getSettings } from "../lib/directus"; const settings = await getSettings(); -const contact = settings?.contact; --- @@ -14,12 +13,12 @@ const contact = settings?.contact;

Pressekontakt

- {contact?.email ? ( + {settings?.contact_email ? (

Kitafreunde Regenbogen e.V.

Keilerstraße 23 · 13503 Berlin

- - {contact.email} + + {settings.contact_email}
) : ( diff --git a/src/pages/projekte/[slug].astro b/src/pages/projekte/[slug].astro index a2cfef2..fb568db 100644 --- a/src/pages/projekte/[slug].astro +++ b/src/pages/projekte/[slug].astro @@ -1,11 +1,10 @@ --- import Layout from "../../layouts/Layout.astro"; -import { getAllProjects, getProject } from "../../lib/sanity"; -import { toHTML } from "@portabletext/to-html"; +import { getAllProjects, getProject, imageUrl } from "../../lib/directus"; export async function getStaticPaths() { const projects = await getAllProjects(); - return projects.map((p: any) => ({ params: { slug: p.slug.current } })); + return projects.map((p: any) => ({ params: { slug: p.slug } })); } const { slug } = Astro.params; @@ -13,23 +12,23 @@ const project = await getProject(slug!); if (!project) return Astro.redirect("/projekte"); -const bodyHtml = project.body ? toHTML(project.body) : ""; +const bodyHtml = project.body ?? ""; --- - {project.image?.asset?.url && ( + {project.image && (
- {project.title} + {project.title}
)} @@ -38,7 +37,7 @@ const bodyHtml = project.body ? toHTML(project.body) : "";
{project.date && 📅 {project.date}} - {project.targetGroup && 👥 {project.targetGroup}} + {project.target_group && 👥 {project.target_group}}

{project.title}

{project.summary}

diff --git a/src/pages/projekte/index.astro b/src/pages/projekte/index.astro index 6058b54..63a5177 100644 --- a/src/pages/projekte/index.astro +++ b/src/pages/projekte/index.astro @@ -1,17 +1,17 @@ --- import Layout from "../../layouts/Layout.astro"; -import { getAllProjects } from "../../lib/sanity"; +import { getAllProjects, imageUrl } from "../../lib/directus"; const projects = await getAllProjects(); const statusLabel: Record = { planned: "Geplant", - active: "Laufend", + running: "Laufend", done: "Abgeschlossen", }; const statusColor: Record = { planned: "var(--color-rb-blue)", - active: "var(--color-rb-green)", + running: "var(--color-rb-green)", done: "var(--color-text-muted)", }; --- @@ -41,10 +41,10 @@ const statusColor: Record = {

{p.title}

{p.summary}

- {p.targetGroup && ( -

👥 {p.targetGroup}

+ {p.target_group && ( +

👥 {p.target_group}

)}
))} diff --git a/src/pages/ueber-uns.astro b/src/pages/ueber-uns.astro index 180b0a8..5f88459 100644 --- a/src/pages/ueber-uns.astro +++ b/src/pages/ueber-uns.astro @@ -1,6 +1,6 @@ --- import Layout from "../layouts/Layout.astro"; -import { getTeam } from "../lib/sanity"; +import { getTeam, imageUrl } from "../lib/directus"; const team = await getTeam(); @@ -96,8 +96,8 @@ const roleLabel: Record = { {team.map((member: any) => (
- {member.photo?.asset - ? {member.name} + {member.photo + ? {member.name} : "👤"}

{member.name}

diff --git a/src/pages/unterstuetzen.astro b/src/pages/unterstuetzen.astro index c853fbf..6f61a37 100644 --- a/src/pages/unterstuetzen.astro +++ b/src/pages/unterstuetzen.astro @@ -1,10 +1,8 @@ --- import Layout from "../layouts/Layout.astro"; -import { getSettings } from "../lib/sanity"; +import { getSettings } from "../lib/directus"; const settings = await getSettings(); -const bank = settings?.bank; -const contact = settings?.contact; --- - {bank?.iban ? ( + {settings?.iban ? (
-

Empfänger: {bank.accountHolder}

-

IBAN: {bank.iban}

- {bank.bic &&

BIC: {bank.bic}

} +

Empfänger: Kitafreunde Regenbogen e.V.

+

IBAN: {settings.iban}

+ {settings.bic &&

BIC: {settings.bic}

}

Verwendung: Spende Kitafreunde Regenbogen

) : ( @@ -67,9 +65,9 @@ const contact = settings?.contact; Spielzeug, Bücher, Materialien, Werkzeug, Pflanzen für den Garten — frag uns, was gerade gebraucht wird. Wir stimmen mit der Kita ab.

- {contact?.email && ( - - {contact.email} + {settings?.contact_email && ( + + {settings.contact_email} )}