first commit
This commit is contained in:
330
README.md
Executable file
330
README.md
Executable file
@@ -0,0 +1,330 @@
|
|||||||
|
# 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](AUTH_SETUP_GUIDE.md) |
|
||||||
|
| Stripe / Preise / Webhooks im Detail | [STRIPE_SETUP_GUIDE.md](STRIPE_SETUP_GUIDE.md) |
|
||||||
|
| nginx / Security-Header / blockierte Pfade | [docs/SECURITY_DEPLOYMENT.md](docs/SECURITY_DEPLOYMENT.md) |
|
||||||
|
| Architektur, Code-Layout, Feature-Liste | [PROJECT.md](PROJECT.md) |
|
||||||
|
| Was beim Security-Hardening gemacht wurde | [SECURITY_HARDENING.md](SECURITY_HARDENING.md), [SECURITY_VERIFICATION.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)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .next Ordner löschen und neu starten
|
||||||
|
rm -rf .next
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Datenbank
|
||||||
|
|
||||||
|
Zwei Rollen sind vorgesehen (siehe [PROJECT.md](PROJECT.md#appendix-database-least-privilege)
|
||||||
|
für die volle Begründung):
|
||||||
|
|
||||||
|
- `receipt_user` — **Owner-Rolle**, macht Migrationen/Schema-Änderungen (DDL).
|
||||||
|
- `receipt_app` — **Runtime-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):
|
||||||
|
```bash
|
||||||
|
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):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node scripts/create-admin.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Nutzt `ADMIN_EMAILS` (erste Adresse in der kommagetrennten Liste) und
|
||||||
|
optional `ADMIN_PASSWORD` (Default: `fiesta` — **in 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)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # Entwicklung, mit Turbopack + Hot Reload
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose restart app
|
||||||
|
```
|
||||||
|
|
||||||
|
**Neu bauen und neustarten** (nach Code-Änderungen):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build app
|
||||||
|
```
|
||||||
|
|
||||||
|
**Alles stoppen** (Daten bleiben im Volume erhalten):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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](.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_URL` → **muss** `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](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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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](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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose restart app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.5 Logs & Healthcheck prüfen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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](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-Fehler** → `rm -rf .next` und neu
|
||||||
|
starten (ist auf dieser Maschine schon mehrfach vorgekommen, siehe die
|
||||||
|
`.next-corrupt-*`-Ordner im Repo-Root).
|
||||||
|
- **Google-Login-Button erscheint nicht** → `GOOGLE_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.
|
||||||
Reference in New Issue
Block a user