479 lines
11 KiB
Markdown
479 lines
11 KiB
Markdown
# 🎉 Laravel Event-Portal - Vollständige Implementierung
|
|
|
|
Dieses Projekt ist ein vollständig arbeitsfertiges Event-Portal für Dresden mit Integration von externen Veranstaltungsquellen.
|
|
|
|
---
|
|
|
|
## 📋 Projektstruktur
|
|
|
|
```
|
|
Veranstaltungen-APP/
|
|
├── app/
|
|
│ ├── Models/
|
|
│ │ ├── Source.php # Quelle (z.B. Stadt Dresden)
|
|
│ │ ├── Event.php # Veranstaltung
|
|
│ │ └── EventOccurrence.php # Einzelne Termine
|
|
│ ├── Http/Controllers/
|
|
│ │ └── EventController.php # REST API Controller
|
|
│ ├── Jobs/
|
|
│ │ └── ImportEventsJob.php # Queue Job für Import
|
|
│ ├── Commands/
|
|
│ │ └── ImportEventsCommand.php # Artisan Command für manuellen Import
|
|
│ ├── Services/
|
|
│ │ └── EventImportService.php # Business Logic Service
|
|
│
|
|
├── database/
|
|
│ └── migrations/
|
|
│ ├── 2026_04_09_000001_create_sources_table.php
|
|
│ ├── 2026_04_09_000002_create_events_table.php
|
|
│ └── 2026_04_09_000003_create_event_occurrences_table.php
|
|
│
|
|
├── routes/
|
|
│ └── api.php # REST API Routen
|
|
│
|
|
├── docs/
|
|
│ ├── SETUP.md # Diese Datei
|
|
│ ├── EXAMPLE_QUERIES.php # Eloquent Query-Beispiele
|
|
│ ├── API_RESPONSES.md # API Response-Formate
|
|
│ ├── IMPORT_SCRAPER_INTEGRATION.md # Import-Dokumentation
|
|
│ └── KERNEL_SCHEDULER_EXAMPLE.php # Scheduler-Konfiguration
|
|
```
|
|
|
|
---
|
|
|
|
## ⚙️ Installation & Setup
|
|
|
|
### 1. Frisches Laravel-Projekt erstellen
|
|
|
|
```bash
|
|
# Laravel 11 LTS (oder aktuelle LTS)
|
|
composer create-project laravel/laravel Veranstaltungen-APP
|
|
|
|
cd Veranstaltungen-APP
|
|
```
|
|
|
|
### 2. Diese Dateien in das Projekt kopieren
|
|
|
|
```bash
|
|
# Kopiere alle PHP/Migration-Dateien aus diesem Package
|
|
# in die entsprechenden Verzeichnisse
|
|
|
|
# Beispiel:
|
|
cp app/Models/*.php ./app/Models/
|
|
cp app/Http/Controllers/*.php ./app/Http/Controllers/
|
|
cp app/Jobs/*.php ./app/Jobs/
|
|
cp app/Commands/*.php ./app/Commands/
|
|
cp app/Services/*.php ./app/Services/
|
|
cp database/migrations/*.php ./database/migrations/
|
|
cp routes/api.php ./routes/
|
|
```
|
|
|
|
### 3. Umgebungsvariablen konfigurieren
|
|
|
|
```bash
|
|
# .env erstellen
|
|
cp .env.example .env
|
|
|
|
# Schüssel generieren
|
|
php artisan key:generate
|
|
```
|
|
|
|
Bearbeite `.env`:
|
|
```env
|
|
DB_CONNECTION=mysql
|
|
DB_HOST=127.0.0.1
|
|
DB_PORT=3306
|
|
DB_DATABASE=veranstaltungen_app
|
|
DB_USERNAME=root
|
|
DB_PASSWORD=
|
|
|
|
QUEUE_CONNECTION=database
|
|
MAIL_FROM_ADDRESS=noreply@veranstaltungen-app.de
|
|
```
|
|
|
|
### 4. Datenbank & Migrations
|
|
|
|
```bash
|
|
# Datenbank erstellen (MariaDB)
|
|
mysql -u root -p -e "CREATE DATABASE veranstaltungen_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
|
|
|
# Migrations ausführen
|
|
php artisan migrate
|
|
|
|
# Optionale Fixtures/Seeds laden
|
|
php artisan db:seed --class=SourceSeeder
|
|
```
|
|
|
|
### 5. Queue für Imports vorbereiten
|
|
|
|
```bash
|
|
# Queue-Tabelle erstellen
|
|
php artisan queue:table
|
|
php artisan migrate
|
|
|
|
# Queue Worker starten (Development)
|
|
php artisan queue:work --verbose
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 Erste Schritte
|
|
|
|
### Event-Quellen erstellen
|
|
|
|
```bash
|
|
# Interaktiv via Artisan Tinker
|
|
php artisan tinker
|
|
|
|
>>> $source = \App\Models\Source::create([
|
|
... 'name' => 'Stadt Dresden',
|
|
... 'description' => 'Offizielle Veranstaltungen der Stadt',
|
|
... 'url' => 'https://stadt-dresden.de',
|
|
... 'status' => 'active',
|
|
... ]);
|
|
```
|
|
|
|
### Events importieren
|
|
|
|
```bash
|
|
# Manueller Import (blockierend)
|
|
php artisan events:import --sync
|
|
|
|
# Oder asynchron in Queue
|
|
php artisan events:import
|
|
|
|
# Queue Worker muss laufen für Verarbeitung:
|
|
php artisan queue:work
|
|
```
|
|
|
|
### API testen
|
|
|
|
```bash
|
|
# Events auflisten
|
|
curl "http://localhost:8000/api/events?from=2026-04-15&to=2026-05-31&limit=10"
|
|
|
|
# Ein Event anzeigen
|
|
curl "http://localhost:8000/api/events/1"
|
|
|
|
# Verfügbare Kategorien
|
|
curl "http://localhost:8000/api/events/categories/list"
|
|
|
|
# Verfügbare Orte
|
|
curl "http://localhost:8000/api/events/locations/list"
|
|
```
|
|
|
|
---
|
|
|
|
## 📚 Dokumentation
|
|
|
|
### Für Event-Queries siehe:
|
|
👉 [EXAMPLE_QUERIES.php](EXAMPLE_QUERIES.php)
|
|
|
|
### Für API-Endpoints siehe:
|
|
👉 [API_RESPONSES.md](API_RESPONSES.md)
|
|
|
|
### Für Import/Scraper-Integration siehe:
|
|
👉 [IMPORT_SCRAPER_INTEGRATION.md](IMPORT_SCRAPER_INTEGRATION.md)
|
|
|
|
### Für Scheduler-Setup siehe:
|
|
👉 [KERNEL_SCHEDULER_EXAMPLE.php](KERNEL_SCHEDULER_EXAMPLE.php)
|
|
|
|
---
|
|
|
|
## 🔑 API-Endpoints
|
|
|
|
| Methode | Endpoint | Beschreibung |
|
|
|---------|----------|-------------|
|
|
| GET | `/api/events` | Events mit Filtern auflisten |
|
|
| GET | `/api/events/{id}` | Einzelnes Event anzeigen |
|
|
| GET | `/api/events/categories/list` | Verfügbare Kategorien |
|
|
| GET | `/api/events/locations/list` | Verfügbare Orte |
|
|
|
|
### Filter-Parameter
|
|
|
|
```
|
|
GET /api/events
|
|
?from=2026-04-15 # Ab Datum (YYYY-MM-DD), Standard: heute
|
|
&to=2026-05-31 # Bis Datum (YYYY-MM-DD), Standard: +3 Monate
|
|
&category=Kultur # Nach Kategorie filtern
|
|
&location=Dresden # Nach Ort filtern
|
|
&limit=20 # Ergebnisse pro Seite (1-100, Standard: 20)
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 Datenmodell
|
|
|
|
### Events (Veranstaltungen)
|
|
- `id` - Eindeutige ID
|
|
- `source_id` - Referenz zur Quelle
|
|
- `external_id` - ID der Quelle
|
|
- `title` - Name der Veranstaltung
|
|
- `description` - Beschreibung
|
|
- `location` - Ort/Stadt
|
|
- `category` - Kategorie (z.B. Kultur, Sport)
|
|
- `slug` - URL-freundlicher Name
|
|
- `image_url`, `website_url`, `contact_email`, `contact_phone`
|
|
- `status` - draft | published | archived
|
|
- `created_at`, `updated_at`
|
|
|
|
### Event Occurrences (Termine)
|
|
- `id` - Eindeutige ID
|
|
- `event_id` - Referenz zum Event
|
|
- `start_datetime` - Startzeit
|
|
- `end_datetime` - Endzeit
|
|
- `is_all_day` - Ganztägig?
|
|
- `location_details` - Raum/Gebäude
|
|
- `capacity` - Kapazität
|
|
- `available_tickets` - Verfügbare Tickets
|
|
- `price` - Preis (optional)
|
|
- `status` - scheduled | cancelled | completed
|
|
|
|
### Sources (Quellen)
|
|
- `id` - Eindeutige ID
|
|
- `name` - Name der Quelle
|
|
- `description` - Beschreibung
|
|
- `url` - Website URL
|
|
- `status` - active | inactive
|
|
- `last_import_at` - Letzter Import-Zeitpunkt
|
|
|
|
---
|
|
|
|
## 🔄 Import-Workflow
|
|
|
|
```
|
|
1. External Source (z.B. Stadt Dresden API)
|
|
↓
|
|
2. ImportEventsCommand / EventImportService
|
|
↓
|
|
3. ImportEventsJob (läuft in Queue)
|
|
↓
|
|
4. fetchExternalEvents() - Ruft externe Daten ab
|
|
↓
|
|
5. upsertEvent() - Erstellt oder aktualisiert Event+Occurrences
|
|
↓
|
|
6. Database (MySQL/MariaDB)
|
|
↓
|
|
7. API (für Frontend verfügbar)
|
|
```
|
|
|
|
### Upsert-Logik
|
|
- Events werden anhand `[source_id, external_id]` abgeglichen
|
|
- Existierende Events = Update
|
|
- Neue Events = Insert
|
|
- Verhindert Duplikate durch Unique Index
|
|
|
|
---
|
|
|
|
## ⏰ Geplante Imports (Scheduler)
|
|
|
|
In `app/Console/Kernel.php` (siehe Beispiel):
|
|
|
|
- **03:00 Uhr** - Täglich alle Quellen importieren
|
|
- **Stündlich** - Stadt-Dresden-Quelle (häufige Updates)
|
|
- **Alle 6 Stunden** - Andere Quellen
|
|
- **04:00 Uhr** - Markiere abgelaufene Termine
|
|
- **Sonntag 05:00** - Räume archivierte Events auf
|
|
|
|
---
|
|
|
|
## 🛠️ Commands
|
|
|
|
```bash
|
|
# Event-Import
|
|
php artisan events:import [--source=ID|Name] [--sync]
|
|
|
|
# Queue einrichten
|
|
php artisan queue:table
|
|
php artisan migrate
|
|
|
|
# Queue Worker starten (Development)
|
|
php artisan queue:work [--verbose] [--tries=3] [--timeout=120]
|
|
|
|
# Failed Jobs anzeigen
|
|
php artisan queue:failed
|
|
php artisan queue:retry ID
|
|
php artisan queue:forget ID
|
|
|
|
# Alle Jobs leeren
|
|
php artisan queue:flush
|
|
|
|
# Cache leeren
|
|
php artisan cache:clear
|
|
|
|
# Logs leeren
|
|
php artisan log:prune
|
|
|
|
# Datenbank frisch seeden
|
|
php artisan migrate:refresh --seed
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Datenbank-Indizes
|
|
|
|
Die Migrationen erstellen folgende Indizes für Performance:
|
|
|
|
**sources:**
|
|
- `status` (Filter nach aktiv/inaktiv)
|
|
- `created_at` (Sortierer)
|
|
|
|
**events:**
|
|
- `source_id` (Foreign Key)
|
|
- `slug` (Unique, für SEO-URLs)
|
|
- `[location, status]` (Composite Index für Location-Filter)
|
|
- `[category, status]` (Composite Index für Kategorie-Filter)
|
|
- `created_at` (Neueste zuerst)
|
|
- `[source_id, external_id]` (Unique, verhindert Duplikate)
|
|
|
|
**event_occurrences:**
|
|
- `event_id` (Foreign Key)
|
|
- `start_datetime` (Filter nach Datum)
|
|
- `[start_datetime, status]` (Composite Index für "nächste Events")
|
|
- `[event_id, status]` (Filter nach Event & Status)
|
|
|
|
---
|
|
|
|
## 🔐 Security-Best-Practices
|
|
|
|
✅ **Implementiert:**
|
|
- SQL-Injections vermieden (Eloquent ORM)
|
|
- CSRF-Schutz (Laravel Standard)
|
|
- Rate Limiting für APIs
|
|
- Input Validation in Controllers
|
|
- Soft Deletes für Datenintegrität
|
|
|
|
⚠️ **Zu implementieren:**
|
|
- API-Authentifizierung (Laravel Passport/Sanctum)
|
|
- Request Throttling
|
|
- CORS-Konfiguration
|
|
- Content Security Policy
|
|
|
|
---
|
|
|
|
## 🐛 Troubleshooting
|
|
|
|
### Migrations schlagen fehl
|
|
```bash
|
|
# Checke MariaDB Version
|
|
mysql --version
|
|
|
|
# Migrations zurückrollen
|
|
php artisan migrate:reset
|
|
|
|
# Neu starten
|
|
php artisan migrate
|
|
```
|
|
|
|
### Queue-Jobs werden nicht verarbeitet
|
|
```bash
|
|
# Worker-Prozess läuft?
|
|
ps aux | grep "queue:work"
|
|
|
|
# Queue starten (Development)
|
|
php artisan queue:work --verbose
|
|
```
|
|
|
|
### API gibt 404 zurück
|
|
```bash
|
|
# Checke Routes
|
|
php artisan route:list
|
|
|
|
# Starte Server
|
|
php artisan serve
|
|
```
|
|
|
|
### Zu viel Memory-Verbrauch
|
|
```bash
|
|
# Optimize Autoloader
|
|
composer install --optimize-autoloader --no-dev
|
|
|
|
# Disable Query Logging in Production
|
|
# In .env: APP_DEBUG=false
|
|
```
|
|
|
|
---
|
|
|
|
## 📈 Production Deployment
|
|
|
|
### Vorbereitung
|
|
```bash
|
|
# .env für Production
|
|
APP_ENV=production
|
|
APP_DEBUG=false
|
|
QUEUE_CONNECTION=redis # oder beanstalkd
|
|
LOG_CHANNEL=stack
|
|
```
|
|
|
|
### Cron-Job einrichten (Scheduler)
|
|
```bash
|
|
# /etc/cron.d/laravel-scheduler
|
|
* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1
|
|
```
|
|
|
|
Oder mit systemd:
|
|
```bash
|
|
# supervisor für Queue Workers
|
|
[program:laravel-worker]
|
|
process_name=%(program_name)s_%(process_num)02d
|
|
command=php /path/to/app/artisan queue:work redis --sleep=3 --tries=3 --timeout=90
|
|
autostart=true
|
|
autorestart=true
|
|
numprocs=4
|
|
redirect_stderr=true
|
|
stdout_logfile=/path/to/app/storage/logs/worker.log
|
|
```
|
|
|
|
---
|
|
|
|
## 🤝 Weitere Integration
|
|
|
|
### Beispiel: Import aus Stadt-Dresden-API
|
|
|
|
Bearbeite `app/Jobs/ImportEventsJob.php`:
|
|
|
|
```php
|
|
protected function fetchExternalEvents()
|
|
{
|
|
$response = Http::withHeaders([
|
|
'Accept' => 'application/json',
|
|
])->get('https://api.stadt-dresden.de/events', [
|
|
'limit' => 1000,
|
|
]);
|
|
|
|
return $response->json('data');
|
|
}
|
|
```
|
|
|
|
### Beispiel: Web-Scraping
|
|
|
|
```bash
|
|
composer require symfony/dom-crawler symfony/http-client
|
|
```
|
|
|
|
Dann in Import-Service:
|
|
```php
|
|
use Symfony\Component\DomCrawler\Crawler;
|
|
|
|
$response = Http::get('https://example.com/events');
|
|
$crawler = new Crawler($response->body());
|
|
// ... scrape & extract events
|
|
```
|
|
|
|
---
|
|
|
|
## 📞 Support & Weitere Ressourcen
|
|
|
|
- [Laravel Documentation](https://laravel.com/docs)
|
|
- [Laravel Queue Driver Comparison](https://laravel.com/docs/queues)
|
|
- [Laravel Scheduler](https://laravel.com/docs/scheduling)
|
|
- [Symfony DomCrawler](https://symfony.com/doc/current/components/dom_crawler.html)
|
|
|
|
---
|
|
|
|
**Version:** 1.0
|
|
**Laravel:** 11 LTS
|
|
**PHP:** 8.2+
|
|
**Database:** MariaDB 10.4+
|
|
**Erstellt:** 9. April 2026
|