Timo f5e06c32b0 Forward COOKIE_DOMAIN to the app container in docker-compose
Without it, the subdomain deployment (app.<domain>/admin.<domain>
sharing a login with the bare domain) would silently get host-only
cookies since the value never reached the container's environment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 21:09:47 +02:00

ScanReceipts — Receipt Scanner App

Next.js 15 App Router App zum Scannen/Digitalisieren von Belegen (KI-Extraktion, Review-UI, Export nach XLSX/CSV/JSON). Lokal läuft die App ohne Datenbank (Local-First IndexedDB im Browser); PostgreSQL wird für Accounts, Abos (Stripe) und den Admin-Bereich gebraucht.

Diese Datei ist der Einstiegspunkt. Tiefere Details stehen in:

Thema Datei
Auth / SMTP / Google Login im Detail AUTH_SETUP_GUIDE.md
Stripe / Preise / Webhooks im Detail STRIPE_SETUP_GUIDE.md
nginx / Security-Header / blockierte Pfade docs/SECURITY_DEPLOYMENT.md
Architektur, Code-Layout, Feature-Liste PROJECT.md
Was beim Security-Hardening gemacht wurde SECURITY_HARDENING.md, SECURITY_VERIFICATION.md

1. Voraussetzungen

  • Node.js 20+ und npm
  • Docker Desktop (für Postgres lokal und/oder für den Produktions-Build)
  • Ein .env.local (siehe unten) — ohne DATABASE_URL läuft die App im Local-Only-Modus (kein Login/Stripe, aber Scannen funktioniert)

Achtung Port-Konflikt: Auf dieser Maschine laufen ~10 Docker-Stacks. Port 5432 ist von einem anderen Projekt belegt — dieses Projekt nutzt bewusst 5436 auf dem Host (siehe docker-compose.yml). Beim Anlegen von DATABASE_URL in .env.local immer localhost:5436 verwenden, nicht 5432.


2. Schnellstart (lokale Entwicklung)

npm install
cp .env.example .env.local

.env.local öffnen und mindestens OPENROUTER_API_KEY (oder einen anderen Vision-Key) eintragen, damit das Scannen funktioniert. Alles andere (Datenbank, Stripe, Google-Login, SMTP) ist optional für den reinen Scan-Workflow.

Nur Postgres per Docker starten (App läuft weiter direkt mit npm run dev, nicht im Container):

docker compose up -d postgres

Postgres ist danach unter localhost:5436 erreichbar. In .env.local:

DATABASE_URL=postgresql://receipt_user:receipt_secure_password@localhost:5436/receipt_scanner

Dev-Server starten:

npm run dev

http://localhost:3000. Der erste Request, der die DB anfasst, legt das Schema automatisch an (siehe Abschnitt 3.1) — ein manueller Migrationsschritt ist für lokale Entwicklung nicht zwingend nötig.

Server neustarten: einfach Strg+C und npm run dev erneut ausführen. Bei hängenden Prozessen/kaputtem .next-Cache (kommt auf dieser Maschine gelegentlich vor):

# .next Ordner löschen und neu starten
rm -rf .next
npm run dev

3. Datenbank

Zwei Rollen sind vorgesehen (siehe PROJECT.md für die volle Begründung):

  • receipt_userOwner-Rolle, macht Migrationen/Schema-Änderungen (DDL).
  • receipt_appRuntime-Rolle, nur SELECT/INSERT/UPDATE/DELETE, kein DDL. Wird von scripts/db-permissions.sql angelegt. In Produktion sollte die App mit dieser eingeschränkten Rolle laufen (APP_DATABASE_URL), Migrationen laufen weiter über die Owner-Rolle (DATABASE_URL).

3.1 Datenbank neu erstellen / Schema anlegen

Drei gleichwertige Wege, je nach Situation:

  1. Automatisch (einfachster Weg): Einfach die App starten (npm run dev oder den Docker-Container). Beim ersten DB-Zugriff prüft src/lib/db/init.ts, ob das Schema existiert, und legt es sonst automatisch an (über die drizzle-Migrationsdateien, mit idempotentem SQL-Fallback, falls die Migrationsdateien zur Laufzeit fehlen — z. B. im Docker-Image).
  2. Explizit per Drizzle Kit (empfohlen während der Entwicklung, wenn man das Schema in src/lib/schema/db.ts ändert):
    npm run db:generate   # neue Migrationsdatei aus Schema-Änderungen erzeugen
    npm run db:push       # Schema direkt in die DB pushen (ohne Migrationsdatei)
    npm run db:studio     # Drizzle Studio GUI zum Browsen der Daten
    
  3. Frisches Docker-Volume: docker compose up postgres auf einem noch nie benutzten Volume führt automatisch scripts/db-permissions.sql aus (Postgres-Init-Mechanismus) — legt dabei direkt auch die Runtime-Rolle receipt_app an.

