Files
Mieterportal/kimi.md
T
ah-admin 311153b3c3 v1.3: Serverseitige Datenbank (PostgreSQL + Node-API) ersetzt localStorage
- server/: Express-API (Auth, Bootstrap, Tickets, Threads, Benutzerverwaltung, Codes)
  mit pg-Schema (11 Tabellen), idempotem Seed (50 Wohnungen, 552 Buchungen)
- mp-db (PostgreSQL 16, Volume mp_pgdata) + mp-api im Compose-Stack,
  nginx proxiet /api/ an mp-api; deploy.sh baut API-Image auf dem Server
- Frontend: Store laedt /api/bootstrap, Schreibzugriff async, kein localStorage
  mehr (ausser Theme), Ladebildschirm beim Session-Check, vite-Proxy fuer Dev
- Rollenrechte serverseitig: Mieter nur eigene Wohnung (getestet, 403-Verweis)
- Mail-Benachrichtigungen komplett serverseitig (server/src/mail.js)
- beispieldaten.py schreibt server/seed/generated.json; Verwaltungs-Mail = axel@hocks.eu
- E2E live verifiziert: Login, Scoping, Ticket + Zustellung (status=sent),
  Registrierung, Codes; Testdaten bereinigt; Doku nachgefuehrt
2026-10-02 12:54:44 +02:00

