Files
Hermes Agent 5d87e4975c Add HttpOnly cookie auth for cross-domain SPA login
Problem: Frontend JS bundle used credentials:'same-origin' which
dropped cookies on cross-domain requests. Login worked (200) but
the subsequent /auth/me check returned 401, leaving the user stuck
on the login screen.

Fix:
- AuthController now sets a dtp_jwt HttpOnly cookie on login/refresh
- Cookie uses Domain=.fahrschultermin.de (shared between frontend
  and api subdomain), Secure, SameSite=Lax, Max-Age=8h
- JwtMiddleware reads JWT from Authorization header OR cookie
- Added AuthController::me() endpoint (was missing, caused 500)
- Logout endpoint clears the cookie
- Frontend index.php patches fetch() to use credentials:'include'
  for all /api/v1/* calls
2026-06-04 20:33:31 +02:00

812 lines
19 KiB
Markdown
Executable File

# fahrschuldesk — Projektplan
## 1. Überblick
**fahrschuldesk** ist eine SaaS-Plattform zur Verwaltung von Fahrschulen. Plattformbetreiber (Admin-Team) können Fahrschulen als Mandanten anlegen und verwalten. Jede Fahrschule erhält eine modulare Oberfläche mit Kundenverwaltung, Kalender, Messenger und optionaler Lernsoftware.
**Status:** Planungsphase (Initialsetup)
---
## 2. Architektur
### 2.1 Domains
| Domain | Zweck | Pfad |
|---|---|---|
| `fahrschuldesk.de` | WebUI (Frontend) | `www/fahrschuldesk.de/public/` |
| `api.fahrschuldesk.de` | REST API (Backend) | `www/api.fahrschuldesk.de/public/` |
### 2.2 Tech-Stack
- **Frontend:** Vue 3 + Vite (SPA)
- **Backend:** PHP (Laravel oder Slim Framework)
- **Datenbank:** PostgreSQL (fahrschuldesk DB, Host: 10.255.30.11:5433)
- **WebSockets:** Für Messenger (Socket.io oder native WebSockets)
- **Dateien:** Lokaler Storage + S3-kompatibel (für Self-Hosted einfach lokaler Ordner)
- **Auth:** JWT-basiert mit Refresh-Token
- **CI/CD:** GitHub Actions / GitLab CI
### 2.3 Kalender-Integration
Der Kalender wird über die fahrschultermin.de API gesteuert (spätere Integration). Die fahrschuldesk-API dient als Schnittstelle.
---
## 3. Hierarchie & Datenmodell
```
Plattform-Admins (intern)
└── Fahrschule (Mandant)
└── Filialen
└── Benutzer
```
### 3.1 Tabellen
#### Plattform-Admins (interne Verwaltung)
```
platform_admins
- id (uuid)
- email (unique)
- password_hash
- name
- role (super_admin | support | sales)
- created_at
- updated_at
- last_login_at
```
#### Fahrschulen (Mandanten)
```
tenants
- id (uuid)
- name
- slug (unique, für Subdomain?)
- email (Rechnungs-Kontakt)
- phone
- address
- status (pending_approval | active | suspended | trial)
- trial_ends_at
- created_at
- updated_at
# Branding
- logo_url
- primary_color
- secondary_color
- custom_css (optional)
# Self-Hosted
- is_self_hosted (bool)
- self_hosted_license_key
```
#### Filialen
```
branches
- id (uuid)
- tenant_id (FK)
- name
- address
- phone
- email
- opening_hours (JSON)
- is_headquarters (bool)
- created_at
- updated_at
```
#### Benutzer
```
users
- id (uuid)
- tenant_id (FK)
- branch_id (FK, nullable)
- email (unique pro Tenant)
- password_hash
- first_name
- last_name
- user_type (admin | office | instructor | lecturer | student)
- status (active | inactive | pending)
- avatar_url
- phone
- created_at
- updated_at
- last_login_at
```
#### Module (pro Fahrschule zuweisbar)
```
modules
- id (uuid)
- key (office | calendar | messenger | learning_blank | learning_premium)
- name
- description
- monthly_price (cent)
- is_active (Plattform-global)
- created_at
tenant_modules
- id (uuid)
- tenant_id (FK)
- module_id (FK)
- status (enabled | disabled)
- enabled_at
- expires_at (nullable)
- trial_ends_at (nullable)
```
#### Pakete (Preismodelle)
```
packages
- id (uuid)
- key (starter | professional | enterprise)
- name
- description
- monthly_price (cent)
- modules_included (JSON array von module_keys)
- max_users
- max_branches
- created_at
tenant_packages
- id (uuid)
- tenant_id (FK)
- package_id (FK)
- started_at
- expires_at
```
#### Lerninhalte (Premium-Modul)
```
learning_contents
- id (uuid)
- tenant_id (FK, nullable — null = Plattform-global)
- category
- title
- description
- content_type (video | document | quiz | exercise)
- content_url
- thumbnail_url
- duration_minutes
- is_premium
- created_at
- updated_at
```
#### Fahrschüler-Fortschritt
```
student_progress
- id (uuid)
- user_id (FK, student)
- content_id (FK)
- status (not_started | in_progress | completed)
- completed_at
- notes (Lehrer-Kommentare)
- created_at
- updated_at
```
#### Audit-Log
```
audit_logs
- id (uuid)
- tenant_id (FK, nullable — null für Plattform-Admin-Aktionen)
- user_id (FK, nullable)
- action (created | updated | deleted | login | logout | etc.)
- entity_type (tenant | user | branch | module | etc.)
- entity_id (UUID des betroffenen Datensatzes)
- old_values (JSON, nullable)
- new_values (JSON, nullable)
- ip_address
- user_agent
- created_at
# Index auf tenant_id + created_at für schnelle Abfragen
# Index auf entity_type + entity_id
```
#### Rechnungen
```
invoices
- id (uuid)
- tenant_id (FK)
- invoice_number (unique)
- amount (cent)
- vat_amount (cent)
- total_amount (cent)
- currency (EUR)
- status (draft | pending | paid | overdue | cancelled)
- billing_period_start
- billing_period_end
- due_date
- paid_at (nullable)
- created_at
- updated_at
invoice_items
- id (uuid)
- invoice_id (FK)
- description
- quantity
- unit_price (cent)
- total_price (cent)
```
#### E-Mail / Notifications
```
notification_templates
- id (uuid)
- key (welcome | password_reset | invoice | trial_expiring | etc.)
- subject
- body_html
- body_text
- variables (JSON)
- created_at
- updated_at
notifications
- id (uuid)
- user_id (FK)
- tenant_id (FK, nullable)
- template_key
- subject
- body
- status (pending | sent | failed)
- sent_at
- created_at
```
#### Dateien / Uploads / DKS (Dokumenten-Klasse-System)
```
files
- id (uuid)
- tenant_id (FK)
- user_id (FK, nullable — wer hochgeladen hat)
- entity_type (vehicle | student | instructor | branch | etc.)
- entity_id (UUID)
- file_type (insurance | tuv | logo | avatar | document | etc.)
- original_filename
- storage_path
- mime_type
- size_bytes
- created_at
# Dateien werden im Storage abgelegt:
# SaaS: /storage/tenants/{tenant_id}/files/
# Self-Hosted: /var/www/fahrschuldesk/storage/files/
```
#### Messenger (DSGVO-konform)
```
conversations
- id (uuid)
- tenant_id (FK)
- type (direct | group)
- name (nullable, nur für groups)
- created_at
- updated_at
conversation_participants
- id (uuid)
- conversation_id (FK)
- user_id (FK)
- joined_at
- last_read_at
messages
- id (uuid)
- conversation_id (FK)
- user_id (FK)
- body (Verschlüsselt auf DB-Ebene)
- is_edited (bool)
- is_deleted (bool)
- created_at
- updated_at
# Keine Ende-zu-Ende Verschlüsselung (Plattform hat Zugriff für Support/Audit)
# DSGVO: Keine externen Dienste, alles auf eigenen Servern
# Nachrichten können auf Anfrage exportiert/gelöscht werden (DSGVO-Compliance)
```
---
## 4. API-Struktur
### 4.1 Auth
```
POST /api/auth/login (JWT Token)
POST /api/auth/logout
POST /api/auth/refresh (Refresh Token)
GET /api/auth/me
POST /api/auth/forgot-password
POST /api/auth/reset-password
```
### 4.2 Registration (Self-Service)
```
POST /api/auth/register (Fahrschule registrieren)
GET /api/auth/register/verify/:token (E-Mail verifizieren)
```
### 4.3 Plattform-Admin (nur Admins)
```
GET /api/admin/tenants
POST /api/admin/tenants
GET /api/admin/tenants/:id
PUT /api/admin/tenants/:id
DELETE /api/admin/tenants/:id
POST /api/admin/tenants/:id/approve (Freigabe nach Registration)
POST /api/admin/tenants/:id/suspend
GET /api/admin/tenants/:id/modules
POST /api/admin/tenants/:id/modules
DELETE /api/admin/tenants/:id/modules/:moduleId
GET /api/admin/platform-modules
GET /api/admin/platform-modules/:id
GET /api/admin/invoices
GET /api/admin/invoices/:id
PUT /api/admin/invoices/:id/mark-paid
GET /api/admin/audit-logs (Plattform-weit)
GET /api/admin/audit-logs/:id
GET /api/admin/platform-admins
POST /api/admin/platform-admins
PUT /api/admin/platform-admins/:id
DELETE /api/admin/platform-admins/:id
```
### 4.4 Tenant-Admin (Fahrschule)
```
GET /api/branches
POST /api/branches
PUT /api/branches/:id
DELETE /api/branches/:id
GET /api/users
POST /api/users
GET /api/users/:id
PUT /api/users/:id
DELETE /api/users/:id
GET /api/my-modules
GET /api/my-profile
PUT /api/my-profile
GET /api/audit-logs (Tenant-spezifisch)
GET /api/audit-logs/:id
# Branding
PUT /api/tenant/branding
GET /api/tenant/branding
# Pakete
GET /api/my-package
PUT /api/tenant/logo
```
### 4.5 Kalender (Integration fahrschultermin.de)
```
GET /api/calendar/events (weiterleiten an fahrschultermin.de API)
POST /api/calendar/events
PUT /api/calendar/events/:id
DELETE /api/calendar/events/:id
GET /api/calendar/availability (Abfrage freier Slots)
```
### 4.6 Messenger
```
GET /api/messenger/conversations
POST /api/messenger/conversations
GET /api/messenger/conversations/:id
POST /api/messenger/conversations/:id/messages
GET /api/messenger/conversations/:id/messages
PUT /api/messenger/messages/:id (editieren)
DELETE /api/messenger/messages/:id (soft delete)
```
### 4.7 Lernsoftware
```
GET /api/learning/contents
GET /api/learning/contents/:id
GET /api/learning/my-progress
POST /api/learning/my-progress
PUT /api/learning/my-progress/:contentId
# Nur für Admin / Dozenten
POST /api/learning/contents (Blanko: eigene Inhalte erstellen)
PUT /api/learning/contents/:id
DELETE /api/learning/contents/:id
```
### 4.8 Dateien / DKS
```
GET /api/files (Liste, gefiltert nach entity)
POST /api/files (Upload)
GET /api/files/:id
DELETE /api/files/:id
GET /api/files/:id/download
```
### 4.9 Rechnungen (Tenant)
```
GET /api/billing/invoices
GET /api/billing/invoices/:id
GET /api/billing/current-package
```
---
## 5. Rollen & Rechte
### Plattform-Admins (intern)
| Rolle | Rechte |
|---|---|
| super_admin | Alles: Tenants, Admins, Module, Settings, Invoices, Audit |
| support | Tenants einsehen/verwalten, keine Admins ändern, keine Settings |
| sales | Tenants anlegen, keine Settings, keine Admin-Verwaltung |
### Fahrschul-Benutzer
| Typ | Beschreibung |
|---|---|
| admin | Voller Zugriff auf eigene Fahrschule |
| office | Büromitarbeiter: Kunden, Kalender, Berichte |
| instructor | Fahrlehrer: Kalender, eigene Fahrschüler, DKS |
| lecturer | Dozent: Kalender, eigene Unterrichtseinheiten |
| student | Fahrschüler: Lernsoftware, eigener Fortschritt, Messenger |
### Rechte-Matrix (pro Modul)
```
user_types:
admin: [office:rw, calendar:rw, messenger:rw, learning:rw, files:rw]
office: [office:rw, calendar:r, messenger:rw, learning:r, files:r]
instructor:[office:r, calendar:rw, messenger:rw, learning:r, files:rw]
lecturer: [calendar:rw, messenger:rw, learning:rw, files:r]
student: [calendar:r, messenger:rw, learning:rw, files:r]
```
---
## 6. Modul-System
### 6.1 Verfügbare Module
| Key | Name | Beschreibung |
|---|---|---|
| `office` | Office | Kundenverwaltung, Fahrzeugverwaltung, Berichte |
| `calendar` | Kalender | Terminplanung (Integration fahrschultermin.de) |
| `messenger` | Messenger | WhatsApp-ähnlich, DSGVO-konform |
| `learning_blank` | Lernsoftware (Blanko) | Eigene Inhalte erstellen |
| `learning_premium` | Lernsoftware (Premium) | Inkl. vorgefertigter Lerninhalte |
### 6.2 Modul-Zuordnung
- Jede Fahrschule kann Module einzeln aktivieren/deaktivieren
- Preise werden pro Modul hinterlegt
- `learning_blank` und `learning_premium` schließen sich gegenseitig aus
---
## 7. Billing & Pakete
### 7.1 Pakete (Beispiel)
| Paket | Preis/Monat | Module | Max Users | Max Filialen |
|---|---|---|---|---|
| Starter | 49€ | Office + Kalender + Messenger | 5 | 1 |
| Professional | 99€ | Alle Module (Blanko) | 20 | 3 |
| Premium | 149€ | Alle Module (Premium) | Unlimited | Unlimited |
### 7.2 Trial
- 14 Tage kostenlos testen
- Danach: Admin-Freigabe nötig
- Trial-End: E-Mail-Erinnerung 3 Tage vorher
### 7.3 Bezahlung
- PayPal (monatlich/jährlich)
- Rechnung (auf Anfrage für Enterprise)
- Automatische Mahnung bei Überfälligkeit
### 7.4 Self-Hosted
- License-Key nötig
- Keine weiteren Mandanten anlegbar (nur Filialen)
- Update-Management via License-Key
---
## 8. Registration & Freigabe
### 8.1 Self-Service Flow
```
1. Fahrschule registriert sich (Name, E-Mail, Password)
2. E-Mail-Verifizierung (Token)
3. Status: "pending_approval"
4. Admin erhält E-Mail: "Neue Registrierung"
5. Admin prüft und genehmigt (oder ablehnen)
6. Status: "active" → Fahrschule kann Login
```
### 8.2 Multi-Admin pro Fahrschule
- Erster Admin = der Registrierer
- Weitere Admin-Accounts können durch den ersten Admin angelegt werden
---
## 9. Branding & Design
### 9.1 Plattform-Defaults
- Logo, Farben, Fonts werden durch Plattform vorgegeben
- Template: Default-Theme für alle neuen Fahrschulen
### 9.2 Tenant-Branding
Jede Fahrschule kann anpassen:
- Logo (Header, E-Mails, Rechnungen)
- Primärfarbe (Buttons, Links, Akzente)
- Sekundärfarbe
- Custom CSS (optional, für fortgeschrittene Nutzer)
### 9.3 E-Mail-Branding
- Rechnungs-E-Mails mit Logo der Fahrschule
- E-Mail-Templates pro Tenant individuell
---
## 10. Audit-Log
### 10.1 Plattform-Admin Audit
- Alle Aktionen aller Tenants einsehbar
- Filterbar nach Tenant, Aktionstyp, Zeitraum
### 10.2 Tenant-Admin Audit
- Nur eigene Fahrschule
- Alle User-Aktionen geloggt
- Erfüllt DSGVO-Anforderungen
### 10.3 Was geloggt wird
- Login / Logout
- CRUD-Operationen auf alle Entitäten
- Änderungen an Benutzern, Modulen, Rechten
---
## 11. Messenger (WhatsApp-like)
### 11.1 Features
- 1:1 Chat
- Gruppen-Chat
- Nachrichten editieren/löschen
- Timestamps, Read-Receipts
- Online-Status
### 11.2 DSGVO
- Alle Daten auf eigenen Servern
- Keine externen Dienste (kein Twilio, keine Cloud-Dienste)
- Export aller Daten möglich (DSGVO-Art. 15)
- Löschen auf Anfrage (DSGVO-Art. 17)
### 11.3 Technisch
- WebSockets für Echtzeit
- Polling-Fallback wenn WebSockets nicht verfügbar
- Messages in DB verschlüsselt (für Backend-Zugriff)
---
## 12. DKS (Dokumenten-Klasse-System)
### 12.1 Dateitypen
- Fahrzeug: Versicherungsunterlagen, TÜV-Berichte, Kennzeichen
- Fahrschüler: Personalausweis, Sehtest, Erste-Hilfe-Nachweis
- Fahrlehrer: Führerschein, Lehrberechtigung
- Filiale: Mietverträge, Gewerbeanmeldung
### 12.2 Features
- Upload mit Drag & Drop
- Dateityp-Kategorisierung
- Ablage nach Entity (Fahrzeug, Student, etc.)
- Download
- Versionierung (neue Datei ersetzt alte, alte bleibt)
- Ablaufdatum-Erinnerungen (z.B. TÜV)
---
## 13. Self-Hosted Option
### 13.1 Anforderungen
- PHP 8.2+
- PostgreSQL 15+
- 4GB RAM minimum
- Ubuntu/Debian/CentOS
### 13.2 Einschränkungen
- **Keine Mandanten** anlegbar (nur die eigene Fahrschule)
- Filialen: Ja (unbegrenzt)
- User: Ja (gemäß Paket)
- Updates: Manuell via License-Key
### 13.3 Installation
- Docker-Compose für einfache Installation
- Oder klassisch via Composer + Migration
---
## 14. Frontend-Struktur (fahrschuldesk.de)
```
src/
├── views/
│ ├── auth/
│ │ ├── Login.vue
│ │ ├── Register.vue
│ │ ├── ForgotPassword.vue
│ │ └── VerifyEmail.vue
│ ├── admin/ (Plattform-Admin)
│ │ ├── Dashboard.vue
│ │ ├── Tenants.vue
│ │ ├── TenantEdit.vue
│ │ ├── Modules.vue
│ │ ├── Invoices.vue
│ │ ├── AuditLog.vue
│ │ └── PlatformAdmins.vue
│ ├── tenant/ (Fahrschule)
│ │ ├── Dashboard.vue
│ │ ├── Users.vue
│ │ ├── Branches.vue
│ │ ├── Office.vue (Kunden, Fahrzeuge)
│ │ ├── Calendar.vue (→ fahrschultermin.de)
│ │ ├── Messenger.vue
│ │ ├── Learning.vue
│ │ ├── Files.vue (DKS)
│ │ ├── Branding.vue
│ │ └── Billing.vue
│ └── student/ (Fahrschüler)
│ ├── Dashboard.vue
│ ├── Learning.vue
│ └── Messenger.vue
├── components/
│ ├── common/
│ │ ├── AppHeader.vue
│ │ ├── AppSidebar.vue
│ │ └── ModuleCard.vue
│ ├── messenger/
│ │ ├── ChatWindow.vue
│ │ ├── ConversationList.vue
│ │ └── MessageBubble.vue
│ ├── files/
│ │ ├── FileUploader.vue
│ │ └── FileList.vue
│ └── branding/
│ └── ColorPicker.vue
├── stores/ (Pinia)
│ ├── auth.js
│ ├── tenant.js
│ ├── modules.js
│ └── messenger.js
├── router/
│ └── index.js
├── api/ (Axios Client)
│ ├── client.js
│ ├── auth.js
│ ├── admin.js
│ └── tenant.js
└── styles/
├── variables.css
└── theme.css
```
---
## 15. CI/CD Pipeline
```yaml
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: PHP Setup
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: pgsql, curl
- name: Run Tests
run: composer test
deploy-api:
needs: test
runs-on: ubuntu-latest
steps:
- name: Sync to Server
uses: appleboy/scp-action@master
with:
host: fahrschuldesk.de
port: 2225
username: fahrschuldesk
key: ${{ secrets.SSH_KEY }}
source: "api/*"
target: "/srv/web/fahrschuldesk/domains/api.fahrschuldesk.de/public/"
deploy-frontend:
needs: test
runs-on: ubuntu-latest
steps:
- name: Build Vue
run: |
npm ci
npm run build
- name: Sync to Server
uses: appleboy/scp-action@master
with:
host: fahrschuldesk.de
port: 2225
username: fahrschuldesk
key: ${{ secrets.SSH_KEY }}
source: "dist/*"
target: "/srv/web/fahrschuldesk/domains/fahrschuldesk.de/public/"
```
---
## 16. Offene Fragen / Next Steps
- [ ] Tech-Stack final abstimmen (Laravel vs. Slim vs. anderes?)
- [ ] PayPal Integration: API Keys besorgen
- [ ] Design-System erstellen (Default-Theme)
- [ ] OpenAPI Spec schreiben
- [ ] Erste Migration: Alle Tabellen
- [ ] Auth-Flow: Login + Registration + E-Mail-Verifizierung
- [ ] Messenger: WebSocket-Integration planen
- [ ] Self-Hosted: Docker-Compose vorbereiten
---
## 17. Phase 1 — Erste Schritte
### Ziel: Grundgerüst stehen
1. **DB-Schema:** Migrationen schreiben für alle Tabellen
2. **API-Skelett:** Auth + Tenant-Registration + Admin-CRUD + Module-CRUD
3. **Frontend-Skelett:** Vue 3 + Vite + Router + Pinia + TailwindCSS
4. **Login-Seite:** Plattform-Admin Login
5. **Registration:** Self-Service + Admin-Freigabe
6. **Admin-Dashboard:** Tenants auflisten, anlegen, Module zuweisen
7. **Branding:** Logo-Upload, Farben wählen
8. **Audit-Log:** Basis-Implementierung
---
## 18. Datei-Ablage (Storage)
### SaaS
```
/srv/web/fahrschuldesk/storage/
├── tenants/{tenant_id}/
│ ├── files/
│ │ ├── vehicles/
│ │ ├── students/
│ │ └── ...
│ ├── avatars/
│ └── logos/
└── backups/
```
### Self-Hosted
```
/var/www/fahrschuldesk/storage/
├── files/
├── avatars/
├── logos/
└── backups/
```