3.2 Datenbank zurücksetzen (alle Daten löschen)

Docker-Setup (empfohlen für lokal):

docker compose down
docker volume rm receiptscannerapp_postgres_data   # Name ggf. mit `docker volume ls` prüfen
docker compose up -d postgres

Das erzeugt ein komplett frisches Volume; Schema + Berechtigungen werden beim nächsten App-Start automatisch wieder angelegt (siehe 3.1).

Ohne Docker / bestehende DB behalten, nur Tabellen leeren: per psql oder Drizzle Studio (npm run db:studio) die Tabellen manuell leeren — es gibt aktuell kein db:reset-Script, das gezielt nur Daten löscht.

3.3 Berechtigungen (Least-Privilege-Rolle) anwenden/prüfen

Nötig, wenn eine bereits bestehende Datenbank auf die eingeschränkte Runtime-Rolle nachgerüstet werden soll (bei einem frischen Docker-Volume passiert das automatisch, siehe 3.1 Punkt 3):

node scripts/apply-db-permissions.mjs        # legt/aktualisiert die Rolle receipt_app
node scripts/verify-db-permissions.mjs       # prüft: kein Superuser, kein DDL, DML funktioniert

Beide Scripts lesen DATABASE_URL aus .env.local (Owner-Verbindung nötig).

3.4 Admin-User anlegen

node scripts/create-admin.ts

Nutzt ADMIN_EMAILS (erste Adresse in der kommagetrennten Liste) und optional ADMIN_PASSWORD (Default: fiestain Produktion immer setzen) aus .env.local. Legt den User an oder setzt Passwort + E-Mail-Verifizierung zurück, falls er schon existiert.


4. Website starten / neustarten

4.1 Lokal (ohne Docker)

npm run dev      # Entwicklung, mit Turbopack + Hot Reload
npm run build    # Produktions-Build
npm run start    # Produktions-Server aus dem Build starten (Port 3000)

4.2 Kompletter Stack per Docker Compose (App + Postgres)

docker compose up -d --build     # baut das Image und startet App + DB im Hintergrund
docker compose logs -f app       # Logs der App live mitlesen
docker compose ps                # Status/Healthchecks prüfen

Neustarten (z. B. nach .env-Änderungen, die kein Rebuild brauchen):

docker compose restart app

Neu bauen und neustarten (nach Code-Änderungen):

docker compose up -d --build app

Alles stoppen (Daten bleiben im Volume erhalten):

docker compose down

docker-compose.yml liest seine Werte aus der Shell-Umgebung bzw. einer .env-Datei im Repo-Root (nicht zu verwechseln mit .env.local, das nur Next.js selbst liest) — für den Docker-Weg müssen die Variablen also in .env stehen, siehe .env.example als Vorlage.


5. Live-Server / Deployment-Checkliste

5.1 Environment-Variablen

Alle Variablen sind in .env.example dokumentiert (mit Erklärung, wofür sie sind). Kurzfassung, was auf dem Live-Server zusätzlich zur lokalen Config wichtig ist:

  • NODE_ENV=production
  • NEXT_PUBLIC_APP_URLmuss https:// sein (die App ist HTTPS-only, erzwingt HSTS). Wird zur Build-Zeit in den Client-Bundle eingebacken, also als Docker-Build-Arg mitgeben (macht docker-compose.yml automatisch über build.args).
  • COOKIE_DOMAIN setzen, falls Dashboard/Admin auf Subdomains laufen (z. B. .scan-receipts.app), sonst leer lassen.
  • CORS_ORIGINS nur setzen, wenn wirklich Cross-Origin-API-Zugriff gebraucht wird (nie *).
  • SMTP_* sind in Produktion Pflicht — ohne SMTP schlägt die Registrierung bewusst fehl, statt einen unbestätigbaren Account anzulegen.
  • GOOGLE_CLIENT_ID/SECRET nur wenn Google-Login aktiv sein soll (Redirect-URI in der Google Cloud Console muss exakt <NEXT_PUBLIC_APP_URL>/api/auth/google/callback sein).
  • STRIPE_* siehe STRIPE_SETUP_GUIDE.md — Webhook-Secret ist pflicht, sonst werden keine Lizenzen vergeben.
  • ADMIN_EMAILS + starkes ADMIN_PASSWORD für scripts/create-admin.ts.
  • APP_DATABASE_URL + APP_DATABASE_PASSWORD setzen und die App damit laufen lassen (nicht mit der Owner-DATABASE_URL) — siehe Abschnitt 3.

Es liegt bereits ein .env.deployment als Referenz im Repo-Root — das ist die Vorlage mit den auf dem Live-Server tatsächlich benötigten Keys (Werte selbst niemals ins Git-Repo committen).

