Der Shop konnte Ernten und Chargen bereits aus ApiCore uebernehmen, den Produktkatalog aber nicht - jedes Glas musste im Admin ein zweites Mal von Hand angelegt werden. Neuer Knopf unter /admin/produkte zieht die Produkte und traegt anschliessend die passende Charge am Produkt nach, wo ApiCore die Abfuellung einem Produkt zugeordnet hat. Genau diese Verknuepfung fuettert die Herkunftsseite hinter dem QR-Code auf dem Glas. ApiCore fuehrt ueberhaupt keine Verkaufspreise. Importierte Produkte entstehen deshalb als deaktivierte Entwuerfe mit Preis 0, den nur ein Mensch setzen kann. Damit daraus nie eine Gratis-Bestellung wird, lehnen die Aktivierung im Admin und der Checkout ein Produkt ohne Preis jetzt ausdruecklich ab, und die Produktliste kennzeichnet solche Entwuerfe. Ein bereits vorhandenes Produkt wird uebersprungen, nie ueberschrieben: genau die drei Felder, die ApiCore liefert (Name, Beschreibung, Gewicht), zieht der Admin vor der Freischaltung von Hand nach - ein Update wuerde diese Arbeit bei jedem weiteren Lauf wegwerfen. Ein in ApiCore ausgelaufenes Produkt wird nicht automatisch deaktiviert, sondern nur gemeldet, damit der Restbestand abverkauft werden kann. Nebenbei behoben: die beiden Produktformulare und die Bestelluebersicht haben den ?error=-Parameter aus fail() nie ausgegeben - abgelehnte Eingaben landeten wortlos wieder auf dem Formular. Der Login gegen ApiCore ist jetzt von den Abfragen getrennt (openApiCoreSession), damit ein Sync-Lauf mit einer einzigen Anmeldung auskommt statt mit dreien. Die Feld-Abbildung liegt neu in lib/apicore-mapping.ts und ist damit testbar, statt hinter "use server" zu verschwinden. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
196 lines
10 KiB
Bash
196 lines
10 KiB
Bash
# Datenbank: "postgresql" (Standard) oder "mysql". Nach einer Aenderung
|
|
# einmal `pnpm db:push` ausfuehren (schreibt prisma/schema.prisma neu und
|
|
# generiert den passenden Treiber-Adapter-Client) — siehe README, Abschnitt
|
|
# "Datenbank-Provider wechseln".
|
|
DATABASE_PROVIDER=postgresql
|
|
|
|
# PostgreSQL (Standard). Fuer docker compose siehe POSTGRES_* unten.
|
|
DATABASE_URL="postgresql://wabenhain:wabenhain@localhost:5432/wabenhain?schema=public"
|
|
# MySQL/MariaDB stattdessen:
|
|
# DATABASE_URL="mysql://wabenhain:wabenhain@localhost:3306/wabenhain"
|
|
|
|
# Postgres-Zugangsdaten fuer docker-compose.yml (Datenbank-Container)
|
|
POSTGRES_USER=wabenhain
|
|
POSTGRES_PASSWORD=change-me
|
|
POSTGRES_DB=wabenhain
|
|
|
|
# Nur noetig, wenn DATABASE_PROVIDER=mysql: Zugangsdaten fuer den
|
|
# db-mysql-Container ("docker compose --profile mysql up -d db-mysql")
|
|
MYSQL_USER=wabenhain
|
|
MYSQL_PASSWORD=change-me
|
|
MYSQL_DATABASE=wabenhain
|
|
|
|
# Stripe (Test-Keys aus dem Stripe-Dashboard, https://dashboard.stripe.com/test/apikeys)
|
|
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
# Webhook-Secret aus `stripe listen` bzw. dem Stripe-Dashboard-Webhook-Endpoint
|
|
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
|
|
# Admin-Zugang: Der interne /admin-Bereich wird NICHT per Basic-Auth
|
|
# geschuetzt, sondern ueber das normale Kundenkonto-Login (/konto) und die
|
|
# Rolle "ADMIN" am Konto (siehe README, Abschnitt "Admin-Zugang"). Diese
|
|
# beiden Werte legt `prisma db seed` als initiales Admin-Konto an, falls es
|
|
# noch nicht existiert. Eine UNVORHERSEHBARE E-Mail waehlen (nicht
|
|
# "admin@..." o.ae.) — sonst koennte sich vor dem ersten Seed-Lauf jemand
|
|
# genau diese Adresse selbst registrieren; prisma/seed.ts befoerdert ein
|
|
# bereits bestehendes Nicht-Admin-Konto zwar nicht mehr automatisch, ein
|
|
# unvorhersehbarer Wert ist trotzdem die robustere erste Verteidigungslinie.
|
|
ADMIN_EMAIL=[email protected]
|
|
ADMIN_PASSWORD=change-me
|
|
|
|
# Geheimer Schluessel zum Signieren der Kundenkonto-JWTs (HS256).
|
|
# In Produktion MINDESTENS 32 Zeichen (wird beim Start erzwungen).
|
|
# Erzeugen z.B. mit: openssl rand -base64 32
|
|
SESSION_SECRET=change-me-to-a-long-random-string
|
|
|
|
# Basis-URL der Seite (fuer absolute Links, z.B. in E-Mails/Sitemaps).
|
|
# Lokale Entwicklung (pnpm dev): http://localhost:3000 (Standard hier).
|
|
# Vollstaendiger Docker-Betrieb: http://localhost (Caddy laeuft auf Port 80,
|
|
# siehe SITE_ADDRESS/Caddyfile unten) bzw. die echte Domain in Produktion.
|
|
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
|
|
|
# Nur fuer den vollstaendigen Docker-Betrieb (docker-compose.yml, Caddyfile):
|
|
# die oeffentliche Domain, unter der die Seite erreichbar sein soll. Leer
|
|
# gelassen laeuft Caddy lokal per HTTP auf Port 80 ohne TLS — fuer eine
|
|
# echte Domain mit automatischem HTTPS z.B. "wabenhain.com" eintragen.
|
|
SITE_ADDRESS=
|
|
|
|
# Google Rezensionen (optional). Ohne diese beiden Werte zeigt die Startseite
|
|
# automatisch die statischen Beispiel-Stimmen als Fallback.
|
|
# API-Key: Google-Cloud-Projekt mit aktivierter "Places API".
|
|
# Place-ID: https://developers.google.com/maps/documentation/places/web-service/place-id
|
|
GOOGLE_PLACES_API_KEY=
|
|
GOOGLE_PLACE_ID=
|
|
|
|
# Cloudflare Turnstile (optional). Ohne diese beiden Werte wird auf
|
|
# Registrierung, Newsletter und Firmenanfrage kein CAPTCHA angezeigt und die
|
|
# serverseitige Pruefung uebersprungen. Site-Key ist oeffentlich (NEXT_PUBLIC_),
|
|
# Secret-Key bleibt privat. Einrichtung: https://dash.cloudflare.com/?to=/:account/turnstile
|
|
TURNSTILE_SECRET_KEY=
|
|
NEXT_PUBLIC_TURNSTILE_SITE_KEY=
|
|
|
|
# SMTP fuer Transaktions-E-Mails (Bestellbestaetigung, Newsletter-Opt-in,
|
|
# Passwort-Reset). Ohne diese drei Werte werden E-Mails nur geloggt statt
|
|
# versendet — praktisch fuer lokale Entwicklung ohne Mail-Setup.
|
|
# Funktioniert mit jedem SMTP-Anbieter (z.B. das E-Mail-Postfach eures
|
|
# Domain-Hosters wie IONOS/Strato, oder ein Dienst wie Resend/Postmark im
|
|
# SMTP-Modus).
|
|
SMTP_HOST=
|
|
SMTP_PORT=587
|
|
SMTP_USER=
|
|
SMTP_PASSWORD=
|
|
SMTP_FROM="Wabenhain <[email protected]>"
|
|
|
|
# ApiCore-Synchronisierung (optional). Zwei Knoepfe nutzen sie: admin/ernten
|
|
# -> "ApiCore-Synchronisierung" (Ernten + Chargen) und admin/produkte ->
|
|
# "ApiCore-Synchronisierung" (Produktkatalog). Ohne diese drei Werte zeigen
|
|
# beide einen Konfigurationsfehler statt eines Ergebnisses. ApiCore ist ein
|
|
# separates Projekt (NestJS-Backend) mit reinem E-Mail/Passwort-Login (kein
|
|
# API-Key-Mechanismus) — hier IDEALERWEISE die Zugangsdaten eines
|
|
# dedizierten, MFA-freien Service-Kontos eintragen, nicht die eines echten
|
|
# Admin-Zugangs. Benoetigte Berechtigungen, alle nur lesend:
|
|
# harvests:read Ernten
|
|
# products:read Produktkatalog
|
|
# extractions:read Schleuderungen (verbinden Charge und Ernte)
|
|
# bottling:read Abfuellungen = Chargen
|
|
# apiaries:read optional, nur fuer die Standortnamen an den Ernten
|
|
# Details siehe src/lib/apicore-client.ts.
|
|
APICORE_API_URL=
|
|
APICORE_SYNC_EMAIL=
|
|
APICORE_SYNC_PASSWORD=
|
|
|
|
# DHL Parcel DE Shipping API ("Post & Parcel Germany", developer.dhl.com) —
|
|
# ermoeglicht das Erstellen von DHL-Versandmarken per Knopfdruck im
|
|
# Admin-Bestelldetail (siehe src/lib/dhl.ts). Ohne diese fuenf Werte zeigt
|
|
# der Button dort nur den Hinweis "DHL ist noch nicht konfiguriert".
|
|
#
|
|
# So kommt ihr an die Werte:
|
|
# 1. Account anlegen auf https://developer.dhl.com und eine App registrieren,
|
|
# die die API "Parcel DE Shipping" (Post & Parcel Germany) einbindet.
|
|
# Die App liefert DHL_API_KEY (Consumer Key) und DHL_API_SECRET
|
|
# (Consumer Secret). Fuer den Sandbox-Test reichen diese beiden Werte
|
|
# plus die DHL-Testzugangsdaten unten.
|
|
# 2. Fuer den PRODUKTIV-Betrieb zusaetzlich einen DHL-Geschaeftskundenvertrag
|
|
# ("DHL Paket" Versandvertrag) abschliessen — Ansprechpartner ist der
|
|
# DHL-Vertrieb bzw. https://www.dhl.de/de/geschaeftskunden/paket.html.
|
|
# Darueber bekommt ihr:
|
|
# - Zugangsdaten zum Geschaeftskundenportal (GKP) -> DHL_GKP_USERNAME/
|
|
# DHL_GKP_PASSWORD (dieselben Zugangsdaten, mit denen ihr euch auf
|
|
# dhl.de/geschaeftskundenportal einloggt).
|
|
# - Die EKP (10-stellige Kundennummer) plus Verfahren/Teilnahme fuer
|
|
# das gebuchte Produkt (Standard-Inlandspaket V01PAK) -> zusammen
|
|
# ergibt das die 14-stellige DHL_ACCOUNT_NUMBER (billingNumber).
|
|
# 3. DHL_ENVIRONMENT auf "sandbox" lassen, bis alles funktioniert, dann auf
|
|
# "production" umstellen (unterschiedliche Basis-URLs, siehe lib/dhl.ts).
|
|
#
|
|
# DHL-Sandbox-Testzugangsdaten (Stand der Recherche, koennen sich aendern —
|
|
# im Zweifel im DHL-Developer-Portal unter der App nachschauen):
|
|
# DHL_GKP_USERNAME=user-valid
|
|
# DHL_GKP_PASSWORD=SandboxPasswort2023!
|
|
# HINWEIS: Fuer die PACKSTATION-SUCHE im Checkout reicht DHL_API_KEY allein.
|
|
# Sie laeuft ueber die DHL Location Finder API (lib/dhl-locations.ts), einen
|
|
# eigenen Dienst mit eigener Authentifizierung — nur der API-Key im Header,
|
|
# kein OAuth und kein Geschaeftskundenvertrag. Ist der Key gesetzt, bietet der
|
|
# Checkout "An eine Packstation" als Lieferziel an; fehlt er, erscheint die
|
|
# Option gar nicht erst. Die uebrigen DHL_*-Werte braucht erst das Erstellen
|
|
# von Versandmarken im Admin-Bereich. Kontingent laut DHL: 500 Aufrufe/Tag —
|
|
# der Endpunkt /api/shop/packstationen ist deshalb streng rate-limitiert und
|
|
# cached Ergebnisse 15 Minuten.
|
|
DHL_API_KEY=
|
|
DHL_API_SECRET=
|
|
DHL_GKP_USERNAME=
|
|
DHL_GKP_PASSWORD=
|
|
# 14-stellig: EKP (10-stellig) + Verfahren (2-stellig) + Teilnahme (2-stellig)
|
|
DHL_ACCOUNT_NUMBER=
|
|
DHL_ENVIRONMENT=sandbox
|
|
|
|
# Microsoft Entra ID SSO ("Mit Microsoft anmelden", siehe
|
|
# src/app/api/auth/entra/). Ohne diese vier Werte zeigt der Button auf
|
|
# /konto einen Hinweis, dass SSO noch nicht konfiguriert ist, statt
|
|
# abzustuerzen.
|
|
#
|
|
# So richtet ihr es ein (Owner-Aufgabe im Azure-Portal, https://entra.microsoft.com):
|
|
# 1. Entra ID -> App registrations -> "New registration".
|
|
# - Name frei waehlbar, z.B. "Wabenhain Kundenlogin".
|
|
# - "Supported account types": i.d.R. "Accounts in this organizational
|
|
# directory only" (Single Tenant) fuer eine reine Firmen-SSO-Anbindung.
|
|
# - Redirect URI: Plattform "Web", URL = <eure Domain>/api/auth/entra/callback
|
|
# (lokal: http://localhost:3000/api/auth/entra/callback).
|
|
# 2. Nach dem Erstellen: "Application (client) ID" -> ENTRA_CLIENT_ID,
|
|
# "Directory (tenant) ID" -> ENTRA_TENANT_ID (beide auf der Overview-Seite
|
|
# der App-Registrierung sichtbar).
|
|
# 3. "Certificates & secrets" -> "New client secret" erzeugen -> der
|
|
# angezeigte WERT (nicht die Secret-ID!) -> ENTRA_CLIENT_SECRET. Nur
|
|
# einmal sichtbar, bei Verlust ein neues Secret erzeugen.
|
|
# 4. ENTRA_REDIRECT_URI = exakt dieselbe URL wie die in Schritt 1 hinterlegte
|
|
# Redirect-URI (muss zeichengenau uebereinstimmen).
|
|
# 5. "Enterprise applications" in Entra ID zeigt die App danach automatisch
|
|
# als Unternehmensapplikation (wird beim App-Registration-Schritt
|
|
# automatisch mit angelegt) — dort laesst sich optional der Nutzerkreis
|
|
# (z.B. nur bestimmte Gruppen) einschraenken.
|
|
ENTRA_TENANT_ID=
|
|
ENTRA_CLIENT_ID=
|
|
ENTRA_CLIENT_SECRET=
|
|
ENTRA_REDIRECT_URI=http://localhost:3000/api/auth/entra/callback
|
|
|
|
# Verzeichnis fuer hochgeladene Bilder (Inhalte-Bild in admin/inhalte,
|
|
# Produkt-/Erntenfotos in admin/produkte bzw. admin/ernten — siehe
|
|
# app/api/admin/upload/route.ts und app/uploads/[...path]/route.ts). Die App
|
|
# liest/schreibt hier ganz normal per node:fs; sie weiss nichts davon, WAS
|
|
# dieses Verzeichnis in Produktion ist (siehe unten).
|
|
#
|
|
# Lokal reicht der Default "./uploads" (relativ zum Projektverzeichnis, per
|
|
# .gitignore ausgeschlossen — beim ersten Upload automatisch angelegt).
|
|
#
|
|
# In docker-compose.yml ist UPLOAD_DIR bereits fest auf /app/uploads gesetzt
|
|
# und ueber ein benanntes Docker-Volume persistiert (siehe dort) — hier
|
|
# nichts weiter noetig, dieser Wert wirkt nur fuer lokales "npm run dev".
|
|
#
|
|
# In Produktion per Kubernetes (siehe k8s/05-app-deployment.yaml) zeigt
|
|
# UPLOAD_DIR stattdessen auf einen Volume-Mount, damit hochgeladene Bilder
|
|
# einen Pod-Neustart/-Redeploy ueberleben — welche Art Volume das konkret
|
|
# ist (SMB/NFS/hostPath/...), entscheidet die Betreiberin beim Anlegen des
|
|
# PersistentVolume; UPLOAD_DIR muss nur exakt auf den gewaehlten mountPath
|
|
# zeigen.
|
|
UPLOAD_DIR=./uploads
|