Initial commit: spreewaldzeit + Dockerfile for Coolify (Next.js + Prisma/SQLite)

This commit is contained in:
2026-06-03 14:08:48 +02:00
committed by Ihor_Zhekov
commit bf5d79a919
94 changed files with 12480 additions and 0 deletions

191
README.md Normal file
View File

@@ -0,0 +1,191 @@
# Spreewaldzeit — MVP
Private Ferienwohnungs-Website mit zwei Wohnungen. Ruhig, editorial, wartungsarm.
Technik-Stack bewusst schlank gehalten — kein Hotel-PMS-Overhead, aber vorbereitet
für spätere Erweiterungen (iCal-Sync, Direktbuchung, Zahlungsabwicklung).
## Tech-Stack
- **Next.js 14** (App Router, Server Components)
- **TypeScript**, strict
- **Prisma** + **SQLite** (einfach portierbar nach Postgres/MySQL)
- **Tailwind CSS** mit eigener Naturpalette
- **React Hook Form** + **Zod** für Formulare & Validierung
- **jose** für signierte Session-Cookies (kein NextAuth-Overhead)
- **nodemailer** für Mailversand (mit Dev-Fallback ins Terminal)
- **react-day-picker** für den Verfügbarkeitskalender
## Projektstruktur
```
spreewaldzeit/
├── app/
│ ├── layout.tsx # Root-Layout + Google Fonts
│ ├── globals.css # Tailwind-Layer + globale Styles
│ ├── page.tsx # Startseite
│ ├── not-found.tsx
│ ├── wohnungen/[slug]/
│ │ └── page.tsx # Wohnungsdetail
│ ├── anfrage/
│ │ └── page.tsx # Anfrageformular
│ ├── datenschutz/page.tsx
│ ├── impressum/page.tsx
│ ├── admin/
│ │ ├── layout.tsx
│ │ ├── page.tsx # Redirect → Anfragen
│ │ ├── login/page.tsx
│ │ ├── anfragen/page.tsx # Anfragen-Liste
│ │ ├── kalender/page.tsx # Blocks sperren/freigeben
│ │ └── wohnungen/page.tsx # Wohnungen pflegen
│ └── api/
│ ├── inquiries/route.ts # POST (öffentlich)
│ ├── availability/[slug]/route.ts # GET (öffentlich)
│ └── admin/
│ ├── login/route.ts
│ ├── logout/route.ts
│ ├── inquiries/[id]/route.ts # PATCH, DELETE
│ ├── blocks/route.ts # POST
│ ├── blocks/[id]/route.ts # DELETE
│ └── apartments/[id]/route.ts # PATCH
├── components/
│ ├── layout/ (Header, Footer)
│ ├── home/ (Hero, About, ApartmentPreview, Location)
│ ├── apartment/ (Gallery, Features, AvailabilityCalendar)
│ ├── inquiry/ (InquiryForm)
│ ├── admin/ (AdminNav, InquiryRow, CalendarManager, ApartmentEditor)
│ └── ui/ (Button, Input, Label, Textarea, FieldError)
├── lib/
│ ├── db.ts # Prisma-Singleton
│ ├── auth.ts # JWT-Session via jose
│ ├── email.ts # Nodemailer + Mail-Templates
│ ├── validations.ts # Zod-Schemas
│ └── utils.ts # Preis-/Datumsformatierung
├── prisma/
│ ├── schema.prisma # Apartment, Inquiry, Block, Admin
│ └── seed.ts # 2 Wohnungen + Admin + 1 Beispiel-Block
├── types/
├── middleware.ts # schützt /admin und /api/admin
├── tailwind.config.ts
├── next.config.js
├── tsconfig.json
├── .env.example
└── package.json
```
## Setup
### 1. Abhängigkeiten installieren
```bash
npm install
```
### 2. Environment vorbereiten
```bash
cp .env.example .env
```
Dann `.env` öffnen und **mindestens** setzen:
- `AUTH_SECRET` — zufällige, mind. 32 Zeichen lange Zeichenkette
(z. B. `openssl rand -base64 48`)
- `ADMIN_EMAIL` und `ADMIN_PASSWORD` — initialer Admin-Account
- `OWNER_EMAIL` — wohin sollen neue Anfragen gehen?
Die SMTP-Variablen sind **optional**. Ohne sie werden Mails ins Terminal geloggt
(praktisch fürs lokale Entwickeln).
### 3. Datenbank + Seed
```bash
npm run setup
```
Das führt die Prisma-Migration aus und legt die zwei Beispiel-Wohnungen samt
Admin-Account an.
### 4. Dev-Server
```bash
npm run dev
```
→ [http://localhost:3000](http://localhost:3000)
→ Admin: [http://localhost:3000/admin/login](http://localhost:3000/admin/login)
## Nützliche Scripts
| Script | Zweck |
|---------------------|----------------------------------------------------|
| `npm run dev` | Dev-Server mit Hot-Reload |
| `npm run build` | Production-Build (inkl. `prisma generate + migrate`)|
| `npm run start` | Production-Server |
| `npm run db:push` | Schema ohne Migration in DB pushen (prototyping) |
| `npm run db:migrate`| Neue Migration erzeugen |
| `npm run db:seed` | Seed erneut laufen lassen |
| `npm run db:studio` | Prisma Studio öffnen (DB-Browser) |
## Design-System
- **Farben:** `parchment` (Hintergrund), `ink` (Text), `moss` & `sand` (Akzente)
- **Typografie:** *Fraunces* (Display) + *Figtree* (Body), geladen via `next/font`
- **Layout:** Container mit großzügigem Whitespace, asymmetrische Grids
- **Bildsprache:** Große Bilder, ruhige Kompositionen, dezente Hover-Zooms
Alle Tokens in `tailwind.config.ts`.
## Wichtige Designentscheidungen
### Warum SQLite?
Ein Reiseobjekt mit zwei Wohnungen erzeugt wenig Schreiblast. SQLite ist schnell,
wartungsarm und das Schema ist 1:1 auf Postgres/MySQL portierbar, sobald gebraucht.
### Warum JWT-Cookie statt NextAuth?
Für einen Admin-User + zwei Gastgeber ist NextAuth Overkill. `jose` liefert
signierte Cookies in ~80 Zeilen Code, die Middleware-kompatibel (Edge) funktionieren.
### Warum `Block.source` trotz MVP?
Damit der spätere iCal-Importer einfach zusätzliche Einträge mit
`source: "airbnb"` / `source: "booking"` schreiben kann — ohne Schema-Änderung.
### Honeypot statt Captcha
Das Anfrageformular hat ein verstecktes `website`-Feld als Bot-Falle.
Für den MVP ausreichend; falls Spam zunimmt, lässt sich hCaptcha o. ä. ergänzen.
## Erweiterungspfad
Der Code ist bewusst so strukturiert, dass die folgenden Features **additiv**
eingebaut werden können — ohne Refactoring:
### iCal-Sync (Airbnb / Booking.com)
- Cron-Job (z. B. via Vercel Cron oder externer Scheduler), der pro Wohnung
eine `ics_import_url` lädt, parst und Blocks mit `source: "airbnb"` /
`source: "booking"` anlegt. Schema muss ggf. um `Apartment.icalUrls` erweitert werden.
- Umgekehrt: GET-Endpoint `/api/ical/[slug].ics`, der alle Blocks als ICS ausliefert,
damit Airbnb/Booking wiederum eure Belegung sehen.
### Direktbuchung
- Stripe/Mollie-Integration im `/api/bookings/route.ts`.
- `Booking`-Tabelle einführen; erfolgreiche Zahlungen legen automatisch einen
`Block` mit `source: "direct"` an.
### Mehr Inhalte
- Tagespreise / Saisonpreise (neues `PriceRule`-Modell).
- Blog/Storys (neues `Post`-Modell + `/journal`-Route).
### Bildupload
- Aktuell pflegt der Editor Bilder als URLs. Für echtes Hochladen:
S3/R2/Cloudinary-Adapter in `lib/storage.ts` und Drag-and-Drop-Komponente
im `ApartmentEditor`.
## DSGVO-Notizen
- Keine Marketing-Cookies, kein Tracking.
- Nur technisch notwendige Session-Cookies (httpOnly, SameSite=Lax, Secure in Prod).
- Anfragen werden mit ausdrücklicher Einwilligung verarbeitet (Checkbox im Formular).
- Datenschutz- und Impressums-Seite als Platzhalter angelegt — **vor Go-Live rechtlich prüfen lassen**.
## Lizenz
Privat / intern. Anpassen wie benötigt.