Files
hd-commerce/README.md
T
till 50dfca59e1 v2.4: Medienbibliothek+WebP, Varianten-Matrix, Litestream-Backups, intelligentere Analytics
P1 Medien: eigener Admin-Bereich /admin/medien (Grid, Mehrfach-Upload, Drag&Drop, Alt-Text, URL kopieren, Loeschen). Upload konvertiert JPG/PNG via sharp zu WebP (Qualitaet 82, max 2000px), Original wird verworfen; WebP/SVG/GIF/AVIF unveraendert; Konvertierungsfehler -> Original behalten statt 500. media um alt/width/height erweitert. Wiederverwendbarer Medien-Picker (public/media-picker.js) ersetzt den URL-Prompt im Block-Editor, Produkt-Editor (Karte/Galerie/Varianten-Bild), Slides und Popups. JSON-Quelle /api/admin/media (session-gesichert).

P2 Varianten: products.options_json + Tabelle product_variants. Produkt-Editor mit Options-Definition + Matrix-Generator (Preis-Override/Bestand/SKU/Bild/aktiv je Variante). PDP-Selektoren -> Variante; Cart/Checkout tragen sku+Options, Order-Item bekommt sku/variant, Variantenpreis serverseitig verifiziert. Produkte ohne Optionen unveraendert.

P3 Litestream: Binary im Dockerfile, docker-entrypoint.sh (Restore+replicate nur bei LITESTREAM_REPLICA_URL, sonst reiner Node-Start), litestream.yml, Backup-Status unter Einstellungen, README + .env.example.

P4 Analytics: Bestseller, Top-Suchbegriffe, Umsatz/Quelle, Umsatz-Zeitreihe, AOV, Wiederkaufrate, Lager-Warnungen.

Neue Dep sharp. +19 Unit-Tests (49 gesamt gruen), Build + Smoke (P1-P4) gruen.
2026-06-18 08:09:57 +00:00

15 KiB
Raw Blame History

hd-commerce

hd-commerce ist ein eigenständiges, brand-neutrales E-Commerce-Backend von Heidrich Digital: eine wiederverwendbare Astro-SSR-Anwendung mit Commerce-Engine (SQLite), Session-gesichertem Admin, Visual-Block-Builder, KI-/MCP-Editierbarkeit, JSON-API und einem schlanken, neutralen Demo-Storefront.

Die mitgelieferte Demo-Instanz heißt „Brittas Nähkiste" (Kurzwaren/Nähbedarf) und dient nur als Beispiel. Name, Akzentfarbe, Texte und Logo-Wortmarke sind über die Einstellungen frei anpassbar — derselbe Code läuft für beliebige Shops.

