# 🎉 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
