diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8c73396 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,99 @@ +# CLAUDE.md — Arbeitsvereinbarung für hd-commerce + +Diese Datei ist die verbindliche Kurzanleitung für jede KI (Claude/Cowork/Codex) und jeden Menschen, +die an **hd-commerce** arbeiten. Vor Code-Änderungen lesen. Sie ergänzt `README.md` (Feature-Referenz) +und `HANDOVER.md` (Betrieb/Go-Live). + +--- + +## 1. Was das hier ist + +hd-commerce ist ein **brand-neutrales, wiederverwendbares E-Commerce-Backend** (Produkt, kein Einzelprojekt). +Eine Codebasis → beliebig viele Shops, unterschieden nur durch `settings` + ENV. Die mitgelieferte Demo +heißt „Brittas Nähkiste" und ist **nur Beispiel** — niemals Demo-spezifische Hacks fest in den Code schreiben. + +**Leitsatz:** Alles, was sich pro Shop unterscheidet, gehört in `settings`/ENV/DB — nicht in den Quellcode. + +## 2. Stack (nicht ohne Grund wechseln) + +- **Astro 5 SSR** (`output: 'server'`, `@astrojs/node` standalone, `security.checkOrigin: false`). +- **better-sqlite3** (synchron, WAL) — die einzige Datenschicht. Kein ORM, bewusst. +- **sharp** (WebP), **nodemailer** (SMTP), **stripe** (Stripe-SDK; Mollie über `fetch`/REST). +- **Self-hosted Fonts** Fraunces + Public Sans (`@fontsource-variable`). **Kein** Google-CDN. Chart.js via cdnjs. +- Node 22. Docker `node:22-slim`. Port **4321**. Persistentes Volume auf **`/data`**. + +EU/DSGVO-first (HD-Linie): keine US-SaaS-Abhängigkeit ohne Grund. Zahlung Default **Mollie**, Mail **Listmonk/SMTP**, +Backup **Litestream → Backblaze B2 (EU)**. Analytics ist **first-party** (eigene `events`-Tabelle), kein externer Tracker. + +## 3. Goldene Regeln (die teuren Fehler) + +1. **Preise immer in Cent** (Integer). Keine Floats für Geld. Helper: `taxFromGross(gross, rate)`, `shippingFor(country, subtotal)`, `discountAmount(...)`. +2. **Block-Shape ist FLACH:** ein Block ist `{ type, headline, html, image, ... }` — **NICHT** unter `data: {}` verschachtelt. + Das API-Manifest liest sich missverständlich; maßgeblich sind `src/components/BlockRenderer.astro` + der Editor. + Beim Erzeugen/Ändern von `pages.blocks` immer die flache Form verwenden. +3. **Datenzugriff nur über `src/lib/store-sqlite.js`** (bzw. `store.js`-Fassade). Kein SQL verstreut in Seiten. + Das ist die **Postgres-Exit-Versicherung** (Portal-Empfehlung): SQLite-Spezifika hinter der Fassade halten. +4. **Migrationen idempotent:** neue Spalten via `ensureColumn(...)`, neue Tabellen via `CREATE TABLE IF NOT EXISTS`, + System-Seiten via `ensureSystemPages()`. Alles läuft bei **jedem** Boot — muss mehrfach gefahrlos sein. Kein Migrations-Framework. +5. **Geld/Rabatt/Versand serverseitig re-validieren** (in `/api/checkout`), nie dem Client-Cart-Preis trauen. Gleiches für Varianten-Preise. +6. **Sanitizen:** jeglicher roher HTML-Output (richtext/html-Blöcke, Seiten-`body`) läuft durch `sanitizeHtml()` aus `src/lib/sanitize.js`. Niemals ungefiltertes `set:html`. +7. **Secrets** (Passwörter, Tokens, API-Keys) gehören in **ENV**, nie ins Repo, nie in Memory/Doku. `.env` ist gitignored; `.env.example` zeigt nur Platzhalter. +8. **Rollen serverseitig gaten** (`owner`/`redaktion`/`versand`) — nicht nur die Nav ausblenden. Auth in `src/lib/auth.js`; Kunden-Session getrennt in `customer-auth.js`. + +## 4. Projektkarte (wo liegt was) + +``` +src/lib/ + store-sqlite.js Datenschicht: Schema, Seed, alle CRUD-/Rechen-Helper (Single Source of Truth) + store.js dünne Fassade über store-sqlite (Import-Stabilität) + auth.js Admin-Session (HMAC-Cookie, scrypt, Rollen, Rate-Limit, resolveSecret/cookieSecure) + customer-auth.js Kunden-Session (Cookie hdc_customer), getrennt vom Admin + payments.js Provider-Abstraktion mollie|stripe|demo (+ publicBase aus X-Forwarded-*/PUBLIC_BASE_URL) + mailer.js listmonk|smtp|Log-Fallback (Tabelle email_log) + sanitize.js sanitizeHtml() — Pflicht für jeden rohen HTML-Output + blocks.js Block-Typ-Definitionen/Defaults + seed.js Demo-Seed-Daten +src/components/BlockRenderer.astro rendert pages.blocks (flache Felder!) +src/middleware.js ADMIN_PATH-Rewrite + Auth-Gate +src/pages/admin/** Admin-UI (Astro-Seiten, rollen-gegatet) +src/pages/api/** Endpunkte (checkout, discount, review, upload, cron/abandoned, payments/webhook, admin/*) +src/pages/{index,shop,produkt/[slug],warenkorb,checkout,suche,merkliste,konto/*,seite/[slug]}.astro Storefront +mcp/ eigenständiger MCP-Server (stdio) über die Admin-API +test/unit.mjs Unit-Tests (Geld/Versand/Rabatt/Sanitizer) +scripts/sync-css.mjs prebuild: CSS-Sync +Dockerfile, docker-entrypoint.sh, litestream.yml Container + optionales Litestream-Backup +``` + +## 5. Entwickeln, testen, bauen + +```bash +npm install +npm run dev # http://localhost:4321 (Storefront /, Admin /admin) +npm test # Unit-Tests — MUSS grün sein vor jedem Commit (nutzt temporäre DB) +npm run build && node ./dist/server/entry.mjs # Produktiv-Lauf lokal prüfen +``` + +- **Vor jedem Commit `npm test`.** Neue Geld-/Rabatt-/Versand-/Sanitizer-Logik bekommt einen Test dazu. +- Erst-Login lokal mit `ADMIN_EMAIL`/`ADMIN_PASS` aus `.env`. +- `ADMIN_PATH` testen, wenn an Auth/Middleware angefasst wird (Default `admin`; abweichend ⇒ `/admin` muss 404 sein). + +## 6. Deploy (Coolify) — und die Stolpersteine + +Deploy via Coolify (Docker-Build aus dem Repo). **Nach jedem Deploy gilt:** + +1. **App `control restart`** ausführen. Rolling-Updates lassen zeitweise Altcontainer stehen → sonst gemischte 401/404/502. +2. **Verifizieren** mit cache-bustendem `curl` (nicht WebFetch — cacht): Storefront 200, Login 200, ein API-Call. +3. **Coolify-Gotchas:** + - `application create_public` verwirft den Git-Host → danach `application update` mit voller `https://till:TOKEN@…`-URL. + - Änderung von `ports_exposes` regeneriert die Traefik-Labels **nicht** → bei Port-Wechsel `custom_labels` (base64) manuell setzen. + - Domain-Wechsel http→https **regeneriert** die Labels (anders als ports_exposes) → danach restart gegen Altcontainer. +4. **Migrationen** brauchen keinen Sonderschritt — sie laufen idempotent beim Boot (siehe Regel 4). +5. **Sandbox-Hinweis (Cowork):** der FUSE-Mount erlaubt kein `rm`/Symlinks → in `/tmp` klonen/bauen, nicht im Mount. Push braucht Tills Gitea-Token (anonymer Clone ist read-only). + +## 7. Versionierung + +`package.json#version` + sprechende Commit-Message pro Feature-Stufe (`v2.4: …`). Aktueller Stand: **2.4** (Medien/WebP, Varianten-Matrix, Litestream, intelligentere Analytics). Bei Schema-/ENV-Änderung: README-Tabelle, `.env.example` und `HANDOVER.md` mitziehen. + +## 8. „Selbst bauen vs. kaufen" — Leitplanke + +Eigenbau ist gerechtfertigt, **weil** hd-commerce ein mehrfach ausrollbares HD-Produkt ist (Wartung amortisiert über die Care-Abo-Basis). Für einen einzelnen 08/15-Shop bleibt Shopware/Medusa/Snipcart pragmatischer. Halte den Code deshalb **generisch und wartungsarm** — jede Demo-Sonderlocke und jede SQLite-Spezifik außerhalb der Store-Fassade erhöht genau die laufende Wartung, die das Produkt rechtfertigen muss. Belegte Einordnung: Portal-Research „SQLite-Eigenbau-Commerce — realistisch oder Overkill?". diff --git a/HANDOVER.md b/HANDOVER.md new file mode 100644 index 0000000..15b75ad --- /dev/null +++ b/HANDOVER.md @@ -0,0 +1,139 @@ +# HANDOVER — hd-commerce + +Betriebs- und Übergabe-Runbook für **hd-commerce**. Zielgruppe: wer das Produkt deployt, einen echten +Kundenshop daraus ausrollt und betreibt. Ergänzt `README.md` (Features) und `CLAUDE.md` (Arbeitsregeln am Code). + +Stand: v2.4 · Repo `till/hd-commerce` (Gitea, GitHub-Mirror siehe `MIRROR.md`). + +--- + +## 1. Was übergeben wird + +Ein **wiederverwechselbares E-Commerce-Backend-Produkt** (eine Codebasis, viele Shops). Enthalten: +Storefront + Premium-Admin, Katalog mit **Varianten-Matrix**, Warenkorb/Checkout, **Zahlung** (Mollie/Stripe/Demo), +**Rabatt-Engine**, **Versandzonen** + MwSt/Grundpreis (DACH), **Inhalte/Block-Builder**, **Medienbibliothek + WebP**, +**Kundenkonten**, **Bewertungen**, **Merkliste**, **Volltextsuche**, **Abandoned-Cart**, **first-party Analytics**, +**Bestellmails** (Listmonk/SMTP/Log), **KI-/MCP-API**, **Litestream-Backups** (optional). + +**Demo-Instanz „Brittas Nähkiste"** (Beispiel, kein echter Shop): +- App (Coolify): `lbv524oul6b6mxvo95pgxgso` · Server `localhost` (heidrich-infra-01) +- URL: `https://lbv524oul6b6mxvo95pgxgso.46.224.49.66.sslip.io` +- Admin-Pfad: `/login` (per `ADMIN_PATH=login`) · DB: `/data/hdc2.db` · RAM-Cap 640 MB +- Let's-Encrypt-Zertifikat, http→https erzwungen, `PUBLIC_BASE_URL=https://…` gesetzt +- Zugangsdaten: **nicht** hier — im 1Password/Chat. Niemals Credentials ins Repo/in Doku. + +## 2. „Selbst gebaut" — bewusste Entscheidung + +Der Eigenbau ist gerechtfertigt, **weil** hd-commerce ein über die Care-Abo-Basis mehrfach ausrollbares HD-Produkt ist: +Datenhoheit, Tempo, Marge (keine Shopify-Gebühren), DSGVO-nativ, KI-/MCP-automatisiert. Der Preis ist **dauerhafte +Eigen-Wartung**. Faustregeln (belegt im Portal-Research „SQLite-Eigenbau-Commerce — realistisch oder Overkill?"): + +- SQLite + Litestream ist für DACH-KMU-Shops (read-heavy Katalog, überschaubare Bestell-Schreiblast) **technisch tragfähig**. +- Harte Grenze: **eine Server-Maschine, ein Writer** — gut bis Größenordnung ~500 aktive Nutzer/Tag; darüber Postgres auf derselben Box. +- Für einen einzelnen 08/15-Shop ist **Shopware 6 / Medusa / Snipcart+Stripe** pragmatischer. +- Postgres-Exit absichern: Datenzugriff bleibt hinter `store-sqlite.js` (siehe CLAUDE.md, Regel 3). + +## 3. Neuen Kundenshop ausrollen (eine Codebasis, neue Instanz) + +1. **Coolify-App** anlegen (Build-Pack: Dockerfile, Branch `main`, Repo `till/hd-commerce`). + - `application create_public` verwirft den Git-Host → danach `application update` mit voller `https://till:TOKEN@git.heidrich-digital.de/till/hd-commerce.git`-URL. +2. **Persistentes Volume** auf `/data` mounten (eigener DB-Pfad je Shop, z. B. `DB_PATH=/data/.db`). +3. **ENV setzen** (Minimal-Set, siehe §4). `SESSION_SECRET`, `HDC_API_TOKEN`, `CRON_TOKEN` je Shop **eindeutig** und zufällig. +4. **Domain** eintragen (echte Kundendomain statt sslip), Coolify zieht das Let's-Encrypt-Zertifikat; `redirect: both` (http→https). `PUBLIC_BASE_URL=https://` setzen. +5. **Deploy** → danach **App `control restart`** (Altcontainer räumen) → verifizieren (§6). +6. **Branding** im Admin → Einstellungen: Shop-Name, Akzentfarbe, Währung, Logo-Wortmarke, Module (Feature-Flags). Kein Code-Eingriff. +7. **Inhalte**: Startseite/Seiten über den Block-Builder, Produkte + Varianten, Versandzonen, Rabatte, Popups anlegen — alles im Admin oder per KI/MCP-API. + +> Pro Shop unterscheidet sich nur ENV + `settings` + DB-Inhalt. Der Code bleibt identisch und generisch (CLAUDE.md, Leitsatz). + +## 4. ENV — Go-Live-Checkliste + +**Pflicht (Minimal-Betrieb):** +- [ ] `DB_PATH=/data/.db` (auf gemountetem Volume) +- [ ] `ADMIN_EMAIL` + `ADMIN_PASS` (Initial-Owner, nur erster Boot; danach im Admin verwaltbar) +- [ ] `ADMIN_PATH` (z. B. `login` oder `intern` — nicht das offensichtliche `admin`) +- [ ] `SESSION_SECRET` (langes Zufallsgeheimnis — in Prod **zwingend**, sonst nur persistenter Fallback) +- [ ] `PUBLIC_BASE_URL=https://` (korrigiert Proxy-Origin für Zahlungs-Return/Webhook) + +**Zahlung (für echten Verkauf):** +- [ ] `MOLLIE_API_KEY=test_…` zum Testen, dann `live_…` → Provider wählt automatisch Mollie. Webhook automatisch `{PUBLIC_BASE_URL}/api/payments/webhook`. +- [ ] (Alternativ Stripe: `STRIPE_SECRET_KEY` + `STRIPE_PUBLIC_KEY`.) Ohne gültigen Key läuft der **Demo-Checkout** (kein echtes Geld). + +**Bestellmails (sonst nur Log-Fallback in `email_log`):** +- [ ] `MAIL_PROVIDER=listmonk` + `LISTMONK_URL/USER/PASS/TX_TEMPLATE_ID` + `MAIL_FROM` — **eine** Listmonk-Instanz reicht für alle Shops (pro Shop Liste+Template+Absender). +- [ ] oder `MAIL_PROVIDER=smtp` + `SMTP_HOST/PORT/USER/PASS/SECURE`. + +**Backup (dringend für echten Shop):** +- [ ] `LITESTREAM_REPLICA_URL=s3:///` + `LITESTREAM_ACCESS_KEY_ID` + `LITESTREAM_SECRET_ACCESS_KEY` + `LITESTREAM_ENDPOINT` (B2-EU). Siehe §7. + +**Optional:** +- [ ] `HDC_API_TOKEN` (KI/MCP-API; leer ⇒ API gesperrt) +- [ ] `CRON_TOKEN` + Coolify-Scheduled-Task für Abandoned-Cart (§5) +- [ ] `WEBP_QUALITY` / `WEBP_MAX_WIDTH`, `ABANDONED_AFTER_MINUTES` + +Vollständige Referenz: `.env.example` + README-Tabelle. + +## 5. Wiederkehrende Jobs + +**Abandoned-Cart-Erinnerung** — Coolify-Scheduled-Task (z. B. alle 30 Min): +``` +curl -fsS -X POST -H "Authorization: Bearer $CRON_TOKEN" https:///api/cron/abandoned +``` +Schickt fälligen, nicht erinnerten Warenkörben eine Mail (Mailer/Log-Fallback) und setzt `reminded=1`. + +## 6. Verifizieren nach jedem Deploy + +Immer **cache-bustend per `curl`** (nicht WebFetch — cacht): +``` +curl -fsS -o /dev/null -w "%{http_code}\n" https:/// # Storefront 200 +curl -fsS -o /dev/null -w "%{http_code}\n" https:/// # Login 200 +curl -fsS -o /dev/null -w "%{http_code}\n" https:///api/admin -H "Authorization: Bearer $HDC_API_TOKEN" +curl -fsS -o /dev/null -w "%{http_code}\n" -I https:/// # http→https-Redirect prüfen +``` +Erwartung: Storefront/Login 200, gebrandete 404 auf Unsinns-Pfad, ein Test-Checkout (Mollie-Test) bis „bezahlt". + +## 7. Backup & Restore (Litestream) + +- **Aktiv**, sobald `LITESTREAM_REPLICA_URL` gesetzt ist: der Container streamt `DB_PATH` live nach S3/B2 und stellt beim Start bei Bedarf wieder her. Ohne die Variable: normaler Node-Start ohne Backup. +- **Status** sichtbar unter Admin → Einstellungen → Backup (Litestream). +- **Disaster-Recovery / Restore** (neue Instanz, leeres Volume): erfolgt beim Start automatisch. Manuell: + ``` + litestream restore -config /app/litestream.yml -if-replica-exists /data/.db + ``` +- **B2-Empfehlung:** Bucket in EU (`s3.eu-central-003.backblazeb2.com`), Application-Key mit Schreibrechten, pro Shop eigener Pfad/Bucket. + +## 8. Wartung / Care-Abo + +- **Updates:** Änderungen am Produkt-Repo `main`; pro Kundenshop neu deployen + restart + verifizieren (§6). Migrationen laufen idempotent beim Boot — kein manueller DB-Schritt. +- **Monitoring:** Coolify-Healthcheck (`/`), App-Logs (`application_logs`). RAM-Cap je Shop setzen (Demo: 640 MB) — heidrich-infra-01 hat 8 GB ohne Swap, also nicht überbuchen. +- **`npm test`** muss grün sein, bevor neuer Code nach `main` geht (Geld/Versand/Rabatt/Sanitizer). +- **Logs/Audit:** Admin → Aktivität (Audit) protokolliert Create/Update/Delete; E-Mail-Log unter Einstellungen. + +## 9. Sicherheit (Betreiber-Pflichten) + +- `SESSION_SECRET`, `HDC_API_TOKEN`, `CRON_TOKEN` **je Shop eindeutig** und geheim (nur ENV). +- `ADMIN_PATH` nicht auf `admin` lassen. Rollen sparsam vergeben (owner/redaktion/versand). +- Roh-HTML in Inhalten läuft durch `sanitizeHtml()` — trotzdem nur vertrauenswürdigen Redakteuren `html`-Blöcke erlauben. +- Vor produktivem Verkauf: **`/engineering:security-review`-Pass** einplanen. + +## 10. Reife-Status & offene Punkte (vor echtem, zahlendem Shop) + +Funktional auf dem Niveau eines kleinen Shopify/Woo-Shops. Noch **betrieblich** zu erledigen (keine Features): +- [ ] **Mollie** live-Key getestet (Test-Checkout bis „bezahlt", Webhook setzt Bestellung auf bezahlt + Mail). +- [ ] **Litestream** mit echten Backblaze-B2-Zugangsdaten scharf geschaltet + ein Restore geprobt. +- [ ] **Echte Rechtstexte**: Impressum, Datenschutz, AGB, Widerruf + Cookie/Consent (DSGVO/BFSG). +- [ ] **Listmonk/SMTP** angebunden, damit Bestellmails real rausgehen (statt Log-Fallback). +- [ ] **Security-Review-Pass** vor Verkaufsstart. + +## 11. Schnellreferenz + +| Sache | Wert | +|---|---| +| Repo | `https://git.heidrich-digital.de/till/hd-commerce` (Branch `main`) | +| Demo-App (Coolify) | `lbv524oul6b6mxvo95pgxgso` | +| Demo-URL | `https://lbv524oul6b6mxvo95pgxgso.46.224.49.66.sslip.io` | +| Demo-Admin | `/login` (ADMIN_PATH=login) | +| Demo-DB | `/data/hdc2.db` (Volume), RAM 640 MB | +| Port / Volume | 4321 / `/data` | +| Tests | `npm test` | +| Nach Deploy | App **restart** → §6 verifizieren |