Features

  • Storefront (hell, editorial, neutral): Startseite mit Announcement-Bar, Slider, Kategorien, Featured-Produkten und Newsletter; Shop-Katalog; Produktdetailseiten; Warenkorb (localStorage); Checkout; Inhaltsseiten aus der DB — wahlweise klassisch oder über den Block-Builder gestaltet.
  • Admin (Premium, „Warmth & Approachability"): Session-Login statt Browser-Basic-Auth, Rollen (Owner/Redaktion/Versand), Command-Palette (⌘K), Toasts, aufgewertetes Dashboard mit KPI-Trends, Sparkline, Aktivitäts-Feed und Schnellaktionen.
  • Visual-Block-Builder: Vollbild-Editor mit Block-Liste (Drag/▲▼/duplizieren/löschen), Live-Vorschau (Desktop/Mobil) und Block-Einstellungen. Block-Typen: Hero, Rich-Text, Bild, Galerie, Slider, Feature-Grid, Produkt-Grid, CTA-Banner, Abstand, Roh-HTML.
  • KI-Editierbarkeit: token-gesicherte Admin-JSON-API (/api/admin/*) plus maschinenlesbares Manifest (/api/admin, /ai-admin.txt) und ein MCP-Server (mcp/).
  • Gutschein-/Rabatt-Engine (v2.1): Codes vom Typ percent / fixed / freeshipping mit Zeitplan, Mindestbestellwert, Gesamt- und Pro-Kunde-Limit, „geheim" (nicht öffentlich listbar) und „automatisch" (greift ohne Code, wenn Bedingungen erfüllt). Admin-Bereich Rabatte (Owner/Redaktion) mit Status-Badges (Aktiv/Geplant/Abgelaufen/Aufgebraucht/Inaktiv); Storefront-Einlösung im Checkout über /api/discount; serverseitige Re-Validierung in /api/checkout; Stripe-Coupon-Anbindung. Popups können einen Code anzeigen (+ Kopieren-Button) — auch für gezielt verteilte geheime Codes; Popup-Stile modal / slidein / bar.
  • Verkaufsfertig-Fundament (v2.2):
    • Payment-Abstraktion (src/lib/payments.js): einheitliche Schnittstelle für Mollie (Default, REST), Stripe und Demo. Provider via Setting payment_provider / ENV PAYMENT_PROVIDER, sonst Auto-Wahl nach vorhandenen Keys. Mollie-Webhook unter /api/payments/webhook; ungültiger Key ⇒ sauberer Demo-Fallback statt Fehler.
    • DACH-Recht: MwSt-Ausweis pro Produkt (mwst 0/7/19), Grundpreis (PAngV) über base_amount/base_unit/base_price_per; Warenkorb/Checkout zeigen Zwischensumme, enthaltene MwSt (nach Satz gruppiert) und Versand vor dem Bezahlen.
    • Versandzonen (Tabelle shipping_zones, Admin „Versand"): länderbasierte Preise mit Gratis-ab-Schwelle; Helper shippingFor(country, subtotal); Checkout berechnet Versand serverseitig neu.
    • Bestell-/Versandmails (src/lib/mailer.js): Provider Listmonk (Transactional-API) / SMTP (nodemailer) / Log-Fallback (Tabelle email_log, Admin „E-Mail-Log"). Gebrandete Bestellbestätigung bei bezahlter Bestellung.
    • Feature-Flags: Module pro Shop abschaltbar (feature_newsletter, feature_accounts, feature_reviews, feature_wishlist, feature_abandoned_cart, feature_search) über Admin → Einstellungen → Module; Helper feature(key).
  • Feature-Module (v2.3): über die jeweiligen Flags ge-gatet — ist ein Flag aus, verschwinden Storefront-Elemente, Routen liefern 302/404 und der Admin-Nav-Punkt entfällt.
    • Volltextsuche (feature_search): Suchfeld im Storefront-Header → Ergebnisseite /suche?q= (SSR, SQLite-LIKE, case-insensitiv über Name/Kurzname/Beschreibung/Material/Kategorie), Treffer als Produktkarten, „keine Treffer"-Zustand.
    • Merkliste (feature_wishlist): Herz-Button auf Produktkarten & Detailseite, Speicherung clientseitig (localStorage, public/wishlist.js), Seite /merkliste.
    • Kundenkonten + Adressbuch (feature_accounts): eigene Kunden-Session (Cookie hdc_customer, getrennt vom Admin; scrypt-Hash). /konto/registrieren, /konto/anmelden, /konto (Bestellhistorie + Adressbuch), /konto/abmelden. Tabelle customer_addresses; Checkout füllt die Adresse vor und ordnet die Bestellung dem Konto zu (orders.customer_id). Gast-Checkout bleibt möglich.
    • Bewertungen (feature_reviews): Tabelle reviews (Sterne 15, Moderation approved). Formular auf der Produktseite (/api/review, speichert approved=0), Anzeige von Durchschnitt + freigegebenen Reviews, optionale aggregateRating im Produkt-JSON-LD. Admin-Bereich Bewertungen (Owner/Redaktion): Freigeben/Verbergen/Löschen, Zähler offener Reviews in der Nav.
    • Warenkorb-Erinnerung (feature_abandoned_cart): beim Checkout-Start wird der Warenkorb serverseitig in abandoned_carts gesichert (/api/cart-capture). Versand-Trigger: POST /api/cron/abandoned (Header Authorization: Bearer <CRON_TOKEN> oder ?token=), schickt für fällige, nicht erinnerte Karten eine gebrandete Erinnerungsmail (Mailer/Log-Fallback) und setzt reminded=1. Erfolgreiche Bestellung der Adresse setzt recovered=1. Status/Zähler unter Einstellungen. Als Coolify-Scheduled-Task z. B. alle 30 Min curl -fsS -X POST -H "Authorization: Bearer $CRON_TOKEN" https://shop.example.com/api/cron/abandoned aufrufen.
  • Medienbibliothek + WebP (v2.4): eigener Admin-Bereich Medien (/admin/medien) mit Grid (Thumbnail, Dateiname, Größe/Maße, Alt-Text, „URL kopieren", Löschen) und Mehrfach-Upload (Drag&Drop). Beim Upload werden JPG/JPEG/PNG automatisch zu WebP konvertiert (sharp, Qualität ~82, max-Breite ~2000px); das Original wird verworfen, nur die .webp bleibt. WebP/SVG/GIF/AVIF werden unverändert durchgereicht; bei Konvertierungsfehler bleibt das Original erhalten (kein Absturz). Ein wiederverwendbarer Medien-Picker (public/media-picker.js) ersetzt überall den alten URL-Prompt: Block-Editor (Hero/Bild/Galerie-Mehrfach), Produkt-Editor (Karten-Bild, Galerie, Varianten-Bild), Slides & Popups. media um alt/width/height erweitert; JSON-Quelle /api/admin/media (session-gesichert).
  • Varianten-Matrix (v2.4): Produkte definieren Optionen (options = [{name, values[]}], z. B. Größe × Farbe). Der Produkt-Editor erzeugt daraus die Varianten-Matrix (product_variants: sku, options_json, price_cents-Override, stock, image, active). Storefront-PDP zeigt Options-Selektoren → wählt Variante → Preis/Bestand/Bild aktualisieren, „nicht lieferbar" bei inaktiv/ausverkauft. Warenkorb & Checkout tragen sku + Options; das Order-Item bekommt sku/variant, der Variantenpreis wird serverseitig verifiziert. Produkte ohne Optionen verhalten sich wie bisher (einfache „Größen"-Liste bleibt).
  • Intelligentere Analytics (v2.4): Conversion je Produkt (Ansichten→Käufe), Bestseller (Menge/Umsatz), Umsatz pro Quelle/UTM, Top-Suchbegriffe (search-Events von /suche, inkl. Null-Treffer-Markierung), Umsatz-Zeitreihe (Chart.js, 30 Tage), AOV, Wiederkaufrate und Lager-Warnungen (knappe Produkte & Varianten) — alles aus SQLite aggregiert, kein externer Dienst.
  • Litestream-Backups (v2.4): optionales Streaming-Backup der SQLite-DB nach S3/Backblaze B2. Ist LITESTREAM_REPLICA_URL gesetzt, stellt der Container beim Start die DB bei Bedarf wieder her (litestream restore) und repliziert dann live (litestream replicate -exec); ohne Replica startet die App normal ohne Backup. Status sichtbar unter Admin → Einstellungen → Backup. Siehe Abschnitt „Backups".
  • Editierbare, gebrandete 404 (v2.1): src/pages/404.astro rendert die System-Seite mit Slug 404 über den Block-Builder. Wird per ensureSystemPages() bei jedem Boot idempotent angelegt und ist im Admin unter Inhalte editierbar.
  • Engine: synchron via better-sqlite3 (WAL), automatisches Seeding beim ersten Start.
  • First-Party-Analytics: eigene events-Tabelle, kein externer Dienst (Session = täglich rollender Hash).
  • Branding konfigurierbar: Shop-Name, Akzentfarbe, Währung u. a. in einer settings-Tabelle.
  • Self-hosted Fonts (Fraunces + Public Sans), kein Google-CDN. Chart.js via cdnjs.

Authentifizierung & Rollen

  • Session-Login per HTML-Formular (signiertes HMAC-Cookie, „Angemeldet bleiben" = 30 Tage). Passwörter werden mit node:crypto.scryptSync + zufälligem Salt gehasht.
  • Initial-Owner wird beim ersten Boot aus ADMIN_EMAIL / ADMIN_PASS angelegt; weitere Nutzer im Admin unter Nutzer & Zugänge (Owner-only).
  • Rollen: owner (alles), redaktion (Produkte/Inhalte/Marketing/Analytics), versand (nur Bestellungen). Navigation und Seiten werden serverseitig nach Rolle gegated.
  • Konfigurierbarer Admin-Pfad über ADMIN_PATH (Default admin, z. B. intern → Admin unter /intern). Direkter Zugriff auf /admin wird bei abweichendem Pfad mit 404 blockiert.
  • Audit-Log (Tabelle audit) protokolliert Create/Update/Delete; Ansicht unter Aktivität (Audit) (Owner). Login-Rate-Limit: nach 5 Fehlversuchen 60 s Sperre pro IP.

Umgebungsvariablen (ENV)

Variable Beschreibung Default
DB_PATH Pfad zur SQLite-Datenbank (wird angelegt) ./data/hdc.db
ADMIN_EMAIL Initial-Owner-E-Mail (erster Boot) admin@example.com
ADMIN_PASS Initial-Owner-Passwort (erster Boot) admin
ADMIN_PATH Pfad-Segment des Admin-Bereichs admin
SESSION_SECRET HMAC-Geheimnis für Session-Cookies interner Fallback (in Prod setzen!)
HDC_API_TOKEN Bearer-Token für /api/admin/*. Leer ⇒ API gesperrt
STRIPE_PUBLIC_KEY Stripe Publishable Key (optional)
STRIPE_SECRET_KEY Stripe Secret Key. Ohne echten Key läuft der Demo-Checkout.
PAYMENT_PROVIDER Zahlungsanbieter erzwingen: mollie / stripe / demo. Leer ⇒ Auto-Wahl
MOLLIE_API_KEY Mollie API-Key (test_…/live_…). Ohne gültigen Key Demo-Fallback
MAIL_PROVIDER listmonk / smtp / leer (⇒ Log-Fallback in email_log)
MAIL_FROM Absenderadresse ausgehender Mails
LISTMONK_URL / LISTMONK_USER / LISTMONK_PASS / LISTMONK_TX_TEMPLATE_ID Listmonk Transactional-API
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_SECURE SMTP-Versand (nodemailer)
CRON_TOKEN Bearer-Token für /api/cron/abandoned (Warenkorb-Erinnerung). Leer ⇒ Endpoint gesperrt
ABANDONED_AFTER_MINUTES Alter (Minuten), ab dem eine offene Warenkorb-Karte erinnert wird 30

Siehe .env.example.

Lokal starten

npm install
npm run dev          # http://localhost:4321
# oder produktiv:
npm run build
node ./dist/server/entry.mjs

Storefront: / · Admin: /admin (bzw. /${ADMIN_PATH}). Erst-Login mit ADMIN_EMAIL / ADMIN_PASS.

Block-Builder

Jede Seite (pages) hat ein Feld blocks (JSON-Array). Der Vollbild-Editor liegt unter /${ADMIN_PATH}/inhalte/editor/<id> (Button „Editor" in der Seitenliste). Gespeicherte Blöcke werden vom Storefront-Block-Renderer (src/components/BlockRenderer.astro) auf /seite/<slug> ausgegeben.

KI-Editierbarkeit (API)

Token-gesicherte JSON-API unter /api/admin/* (Header Authorization: Bearer <HDC_API_TOKEN>):

  • GET /api/admin — maschinenlesbares Manifest (Ressourcen, Felder, Block-Typen, Endpunkte).
  • GET /ai-admin.txt — dieselbe Beschreibung als Klartext für LLMs.
  • GET /api/admin/{resource} · GET /api/admin/{resource}/{id} — lesen.
  • POST /api/admin/{resource} — Upsert (mit id oder slug ⇒ Update, sonst Create).
  • DELETE /api/admin/{resource}/{id} — löschen.
  • POST /api/admin/pages/{id}/blocks — Block-Array einer Seite setzen.

Schreibbar: products, pages, slides, popups, settings. Nur lesbar: orders, customers. Preise in Cent.

curl -H "Authorization: Bearer $HDC_API_TOKEN" https://shop.example.com/api/admin/products
curl -H "Authorization: Bearer $HDC_API_TOKEN" -X POST https://shop.example.com/api/admin/products \
  -H 'Content-Type: application/json' -d '{"name":"Neues Produkt","priceCents":1990,"category":"Test"}'

MCP-Server

Unter mcp/ liegt ein eigenständiger Model-Context-Protocol-Server (stdio), mit dem ein LLM/Agent den Shop über die Admin-API bearbeitet. Tools u. a.: list_products, upsert_product, get_page, update_page_blocks, list_orders, update_settings, create_page. Installation, ENV (HDC_BASE_URL, HDC_API_TOKEN) und Registrierung in Claude/Cowork: siehe mcp/README.md.

Docker / Coolify

docker build -t hd-commerce .
docker run -p 4321:4321 -v hdc-data:/data \
  -e ADMIN_EMAIL=admin@example.com -e ADMIN_PASS=geheim \
  -e SESSION_SECRET=langes-geheimnis -e HDC_API_TOKEN=token hd-commerce

Das Dockerfile (node:22-slim) baut better-sqlite3 nativ, legt /data an und setzt DB_PATH=/data/hdc.db. Auf Coolify ein persistentes Volume auf /data mounten. HEALTHCHECK prüft /.

Das Image enthält zusätzlich das Litestream-Binary und startet über docker-entrypoint.sh: ohne Backup-ENV ein reiner node ./dist/server/entry.mjs, mit LITESTREAM_REPLICA_URL ein litestream replicate -exec (nach optionalem Restore).

Backups (Litestream → Backblaze B2 / S3)

Optionales kontinuierliches Streaming-Backup der SQLite-DB. Ohne Konfiguration läuft die App unverändert ohne Backup.

  1. B2-Bucket anlegen (Backblaze) und einen Application-Key mit Schreibrechten erzeugen. B2 ist S3-kompatibel.
  2. ENV setzen (Coolify → App → Environment):
    LITESTREAM_REPLICA_URL=s3://<bucket>/<pfad>
    LITESTREAM_ACCESS_KEY_ID=<keyID>
    LITESTREAM_SECRET_ACCESS_KEY=<applicationKey>
    LITESTREAM_ENDPOINT=s3.eu-central-003.backblazeb2.com   # B2-S3-Endpoint; für AWS S3 leer lassen
    
    (Für AWS S3 genügen LITESTREAM_REPLICA_URL + die beiden Keys; LITESTREAM_ENDPOINT bleibt leer.)
  3. Restore (z. B. neue Instanz / Disaster-Recovery) — die DB wird beim Start automatisch wiederhergestellt, falls lokal keine existiert. Manuell:
    litestream restore -config /app/litestream.yml -if-replica-exists /data/hdc.db
    

Der Status (konfiguriert? Ziel?) ist unter Admin → Einstellungen → Backup (Litestream) sichtbar. Hinweis: Die Live-Demo nutzt DB_PATH=/data/hdc2.db.

Datenmodell

settings (inkl. Feature-Flags & payment_provider), products (inkl. mwst / base_amount / base_unit / base_price_per), orders (inkl. discount_code/discount_cents, tax_cents/shipping_cents/country, payment_provider/payment_id), customers, slides, pages (inkl. blocks; System-Seite 404), popups (inkl. style / discount_id), discounts, discount_redemptions, shipping_zones, email_log, subscribers, events, media (inkl. alt/width/height), product_variants (Größe×Farbe-Matrix), users, audit — alles seed-bar und im Admin pflegbar.


Hinweis: „Brittas Nähkiste" ist nur die mitgelieferte Demo-Instanz. Brand (Name, Farben, Texte) ist über Admin → Einstellungen anpassbar.

Lizenz: MIT (siehe LICENSE).