5.2 Erstmaliges Deployment

# auf dem Server, im Projektverzeichnis
docker compose up -d --build
  1. Postgres startet zuerst, Healthcheck muss grün sein, bevor die App startet (depends_on: condition: service_healthy, ist in docker-compose.yml schon konfiguriert).
  2. Auf einem frischen Volume werden Runtime-Rolle + Berechtigungen automatisch angelegt (Abschnitt 3.1 Punkt 3).
  3. Schema wird beim ersten Request automatisch angelegt (Abschnitt 3.1 Punkt 1).
  4. Admin-User anlegen: node scripts/create-admin.ts (mit Produktions-.env.local bzw. den Server-Umgebungsvariablen).
  5. nginx davorschalten — die App selbst bindet nur intern an 127.0.0.1:3000, TLS/HSTS/Security-Header kommen von nginx:
    • nginx.conf.example nach /etc/nginx/conf.d/receipt-scanner.conf kopieren, Platzhalter (example.com, Zertifikatspfade) ersetzen.
    • TLS-Zertifikat besorgen (z. B. Let's Encrypt).
    • nginx -t && systemctl reload nginx.
    • Details/Begründung: docs/SECURITY_DEPLOYMENT.md.
  6. Stripe-Webhook-Endpoint auf https://<domain>/api/stripe/webhook im Stripe-Dashboard eintragen (siehe STRIPE_SETUP_GUIDE.md Abschnitt 5).

5.3 Redeploy nach Code-Änderungen

git pull
docker compose up -d --build app

Datenbank-Migrationen laufen automatisch beim ersten Request nach dem Neustart mit (Abschnitt 3.1). Bei Schema-Änderungen, die Drizzle nicht automatisch sauber migrieren kann, vorher lokal gegen eine Kopie der Prod-DB mit npm run db:generate eine Migration erzeugen und testen.

5.4 Neustart ohne Rebuild

docker compose restart app

5.5 Logs & Healthcheck prüfen

docker compose logs -f app
docker compose logs -f postgres
curl -f http://127.0.0.1:3000/api/auth/providers   # das nutzt auch der Docker-Healthcheck

5.6 Backup

Das Postgres-Datenverzeichnis liegt im benannten Volume postgres_data. Dump vor jedem größeren Deployment:

docker exec scanreceipts_postgres pg_dump -U receipt_user receipt_scanner > backup_$(date +%Y%m%d_%H%M%S).sql

6. Nützliche Scripts (Auswahl)

Command Zweck
npm run dev / build / start Dev-Server / Produktions-Build / Produktions-Server
npm run db:generate / db:push / db:studio Drizzle-Migrationen erzeugen / pushen / GUI
npm run test E2E-Testsuite (tests/e2e/runner.ts)
npm run test:auth Auth/DB-Integrationstest
node scripts/create-admin.ts Admin-User anlegen/zurücksetzen
node scripts/apply-db-permissions.mjs Least-Privilege-Runtime-Rolle anlegen/aktualisieren
node scripts/verify-db-permissions.mjs Rechte-Setup verifizieren
node scripts/verify_sensitive_paths.mjs Prüft, dass sensible Pfade (.env, .git, …) blockiert sind
node scripts/verify_cors.mjs CORS-Policy verifizieren
node scripts/verify_rate_limit.mjs Rate-Limiting (Passwort-Reset u. a.) verifizieren

Weitere verify_*-Scripts in scripts/ decken einzelne Security-Hardening- Maßnahmen ab (siehe SECURITY_HARDENING.md für den Kontext, wofür jedes Script steht).


7. Troubleshooting

  • "DATABASE_URL is not set".env.local fehlt oder wurde nicht geladen; Next.js liest .env.local automatisch, Node-Scripts (drizzle-kit, die scripts/*.mjs) laden es teils manuell — prüfen, ob die Datei existiert.
  • Port 5432 schon belegt → dieses Projekt läuft absichtlich auf 5436, siehe Kasten oben in Abschnitt 1.
  • .next-Cache kaputt / komische Build-Fehlerrm -rf .next und neu starten (ist auf dieser Maschine schon mehrfach vorgekommen, siehe die .next-corrupt-*-Ordner im Repo-Root).
  • Google-Login-Button erscheint nichtGOOGLE_CLIENT_ID/SECRET fehlen; der Button rendert nur, wenn beide gesetzt sind.
  • Signup schlägt mit mail_failed fehl → in Produktion (NODE_ENV=production) ist SMTP Pflicht, es gibt keinen Dev-Fallback.
Description
No description provided
Readme 23 MiB
Languages
TypeScript 91.4%
JavaScript 6.4%
HTML 1.9%
CSS 0.2%