246 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Kimi-Kontext: Hinterstein Immobilien · Mieterportal (mieterportal.hocks.eu)
> Projekt-Kontextdatei für Kimi Work. Stand: 02.10.2026, 13:00 Uhr.
> Bitte bei größeren Änderungen am Projekt aktualisieren.
> Konventionen und Workflow sind am Projekt Schulportal (sfg.hocks.eu) orientiert.
## 1. Projektüberblick
Mieterportal der **Hinterstein Immobilien** (Demo, Inhaber Joseph Hinterstein) für
**50 Wohnungen in 3 Häusern** (Hauptstraße 12 · Ringweg 3 · Am Park 5). Analog zum
Schulportal: alle Mietangelegenheiten digital an einem Ort.
**Datenhaltung (seit v1.3):** zentral serverseitig in **PostgreSQL** über eine
**Node-API** (Express) – kein localStorage mehr für Mietdaten. Mehrere Nutzer
sehen denselben Datenstand in Echtzeit; Rechte werden serverseitig erzwungen
(Mieter nur eigene Wohnung, Verwaltung alles).
**Authentifizierung:** Login-Pflicht auf `/login` (Route-Guard in `App.tsx`).
Gruppen `verwaltung` und `mieter`; Mieter registrieren sich per einmaligem
**Registrierungscode** (`/registrieren`). Passwörter als SHA-256-Hash (im Browser
gerechnet, Vergleich serverseitig), Sitzung als HttpOnly-Cookie `mp_session`
(8 h, Tabelle `sessions`). Demo-Zugänge (Passwort `0815`): `j.hinterstein`
(Verwaltung), `a.berger` (Mieterin, Hauptstraße 12 Whg. 7), `m.wolf` (Mieter,
Ringweg 3 Whg. 4). Demo-Registrierungscode: `MP-2026-0815`.
**Die 6 Module:**
1. **Dashboard** (`/`) – Mieter: Begrüßung, nächste Zahlung, Mietdiagramm, Ankündigungen,
offene Meldungen. Verwaltung: Kennzahlen (Vermietungsquote, Miet-Soll, Rückstände),
Einnahmenverlauf + Belegung pro Haus (Recharts).
2. **Mietkonto & Zahlungen** (`/mietkonto`) – Buchungsverlauf 2026 mit Statusfilter,
Nebenkostenguthaben, Zahlungsdaten. Verwaltung: Zahlungsstatus aller Wohnungen,
Rückstandsliste, Haus-Filter.
3. **Schadensmeldungen** (`/schadensmeldungen`) – Tickets mit Kategorie, Dringlichkeit,
Status; Neuanlage per Dialog; Verwaltung pflegt Status per Select.
4. **Dokumente** (`/dokumente`) – Verträge/Abrechnungen/Hausordnung/Protokolle,
Suche + Kategorie-Chips. Mieter sieht eigene + Hausdokumente (unitId=null).
5. **Nachrichten** (`/nachrichten`) – Chat Mieter ↔ Verwaltung, Ungelesen-Markierung,
neue Unterhaltungen.
6. **Benutzerverwaltung** (`/benutzer`, nur Gruppe `verwaltung`) – Nutzerliste mit
Gruppen und Wohnungsbezug, Registrierungscodes erzeugen/deaktivieren,
Demo-Zugänge zurücksetzen.
## 2. Technischer Stack
| Teil | Technologie |
|---|---|
| Frontend | React 18 + TypeScript, Vite 7, Tailwind v3.4 + shadcn/ui (Radix), React Router 7 (BrowserRouter), Recharts |
| State | Store-Kontext im Browser, **alle Daten serverseitig** (kein localStorage mehr); Ladezustand `bereit`, Schreibzugriff async |
| API | `server/`: Node 20 + Express + pg, Session-Cookie, Rollenrechte, serverseitige Mail-Benachrichtigungen |
| Datenbank | PostgreSQL 16 (`mp-db`), Schema/Seed in `server/src/db.js` + `seed.js`, Volume `mp_pgdata` |
| Daten | `server/seed/generated.json` (50 Wohnungen + 552 Buchungen via `beispieldaten.py`) + `server/src/seed-data.js` (handschriftlich) |
| Auth | SHA-256-Hash (Browser) + HttpOnly-Cookie-Session (8 h), `src/lib/api.ts`, `src/lib/auth.ts` |
| Branding | Kunde: `src/assets/hinterstein/` (logo.png, joseph-hinterstein.jpg, kontakt.jpg); KI-generiert: `src/assets/logo.png`, `hero.jpg`, `modules.jpg` |
| Webserver | Docker-Container `mp-portal` (nginx), statisches `dist/`, proxiet `/api/` → `mp-api:3000` |
| Mail | `mail-server/`: Postfix-Relay (`mp-mail`) + Python-API (`mp-mail-api`) – **live**, `/opt/mp-mail`; Portal-nginx proxyt `POST /api/mail/send` mit Token aus `portal.env` |
| Server | Root-VPS hocks.eu (IONOS), Docker + Portainer, Subdomain via NPM (Nginx Proxy Manager, Let's Encrypt) |
## 3. Wichtige Dateien
Projektverzeichnis: `C:\Users\ah\Documents\Kimi\Workspaces\Mieterportal`
**Git:** `https://git.hocks.eu/ah-admin/Mieterportal` (Branch `main`). Secrets
ausgesperrt via `.gitignore` (`mail-server/mail.env` etc.).
| Datei | Inhalt |
|---|---|
| `src/store/store.tsx` | Store-Kontext: lädt `/api/bootstrap`, stellt Collections + `me`/`role`/`meineEinheit` bereit, async-Methoden für alle Schreibaktionen, `fmtEUR` |
| `src/lib/api.ts` | Fetch-Client (same-origin, JSON, `ApiFehler` mit deutscher Meldung) |
| `src/lib/auth.ts` | SHA-256 für Passwörter, `DEMO_PASSWORT` |
| `server/src/index.js` | Express-API: Auth (Login/Logout/me/Register), Bootstrap, Tickets, Threads/Messages, Benutzerverwaltung, Codes, `/api/healthz` |
| `server/src/db.js` | pg-Pool + idempotentes Schema (11 Tabellen, snake_case) |
| `server/src/seed.js` | Idempotenter Seed (läuft nur bei leerer users-Tabelle) |
| `server/src/seed-data.js` | Handschriftliche Demo-Daten (Tickets, Dokumente, Threads, Ankündigungen, Benutzer, Codes) |
| `server/src/mail.js` | Serverseitiger Versand über mp-mail-api (fire-and-forget) |
| `server/seed/generated.json` | 50 Wohnungen + 552 Buchungen, erzeugt von `beispieldaten.py` (Seed 20261002) |
| `beispieldaten.py` | Deterministischer Generator: `python beispieldaten.py [seed]` → `server/seed/generated.json` |
| `src/types/index.ts` | Alle Typen (Unit, Transaction, Ticket, DocumentItem, Thread, Announcement, Benutzer, RegistrierungsCode) |
| `src/components/AppLayout.tsx` | Sidebar (Desktop) + Mobile Top-Bar/Nav, Logout, Ungelesen-Badge, ThemeToggle |
| `src/components/ThemeToggle.tsx` | Dark-Mode-Umschalter (Sonne/Mond) |
| `src/lib/theme.ts` | `mp-theme` in localStorage (nur noch Theme!), Init vor erstem Render (main.tsx) |
| `src/pages/Login.tsx` | Login-Pflicht: Hero mit Joseph-Hinterstein-Portrait, Logo, Feature-Liste, Login + Registrierung + Demo-Einstiege |
| `src/pages/BenutzerVerwaltung.tsx` | Benutzerverwaltung (nur Verwaltung): Nutzer anlegen/pflegen, Registrierungscodes erzeugen |
| `docker/templates/default.conf.template` | nginx-Site als envsubst-Template: SPA, `/api/` → mp-api, `POST /api/mail/send` → mp-mail-api mit `${MAIL_API_TOKEN}` |
| `docker-compose.yml` | Stack: `mp-portal` (nginx), `mp-api` (Node, Build ./server), `mp-db` (PostgreSQL 16, Volume mp_pgdata), Netz npm_default |
| `deploy.sh` | Deploy-Workflow: Build, Paket (dist + compose + nginx + server), Upload, `docker compose up -d --build`, Health-Check |
| `mail-server/` | Postfix-Relay + schlanke Mail-API (Muster Schulportal, angepasst) – separater Stack unter /opt/mp-mail |
## 4. Deploy-Workflow (bewährt, aus Schulportal übernommen)
```bash
# 1. Bauen (im Projektverzeichnis)
npm run build
# 2. Paketieren + Hochladen (pscp braucht cwd = Projektverzeichnis)
tar -czf mp-dist.tar.gz -C dist .
"/c/Program Files/PuTTY/pscp.exe" -batch \
-hostkey "SHA256:4G3UxsugNkHH6BrPXScLXh+vX8P1sLgWKn8cZBKSRxw" \
-i "Z:\Devel\ssh\priv_ionos_hocks_vps.ppk" -P 38592 \
mp-dist.tar.gz root@hocks.eu:/tmp/
# 3. Entpacken + Container neu starten + Hash prüfen
"/c/Program Files/PuTTY/plink.exe" -batch -hostkey "SHA256:4G3UxsugNkHH6BrPXScLXh+vX8P1sLgWKn8cZBKSRxw" \
-i "Z:\Devel\ssh\priv_ionos_hocks_vps.ppk" -P 38592 root@hocks.eu \
"rm -rf /opt/mieterportal/dist && mkdir -p /opt/mieterportal/dist && \
tar -xzf /tmp/mp-dist.tar.gz -C /opt/mieterportal/dist && \
cd /opt/mieterportal && docker compose up -d && sleep 2 && \
curl -s https://mieterportal.hocks.eu/ | grep -o 'index-[^\"]*\.js'"
# Alternativ alles in einem Schritt:
./deploy.sh
```
- SSH: Port **38592**, Key `Z:\Devel\ssh\priv_ionos_hocks_vps.ppk` (Pageant), Hostkey-Flag zwingend.
- **`portal.env` auf dem Server** (gitignored, liegt nur dort): liefert
`MAIL_API_TOKEN` (für nginx-envsubst und mp-api) und `POSTGRES_PASSWORD`
(für mp-db und die DATABASE_URL des mp-api). Einmalig angelegt:
`grep '^MAIL_API_TOKEN=' /opt/mp-mail/mail.env > /opt/mieterportal/portal.env`
plus zufälliges `POSTGRES_PASSWORD=…`. Wichtig: compose interpoliert
`${VARIABLE}` nur aus einer `.env`-Datei im Projektverzeichnis (nicht aus
`env_file:`!) – daher auf dem Server `/opt/mieterportal/.env` als
**Symlink auf portal.env**:
`cd /opt/mieterportal && ln -sf portal.env .env`
- deploy.sh macht `docker compose up -d --build` (baut das mp-api-Image auf
dem Server, npm ci im Container) und prüft danach Container-Status,
Portal-Hash und `GET /api/healthz`.
- **Vorsicht Fail2Ban:** zu viele gescheiterte SSH-Versuche von einer IP führen zu einem
Ban (Timeout-Verbindungsbild). Ban aufheben auf dem Server: `fail2ban-client unban --all`.
- **Live (seit 02.10.2026):** Container `mp-portal` läuft auf hocks.eu, statisches `dist/`
unter `/opt/mieterportal/dist` (gemountet). NPM-Proxy-Host **id 7** (Datei
`/data/compose/1/data/nginx/proxy_host/7.conf`, DB-Tabelle proxy_host), Zertifikat
**npm-9** (Let's Encrypt, läuft bis 31.12.2026; via certbot/webroot erstellt, liegt im
npm-Container unter `/etc/letsencrypt/live/npm-9/`). HTTP→HTTPS-Redirect aktiv
(HSTS, Block-Exploits, HTTP/2). Änderungen am Proxy-Host im NPM-UI werden die
DB-Konfiguration konsistent halten; manuelle Conf-Edits werden beim nächsten
UI-Speichern überschrieben.
- HTML wird mit `no-cache` geliefert (nginx-Config), Assets hash-benannt → Reload genügt.
Der eingebettete Kimi-Browser cached hart → beim Testen Cache-Buster anhängen: `?v=X`.
## 5. Zugänge & Konfiguration
- **Demo-Zugänge (Passwort `0815`):** `j.hinterstein` (Verwaltung), `a.berger`
(Mieterin, Hauptstraße 12 Whg. 7), `m.wolf` (Mieter, Ringweg 3 Whg. 4).
Demo-Registrierungscode: `MP-2026-0815`. Passwörter liegen als SHA-256-Hash
in der users-Tabelle; Sitzung als HttpOnly-Cookie (Tabelle sessions).
Abmelden reicht zum „Reset" – nichts liegt mehr im Browser-localStorage
(außer dem Dark-Mode-Theme).
- **Verwaltungs-E-Mail:** `axel@hocks.eu` (echtes, zustellbares Postfach, damit
Benachrichtigungen ankommen) – im Portal änderbar.
- **Mail-API-Token:** bewusst nicht hier dokumentiert – liegt auf dem Server in
`/opt/mp-mail/mail.env` (Vorlage `mail.env.example` im Repo).
- **Mail-Stack live (seit 02.10.2026):** Container `mp-mail` + `mp-mail-api` laufen
auf hocks.eu unter `/opt/mp-mail` (Relay über `hocks-eu.mail.protection.outlook.com`).
Testmail und API-`/send` (mit `X-Api-Token`) verifiziert (status=sent); ohne Token 401.
- **Mail-Benachrichtigungen (seit v1.2 nginx-Proxy, seit v1.3 serverseitig):** Der
Portal-nginx proxiet `POST /api/mail/send` an `http://mp-mail-api:8080/send` mit
Token aus `portal.env` (403 für alles außer POST). **Versand aus der Node-API**
(`server/src/mail.js`, fire-and-forget): neue Schadensmeldung → alle aktiven
Verwaltungsbenutzer, neue Nachricht/Antwort → Gegenstelle (Mieter ↔ Verwaltung,
Auflösung über Wohnungsbezug). E2E verifiziert: Zustellung an axel@hocks.eu
mit Postfix status=sent (02.10., 10:42 Uhr).
## 6. Konventionen & Besonderheiten
- **Dark Mode:** class-Strategie, Präferenz `mp-theme`, Init vor erstem Render.
Feste Farbklassen werden in `index.css` für `.dark` umrechnen
(`.dark .bg-white`/`.bg-slate-50`/`.bg-teal-50`); Sidebar ist eigenes Dunkel-Teal.
- **Barrierefreiheit:** aria-pressed/labels am ThemeToggle und Rollenwechsel, Fokus-Ringe.
- **Formatierung:** Beträge via `fmtEUR` (Intl de-DE), Datumsformate TT.MM.JJJJ.
- **Datenpflege:** Bestandsdaten ändern → `beispieldaten.py` anpassen und neu ausführen,
nicht generated.json von Hand editieren.
- **Sicherheit:** kein offenes Relay (Postfix mynetworks loopback + Docker-Netz,
sender_canonical zentral). Upload/Download-Funktionen sind Demo-Darstellungen.
- **Keine destruktiven Aktionen auf dem Server ohne ausdrückliche Bestätigung.**
## 7. Testing
- `npm run build` (`tsc -b`) ist die echte Typprüfung; `npx tsc --noEmit` prüft wegen
Solution-tsconfig ggf. nichts. Server-Syntax: `node --check server/src/*.js`.
- API-Livetests mit curl (Cookie-Jar): Login → `/api/auth/me` → `/api/bootstrap`
(Mieter-Sicht zählt kleiner!) → Ticket/Thread schreiben → Postfix-Log auf
Zustellung prüfen (`docker logs mp-mail | grep to=<empfaenger>`).
- Headless-Screenshots (Dark Mode = Systempräferenz, `--virtual-time-budget=10000`
wartet auf den Session-Check):
`"/c/Program Files/Google/Chrome/Application/chrome.exe" --headless --disable-gpu --screenshot=... --window-size=WxH "URL"`
- Screenshots ausgeliefert nach `preview/`.
## 8. Verlauf / offene Punkte
**Erledigt 02.10. (v1.0):** Portal komplett neu aufgesetzt (5 Module, Mieter-/Verwaltungsansicht,
KI-Branding: Logo/Hero/Illustration), Docker-Setup (Dockerfile, nginx, compose),
Repo in git.hocks.eu angelegt, Deploy-Workflow + Dark Mode + beispieldaten.py aus dem
Schulportal übernommen, mail-server-Struktur angelegt (noch nicht live verkabelt).
**Erledigt 02.10. (v1.0 live):** Deployment auf hocks.eu durchgeführt: Container `mp-portal`
läuft (`/opt/mieterportal`, compose + gemountetes dist), NPM-Proxy-Host id 7 + LE-Zertifikat
npm-9 eingerichtet (per certbot/webroot, DB-Insert, 7.conf aus 5.conf abgeleitet, nginx reload).
Live geprüft: `https://mieterportal.hocks.eu/` → HTTP 200, Titel korrekt, SPA-Routing
(`/mietkonto`) 200, HTTP→HTTPS-Redirect 301, Zertifikat CN=mieterportal.hocks.eu gültig
bis 31.12.2026. Zertifikatsdateien liegen im npm-Container unter `/etc/letsencrypt/live/npm-9/`
(Backup der Zertifikate auf dem Host: `/root/le-mp/`). Fail2Ban-Ban nach frühen
Fehlversuchen ist nach ~40 min automatisch ausgelaufen.
**Erledigt 02.10. (v1.1 live):** Rebrand auf „Hinterstein Immobilien" (Kunden-Logo,
Joseph-Hinterstein-Portrait auf der Login-Seite, Kontakt-Banner), echtes
Authentifizierungssystem (Login-Pflicht, Gruppen Verwaltung/Mieter, SHA-256,
Session 8 h), Mieter-Registrierung per einmaligem Code, Benutzerverwaltung als
6. Modul (`/benutzer`), Demo-Zugänge mit Passwort `0815`. Mail-Stack auf dem Server
aktiviert (`/opt/mp-mail`, Testmail + API-/send verifiziert). Store-Key auf
`hinterstein-mieterportal-v2` gehoben. Live deployed und geprüft (HTTP 200, Titel
„Hinterstein Immobilien · Mieterportal").
**Erledigt 02.10. (v1.2 live):** E-Mail-Benachrichtigungen an das Portal
angeschlossen: nginx-Proxy `/api/mail/send` mit serverseitig injiziertem Token
(envsubst-Template + `portal.env` auf dem Server), `src/lib/mail.ts` sendet bei
neuen Schadensmeldungen (→ Verwaltung) und Nachrichten/Antworten (→ Gegenstelle).
E2E-Versand live verifiziert (versandt: true), GET auf den Endpunkt 403.
**Erledigt 02.10. (v1.3 live):** Serverseitige Datenbank: PostgreSQL 16 (`mp-db`,
Volume `mp_pgdata`) + Node-API (`mp-api`, Express + pg, eigenes Image via
deploy.sh `--build`). Schema 11 Tabellen (idempotent), Seed einmalig aus
`server/seed/generated.json` + `seed-data.js` (Verwaltungs-E-Mail auf echtes
Postfach axel@hocks.eu gesetzt). Frontend komplett auf API umgestellt:
`src/lib/api.ts`, Store lädt `/api/bootstrap`, kein localStorage mehr (außer
Theme), Ladebildschirm während Session-Check. Rollenrechte serverseitig
durchgesetzt und getestet (Mieterin sieht nur hs-7: 12 Buchungen/2 Tickets,
403 auf fremde Unterhaltung; Verwaltung sieht alles: 552 Buchungen/8 Tickets).
Registrierung, Code-Erzeugung, Ticket inkl. Mail-Benachrichtigung (status=sent
an axel@hocks.eu) E2E verifiziert; Testdaten danach bereinigt. deploy.sh baut
das API-Image auf dem Server; `.env`-Symlink auf portal.env für
Compose-Interpolation beachten (POSTGRES_PASSWORD!).
**Offen:**
- Zertifikatserneuerung prüfen (per 01.12.2026): NPM-Renewer sollte cert 9 über HTTP-Challenge
erneuern; sonst certbot/webroot + Kopie nach npm-9 wiederholen.
- Demo-Empfängeradressen der Mieter (example.de) durch echte ersetzen, sobald bekannt.
- Sicherheit für Produktion: Passwort-Hash serverseitig salten (bcrypt/argon2), CSRF-Token,
Rate-Limiting auf /api/auth/login.
- Mögliche nächste Schritte: Datenbank-Backup-Job (nächtlicher pg_dump), E-Mail-Verifizierung
bei Registrierung, dokumenten-Download mit echten Dateien.
## 9. Sonstiges
- User kommuniziert auf Deutsch; alle UI-Texte und Antworten auf Deutsch.
- Dateien außerhalb des Workspaces immer erst in den Workspace kopieren.
- Vorsicht mit destruktiven Aktionen (Explizite Bestätigung einholen).