This commit is contained in:
2025-09-23 14:13:24 +02:00
commit 58b5e6b074
103 changed files with 44000 additions and 0 deletions

752
wiki/API-Referenz.md Normal file
View File

@@ -0,0 +1,752 @@
# 📡 API Referenz
Vollständige Dokumentation der REST API des Ninja Cross Parkour Systems.
## 📋 Inhaltsverzeichnis
- [🔐 Authentifizierung](#-authentifizierung)
- [🌐 Public API](#-public-api)
- [🔒 Private API](#-private-api)
- [🖥️ Web API](#-web-api)
- [👑 Admin API](#-admin-api)
- [🏆 Achievements API](#-achievements-api)
- [📊 Datenmodelle](#-datenmodelle)
- [❌ Fehlerbehandlung](#-fehlerbehandlung)
## 🔐 Authentifizierung
### API-Key Authentifizierung
Für private Endpoints wird ein API-Key im Authorization Header benötigt:
```http
Authorization: Bearer YOUR_API_KEY_HERE
```
### Session-basierte Authentifizierung
Für Web-Endpoints wird eine Session-basierte Authentifizierung verwendet.
### Admin-Authentifizierung
Für Admin-Endpoints wird eine erweiterte Authentifizierung mit Admin-Rechten benötigt.
## 🌐 Public API
Öffentliche Endpoints ohne Authentifizierung.
### 🔑 Authentifizierung
#### Login
```http
POST /api/v1/public/login
Content-Type: application/json
{
"username": "admin",
"password": "admin123"
}
```
**Response:**
```json
{
"success": true,
"message": "Login erfolgreich",
"user": {
"id": 1,
"username": "admin",
"is_active": true
}
}
```
#### Logout
```http
POST /api/v1/public/logout
```
**Response:**
```json
{
"success": true,
"message": "Logout erfolgreich"
}
```
### 👥 Spieler Management
#### Spieler erstellen
```http
POST /api/v1/public/players
Content-Type: application/json
{
"firstname": "Max",
"lastname": "Mustermann",
"birthdate": "1990-01-01",
"rfiduid": "AA:BB:CC:DD"
}
```
**Response:**
```json
{
"success": true,
"data": {
"id": "uuid",
"firstname": "Max",
"lastname": "Mustermann",
"birthdate": "1990-01-01",
"rfiduid": "AA:BB:CC:DD",
"created_at": "2024-01-01T00:00:00Z"
}
}
```
#### Spieler verknüpfen
```http
POST /api/v1/public/link-player
Content-Type: application/json
{
"rfiduid": "AA:BB:CC:DD",
"supabase_user_id": "uuid-here"
}
```
#### Spieler per RFID verknüpfen
```http
POST /api/v1/public/link-by-rfid
Content-Type: application/json
{
"rfiduid": "AA:BB:CC:DD",
"supabase_user_id": "uuid-here"
}
```
### 📍 Standorte
#### Alle Standorte abrufen
```http
GET /api/v1/public/locations
```
**Response:**
```json
{
"success": true,
"data": [
{
"id": "uuid",
"name": "Standort 1",
"latitude": 48.1351,
"longitude": 11.5820,
"time_threshold": {
"seconds": 120
},
"created_at": "2024-01-01T00:00:00Z"
}
]
}
```
### ⏱️ Zeiten
#### Alle Zeiten abrufen
```http
GET /api/v1/public/times
```
#### Zeiten mit Details abrufen
```http
GET /api/v1/public/times-with-details
```
#### Beste Zeiten abrufen
```http
GET /api/v1/public/best-times
```
#### Benutzer-Zeiten abrufen
```http
GET /api/v1/public/user-times/{supabase_user_id}
```
#### Benutzer-Spieler abrufen
```http
GET /api/v1/public/user-player/{supabase_user_id}
```
### 📊 Statistiken
#### Seitenaufruf verfolgen
```http
POST /api/v1/public/track-page-view
Content-Type: application/json
{
"page": "/dashboard",
"user_agent": "Mozilla/5.0...",
"ip_address": "192.168.1.1"
}
```
### 🔔 Push Notifications
#### Push-Benachrichtigung abonnieren
```http
POST /api/v1/public/subscribe
Content-Type: application/json
{
"endpoint": "https://fcm.googleapis.com/fcm/send/...",
"keys": {
"p256dh": "key-here",
"auth": "auth-key-here"
}
}
```
#### Push-Benachrichtigung testen
```http
POST /api/v1/public/test-push
Content-Type: application/json
{
"title": "Test",
"body": "Test-Nachricht",
"icon": "/icon.png"
}
```
#### Push-Status abrufen
```http
GET /api/v1/public/push-status
```
## 🔒 Private API
Private Endpoints mit API-Key Authentifizierung.
### 🎫 Token Management
#### Token speichern
```http
POST /api/v1/private/save-token
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"token": "GENERATED_TOKEN",
"description": "Beschreibung",
"standorte": "München, Berlin"
}
```
#### Alle Token abrufen
```http
GET /api/v1/private/tokens
Authorization: Bearer YOUR_API_KEY
```
#### Token validieren
```http
POST /api/v1/private/validate-token
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"token": "TOKEN_TO_VALIDATE"
}
```
### 📍 Standort Management
#### Standort erstellen
```http
POST /api/v1/private/create-location
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"name": "München",
"latitude": 48.1351,
"longitude": 11.5820
}
```
#### Alle Standorte abrufen
```http
GET /api/v1/private/locations
Authorization: Bearer YOUR_API_KEY
```
#### Standort-Schwelle aktualisieren
```http
PUT /api/v1/private/locations/{id}/threshold
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"threshold_seconds": 120
}
```
### 👥 Spieler Management
#### Spieler erstellen
```http
POST /api/v1/private/create-player
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"firstname": "Max",
"lastname": "Mustermann",
"birthdate": "1990-01-01",
"rfiduid": "AA:BB:CC:DD"
}
```
#### Benutzer suchen
```http
POST /api/v1/private/users/find
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"search_term": "Max"
}
```
### ⏱️ Zeit Management
#### Zeit erstellen
```http
POST /api/v1/private/create-time
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"player_id": "RFIDUID",
"location_id": "Name",
"recorded_time": "01:23.456"
}
```
## 🖥️ Web API
Web-API-Endpoints für das Frontend.
### 🔑 API-Key generieren
```http
POST /api/v1/web/generate-api-key
Content-Type: application/json
{
"description": "Mein API Key",
"standorte": "München, Berlin"
}
```
### 📍 Standort erstellen
```http
POST /api/v1/web/create-location
Content-Type: application/json
{
"name": "München",
"latitude": 48.1351,
"longitude": 11.5820
}
```
### 🎫 Token speichern
```http
POST /api/v1/web/save-token
Content-Type: application/json
{
"token": "GENERATED_TOKEN",
"description": "Beschreibung",
"standorte": "München, Berlin"
}
```
### 🔍 Session prüfen
```http
GET /api/v1/web/check-session
```
## 👑 Admin API
Admin-API-Endpoints für Verwaltung.
### 📊 Statistiken
#### Admin-Statistiken
```http
GET /api/v1/admin/stats
Authorization: Bearer ADMIN_TOKEN
```
#### Seiten-Statistiken
```http
GET /api/v1/admin/page-stats
Authorization: Bearer ADMIN_TOKEN
```
### 👥 Spieler Verwaltung
#### Alle Spieler abrufen
```http
GET /api/v1/admin/players
Authorization: Bearer ADMIN_TOKEN
```
#### Spieler erstellen
```http
POST /api/v1/admin/players
Authorization: Bearer ADMIN_TOKEN
Content-Type: application/json
{
"firstname": "Max",
"lastname": "Mustermann",
"birthdate": "1990-01-01",
"rfiduid": "AA:BB:CC:DD"
}
```
#### Spieler aktualisieren
```http
PUT /api/v1/admin/players/{id}
Authorization: Bearer ADMIN_TOKEN
Content-Type: application/json
{
"firstname": "Max",
"lastname": "Mustermann",
"birthdate": "1990-01-01",
"rfiduid": "AA:BB:CC:DD"
}
```
#### Spieler löschen
```http
DELETE /api/v1/admin/players/{id}
Authorization: Bearer ADMIN_TOKEN
```
### 🏃‍♂️ Läufe Verwaltung
#### Alle Läufe abrufen
```http
GET /api/v1/admin/runs
Authorization: Bearer ADMIN_TOKEN
```
#### Lauf abrufen
```http
GET /api/v1/admin/runs/{id}
Authorization: Bearer ADMIN_TOKEN
```
#### Lauf erstellen
```http
POST /api/v1/admin/runs
Authorization: Bearer ADMIN_TOKEN
Content-Type: application/json
{
"player_id": "uuid",
"location_id": "uuid",
"recorded_time": "01:23.456"
}
```
#### Lauf aktualisieren
```http
PUT /api/v1/admin/runs/{id}
Authorization: Bearer ADMIN_TOKEN
Content-Type: application/json
{
"recorded_time": "01:20.000"
}
```
#### Lauf löschen
```http
DELETE /api/v1/admin/runs/{id}
Authorization: Bearer ADMIN_TOKEN
```
### 📍 Standort Verwaltung
#### Alle Standorte abrufen
```http
GET /api/v1/admin/locations
Authorization: Bearer ADMIN_TOKEN
```
#### Standort erstellen
```http
POST /api/v1/admin/locations
Authorization: Bearer ADMIN_TOKEN
Content-Type: application/json
{
"name": "München",
"latitude": 48.1351,
"longitude": 11.5820,
"time_threshold": 120
}
```
#### Standort aktualisieren
```http
PUT /api/v1/admin/locations/{id}
Authorization: Bearer ADMIN_TOKEN
Content-Type: application/json
{
"name": "München Updated",
"latitude": 48.1351,
"longitude": 11.5820,
"time_threshold": 120
}
```
#### Standort löschen
```http
DELETE /api/v1/admin/locations/{id}
Authorization: Bearer ADMIN_TOKEN
```
### 👤 Admin-Benutzer Verwaltung
#### Alle Admin-Benutzer abrufen
```http
GET /api/v1/admin/adminusers
Authorization: Bearer ADMIN_TOKEN
```
#### Admin-Benutzer erstellen
```http
POST /api/v1/admin/adminusers
Authorization: Bearer ADMIN_TOKEN
Content-Type: application/json
{
"username": "newadmin",
"password": "securepassword"
}
```
#### Admin-Benutzer aktualisieren
```http
PUT /api/v1/admin/adminusers/{id}
Authorization: Bearer ADMIN_TOKEN
Content-Type: application/json
{
"username": "updatedadmin",
"is_active": true
}
```
#### Admin-Benutzer löschen
```http
DELETE /api/v1/admin/adminusers/{id}
Authorization: Bearer ADMIN_TOKEN
```
## 🏆 Achievements API
Achievement-System Endpoints.
### 🏆 Achievements abrufen
```http
GET /api/achievements
```
**Response:**
```json
{
"success": true,
"data": [
{
"id": "uuid",
"name": "Erster Lauf",
"description": "Absolviere deinen ersten Lauf",
"category": "consistency",
"condition_type": "runs_count",
"condition_value": 1,
"icon": "🏃‍♂️",
"points": 10,
"is_active": true
}
]
}
```
### 👤 Spieler-Achievements
```http
GET /api/achievements/player/{playerId}
```
### 📊 Spieler-Statistiken
```http
GET /api/achievements/player/{playerId}/stats
```
### ✅ Achievement prüfen
```http
POST /api/achievements/check/{playerId}
Content-Type: application/json
{
"achievement_id": "uuid"
}
```
### 📅 Tägliche Prüfung
```http
POST /api/achievements/daily-check
```
### 🏃‍♂️ Beste Zeit prüfen
```http
POST /api/achievements/best-time-check
```
### 🏅 Leaderboard
```http
GET /api/achievements/leaderboard
```
## 📊 Datenmodelle
### Player
```json
{
"id": "string (uuid)",
"firstname": "string",
"lastname": "string",
"birthdate": "string (date)",
"rfiduid": "string (XX:XX:XX:XX)",
"supabase_user_id": "string (uuid)",
"created_at": "string (date-time)"
}
```
### Time
```json
{
"id": "string (uuid)",
"player_id": "string (uuid)",
"location_id": "string (uuid)",
"recorded_time": {
"seconds": "number",
"minutes": "number",
"milliseconds": "number"
},
"created_at": "string (date-time)"
}
```
### Location
```json
{
"id": "string (uuid)",
"name": "string",
"latitude": "number (float)",
"longitude": "number (float)",
"time_threshold": {
"seconds": "number",
"minutes": "number"
},
"created_at": "string (date-time)"
}
```
### Achievement
```json
{
"id": "string (uuid)",
"name": "string",
"description": "string",
"category": "string (consistency|improvement|seasonal|monthly)",
"condition_type": "string",
"condition_value": "integer",
"icon": "string (emoji)",
"points": "integer",
"is_active": "boolean"
}
```
### PlayerAchievement
```json
{
"id": "string (uuid)",
"player_id": "string (uuid)",
"achievement_id": "string (uuid)",
"progress": "integer",
"is_completed": "boolean",
"earned_at": "string (date-time)"
}
```
## ❌ Fehlerbehandlung
### Standard-Fehlerantwort
```json
{
"success": false,
"message": "Fehlermeldung",
"error": "DETAILED_ERROR_INFO"
}
```
### HTTP-Status-Codes
- **200 OK** - Erfolgreiche Anfrage
- **201 Created** - Ressource erfolgreich erstellt
- **400 Bad Request** - Ungültige Anfrage
- **401 Unauthorized** - Nicht authentifiziert
- **403 Forbidden** - Keine Berechtigung
- **404 Not Found** - Ressource nicht gefunden
- **500 Internal Server Error** - Serverfehler
### Häufige Fehlermeldungen
- `"API-Key erforderlich"` - Fehlender oder ungültiger API-Key
- `"Ungültige Anmeldedaten"` - Falsche Login-Daten
- `"Ressource nicht gefunden"` - Angeforderte Ressource existiert nicht
- `"Ungültige Daten"` - Validierungsfehler bei Eingabedaten
- `"Keine Berechtigung"` - Unzureichende Rechte für die Aktion
## 🔧 Entwicklung
### Lokale Entwicklung
```bash
# Server starten
npm run dev
# API-Dokumentation anzeigen
# Swagger UI verfügbar unter: http://localhost:3000/api-docs
```
### Produktionsumgebung
```bash
# Server starten
npm start
# API-Dokumentation: https://ninja.reptilfpv.de/api-docs
```
---
**Version:** 1.0.0
**Base URL:** `https://ninja.reptilfpv.de/api`
**Autor:** Carsten Graf

479
wiki/Achievement-System.md Normal file
View File

@@ -0,0 +1,479 @@
# 🏆 Achievement System
Umfassende Dokumentation des Achievement-Systems für das Ninja Cross Parkour System.
## 📊 System-Übersicht
Das Achievement-System besteht aus:
- **32 verschiedene Achievements** in 4 Kategorien
- **Automatische tägliche Vergabe** am Ende des Tages
- **REST API Endpoints** für Frontend-Integration
- **PostgreSQL Funktionen** für effiziente Verarbeitung
## 🎯 Achievement-Kategorien
### 1. Konsistenz-basierte Achievements
- **Erste Schritte** 👶 - Erste Zeit aufgezeichnet (5 Punkte)
- **Durchhalter** 💪 - 3 Versuche an einem Tag (10 Punkte)
- **Fleißig** 🔥 - 5 Versuche an einem Tag (15 Punkte)
- **Besessen** 😤 - 10 Versuche an einem Tag (25 Punkte)
- **Regelmäßig** 📅 - 5 verschiedene Tage gespielt (20 Punkte)
- **Stammgast** ⭐ - 10 verschiedene Tage gespielt (30 Punkte)
- **Treue** 💎 - 20 verschiedene Tage gespielt (50 Punkte)
- **Veteran** 🏆 - 50 verschiedene Tage gespielt (100 Punkte)
### 2. Verbesserungs-basierte Achievements
- **Fortschritt** 📈 - Persönliche Bestzeit um 5 Sekunden verbessert (15 Punkte)
- **Durchbruch** ⚡ - Persönliche Bestzeit um 10 Sekunden verbessert (25 Punkte)
- **Transformation** 🔄 - Persönliche Bestzeit um 15 Sekunden verbessert (40 Punkte)
- **Perfektionist** ✨ - Persönliche Bestzeit um 20 Sekunden verbessert (60 Punkte)
### 3. Saisonale Achievements
- **Wochenend-Krieger** 🏁 - Am Wochenende gespielt (10 Punkte)
- **Nachmittags-Sportler** ☀️ - Zwischen 14-18 Uhr gespielt (10 Punkte)
- **Frühaufsteher** 🌅 - Vor 10 Uhr gespielt (15 Punkte)
- **Abend-Sportler** 🌙 - Nach 18 Uhr gespielt (10 Punkte)
### 4. Monatliche Achievements
- **Januar-Krieger** ❄️ bis **Dezember-Dynamo** 🎄 (je 20 Punkte)
### 5. Jahreszeiten-Achievements
- **Frühjahrs-Fighter** 🌱 - Im Frühling gespielt (30 Punkte)
- **Sommer-Sportler** ☀️ - Im Sommer gespielt (30 Punkte)
- **Herbst-Held** 🍂 - Im Herbst gespielt (30 Punkte)
- **Winter-Warrior** ❄️ - Im Winter gespielt (30 Punkte)
## 🗄️ Datenbank-Schema
### Tabelle: `achievements`
```sql
- id (uuid, PK)
- name (varchar) - Achievement-Name
- description (text) - Beschreibung
- category (varchar) - Kategorie
- condition_type (varchar) - Bedingungstyp
- condition_value (integer) - Bedingungswert
- icon (varchar) - Emoji-Icon
- points (integer) - Punkte
- is_active (boolean) - Aktiv
- created_at (timestamp)
```
### Tabelle: `player_achievements`
```sql
- id (uuid, PK)
- player_id (uuid, FK) - Verweis auf players.id
- achievement_id (uuid, FK) - Verweis auf achievements.id
- earned_at (timestamp) - Wann erreicht
- progress (integer) - Fortschritt
- is_completed (boolean) - Abgeschlossen
- created_at (timestamp)
```
## 🔧 PostgreSQL Funktionen
### `check_consistency_achievements(player_uuid)`
Überprüft alle Konsistenz-basierten Achievements für einen Spieler.
**Logik:**
- Zählt Gesamtläufe des Spielers
- Zählt Läufe pro Tag
- Zählt verschiedene Spieltage
- Vergibt entsprechende Achievements
### `check_improvement_achievements(player_uuid)`
Überprüft alle Verbesserungs-basierten Achievements für einen Spieler.
**Logik:**
- Ermittelt persönliche Bestzeit
- Berechnet Verbesserung seit erster Zeit
- Vergibt entsprechende Achievements
### `check_seasonal_achievements(player_uuid)`
Überprüft alle saisonalen und monatlichen Achievements für einen Spieler.
**Logik:**
- Prüft Wochentag (Wochenende)
- Prüft Tageszeit (morgens, nachmittags, abends)
- Prüft Monat (Januar bis Dezember)
- Prüft Jahreszeit (Frühling, Sommer, Herbst, Winter)
### `check_all_achievements(player_uuid)`
Führt alle Achievement-Überprüfungen für einen Spieler aus.
## 🚀 API Endpoints
### GET `/api/achievements`
Alle verfügbaren Achievements abrufen.
**Response:**
```json
{
"success": true,
"data": [
{
"id": "uuid",
"name": "Erste Schritte",
"description": "Absolviere deinen ersten Lauf",
"category": "consistency",
"condition_type": "runs_count",
"condition_value": 1,
"icon": "👶",
"points": 5,
"is_active": true
}
]
}
```
### GET `/api/achievements/player/:playerId`
Achievements eines bestimmten Spielers abrufen.
**Response:**
```json
{
"success": true,
"data": [
{
"id": "uuid",
"achievement_id": "uuid",
"name": "Erste Schritte",
"description": "Absolviere deinen ersten Lauf",
"icon": "👶",
"points": 5,
"progress": 1,
"is_completed": true,
"earned_at": "2024-01-01T00:00:00Z"
}
]
}
```
### GET `/api/achievements/player/:playerId/stats`
Achievement-Statistiken eines Spielers abrufen.
**Response:**
```json
{
"success": true,
"data": {
"total_achievements": 32,
"completed_achievements": 5,
"total_points": 150,
"earned_points": 75,
"completion_percentage": 15.6
}
}
```
### POST `/api/achievements/check/:playerId`
Achievements für einen Spieler manuell überprüfen.
**Request:**
```json
{
"achievement_id": "uuid"
}
```
### POST `/api/achievements/daily-check`
Tägliche Achievement-Überprüfung für alle Spieler ausführen.
**Response:**
```json
{
"success": true,
"message": "Daily achievement check completed",
"players_checked": 150,
"achievements_awarded": 25
}
```
### GET `/api/achievements/leaderboard?limit=10`
Bestenliste der Spieler nach Achievement-Punkten.
**Response:**
```json
{
"success": true,
"data": [
{
"player_id": "uuid",
"firstname": "Max",
"lastname": "Mustermann",
"total_points": 500,
"completed_achievements": 15,
"rank": 1
}
]
}
```
## 📅 Automatisierung
### Tägliches Script
```bash
# Manuell ausführen
node scripts/daily_achievements.js
# Cron-Job einrichten
node scripts/setup_cron.js setup
# Cron-Job Status prüfen
node scripts/setup_cron.js status
# Cron-Job entfernen
node scripts/setup_cron.js remove
```
### Cron-Schedule
- **Zeit**: Täglich um 23:59 Uhr
- **Log**: `/var/log/ninjaserver_achievements.log`
### Script-Details
```javascript
// scripts/daily_achievements.js
const { checkAllAchievements } = require('../models/Achievement');
async function dailyCheck() {
try {
// Alle Spieler abrufen
const players = await getAllPlayers();
let totalAwarded = 0;
for (const player of players) {
const awarded = await checkAllAchievements(player.id);
totalAwarded += awarded;
}
console.log(`Daily check completed: ${totalAwarded} achievements awarded`);
} catch (error) {
console.error('Daily check failed:', error);
}
}
```
## 🎮 Frontend-Integration
### Beispiel: Achievement-Liste laden
```javascript
async function loadAchievements(playerId) {
try {
const response = await fetch(`/api/achievements/player/${playerId}`);
const data = await response.json();
if (data.success) {
data.data.forEach(achievement => {
const status = achievement.is_completed ? '✅' : '❌';
console.log(`${achievement.icon} ${achievement.name}: ${status}`);
});
}
} catch (error) {
console.error('Error loading achievements:', error);
}
}
```
### Beispiel: Statistiken anzeigen
```javascript
async function loadStats(playerId) {
try {
const response = await fetch(`/api/achievements/player/${playerId}/stats`);
const data = await response.json();
if (data.success) {
const stats = data.data;
document.getElementById('total-points').textContent = stats.total_points;
document.getElementById('completed').textContent =
`${stats.completed_achievements}/${stats.total_achievements}`;
document.getElementById('percentage').textContent =
`${stats.completion_percentage}%`;
}
} catch (error) {
console.error('Error loading stats:', error);
}
}
```
### Beispiel: Achievement-Animation
```javascript
function showAchievementNotification(achievement) {
const notification = document.createElement('div');
notification.className = 'achievement-notification';
notification.innerHTML = `
<div class="achievement-icon">${achievement.icon}</div>
<div class="achievement-text">
<h3>${achievement.name}</h3>
<p>${achievement.description}</p>
<span class="points">+${achievement.points} Punkte</span>
</div>
`;
document.body.appendChild(notification);
// Animation
setTimeout(() => {
notification.classList.add('show');
}, 100);
// Entfernen nach 5 Sekunden
setTimeout(() => {
notification.remove();
}, 5000);
}
```
## 🔍 Monitoring
### Logs überwachen
```bash
# Live-Logs anzeigen
tail -f /var/log/ninjaserver_achievements.log
# Letzte Ausführung prüfen
grep "Daily achievement check completed" /var/log/ninjaserver_achievements.log | tail -1
# Fehler-Logs anzeigen
grep "ERROR" /var/log/ninjaserver_achievements.log
```
### Datenbank-Status prüfen
```sql
-- Achievement-Statistiken
SELECT
COUNT(*) as total_achievements,
COUNT(CASE WHEN is_active = true THEN 1 END) as active_achievements
FROM achievements;
-- Spieler-Statistiken
SELECT
COUNT(DISTINCT player_id) as players_with_achievements,
COUNT(*) as total_earned_achievements
FROM player_achievements
WHERE is_completed = true;
-- Top-Spieler
SELECT
p.firstname,
p.lastname,
COUNT(pa.id) as achievements,
SUM(a.points) as total_points
FROM players p
JOIN player_achievements pa ON p.id = pa.player_id
JOIN achievements a ON pa.achievement_id = a.id
WHERE pa.is_completed = true
GROUP BY p.id, p.firstname, p.lastname
ORDER BY total_points DESC
LIMIT 10;
```
## 🛠️ Wartung
### Neue Achievements hinzufügen
1. Achievement in `achievements` Tabelle einfügen:
```sql
INSERT INTO achievements (name, description, category, condition_type, condition_value, icon, points)
VALUES ('Neues Achievement', 'Beschreibung', 'consistency', 'runs_count', 5, '🏆', 25);
```
2. Logik in entsprechenden PostgreSQL Funktionen erweitern
3. API Endpoints testen
### Achievement deaktivieren
```sql
UPDATE achievements SET is_active = false WHERE name = 'Achievement-Name';
```
### Daten zurücksetzen
```sql
-- Alle Spieler-Achievements löschen
DELETE FROM player_achievements;
-- Achievement-Statistiken zurücksetzen
UPDATE achievements SET created_at = NOW();
```
### Achievement-Import/Export
```bash
# Export
pg_dump -t achievements -t player_achievements ninjaserver > achievements_backup.sql
# Import
psql ninjaserver < achievements_backup.sql
```
## 📈 Performance
### Indizierung
```sql
-- Performance-Indizes
CREATE INDEX CONCURRENTLY idx_player_achievements_player_id
ON player_achievements(player_id);
CREATE INDEX CONCURRENTLY idx_player_achievements_achievement_id
ON player_achievements(achievement_id);
CREATE INDEX CONCURRENTLY idx_player_achievements_completed
ON player_achievements(is_completed) WHERE is_completed = true;
```
### Batch-Processing
- **Effiziente Verarbeitung** aller Spieler in einem Durchgang
- **Transaktionale Sicherheit** für Datenkonsistenz
- **Fehlerbehandlung** für einzelne Spieler
### Caching
- **Achievement-Definitionen** werden gecacht
- **Spieler-Statistiken** werden bei Änderungen neu berechnet
- **Leaderboard** wird periodisch aktualisiert
### Zeitzone-Behandlung
- **Korrekte Zeitzone** (Europe/Berlin) für alle Zeitberechnungen
- **Saisonale Achievements** berücksichtigen lokale Zeit
- **Tägliche Prüfung** erfolgt zur richtigen Zeit
## 🔒 Sicherheit
### API-Schutz
- **Alle Endpoints** über bestehende Authentifizierung
- **Admin-Endpoints** erfordern erweiterte Berechtigung
- **Rate Limiting** für häufige Anfragen
### SQL-Injection
- **Parametrisierte Queries** in allen Funktionen
- **Input-Validierung** vor Datenbankzugriff
- **Escape-Funktionen** für dynamische Inhalte
### Datenvalidierung
- **Eingabe-Validierung** in allen API-Endpoints
- **Typ-Überprüfung** für alle Parameter
- **Bereichs-Validierung** für numerische Werte
### Fehlerbehandlung
- **Umfassende Error-Handling** in allen Funktionen
- **Logging** aller Fehler und Warnungen
- **Graceful Degradation** bei Systemfehlern
## 🎯 Best Practices
### Achievement-Design
- **Klare Bedingungen** für alle Achievements
- **Angemessene Punkte** basierend auf Schwierigkeit
- **Motivierende Beschreibungen** für Spieler
### Performance-Optimierung
- **Batch-Processing** für große Datenmengen
- **Indizierung** für häufige Abfragen
- **Caching** für statische Daten
### Wartbarkeit
- **Modulare Struktur** für einfache Erweiterungen
- **Dokumentation** aller Funktionen
- **Tests** für kritische Komponenten
---
**Erstellt am**: $(date)
**Version**: 1.0.0
**Autor**: Ninja Cross Parkour System

261
wiki/Benutzerhandbuch.md Normal file
View File

@@ -0,0 +1,261 @@
# 📖 Benutzerhandbuch
Anleitung für Endbenutzer des Ninja Cross Parkour Systems.
## 🎯 Übersicht
Das Ninja Cross Parkour System ermöglicht es Schwimmbadbesuchern, ihre Parkour-Zeiten zu messen, zu verfolgen und sich mit anderen zu vergleichen.
## 🚀 Erste Schritte
### 1. Registrierung
1. Öffnen Sie das Web-Interface
2. Klicken Sie auf "Registrieren"
3. Füllen Sie das Formular aus:
- Vorname
- Nachname
- Geburtsdatum
- RFID-Karten-ID (falls vorhanden)
### 2. RFID-Karte verknüpfen
Falls Sie eine RFID-Karte haben:
1. Melden Sie sich an
2. Gehen Sie zu "Mein Profil"
3. Klicken Sie auf "RFID-Karte verknüpfen"
4. Halten Sie Ihre Karte an den Reader
### 3. Erste Zeit messen
1. Wählen Sie einen Standort aus
2. Halten Sie Ihre RFID-Karte an den Start-Reader
3. Laufen Sie den Parkour
4. Halten Sie Ihre Karte an den Ziel-Reader
5. Ihre Zeit wird automatisch aufgezeichnet
## 🏠 Dashboard
### Übersicht
Das Dashboard zeigt:
- **Aktuelle Zeit** - Ihre letzte gemessene Zeit
- **Beste Zeit** - Ihr persönlicher Rekord
- **Achievements** - Ihre Erfolge
- **Statistiken** - Fortschritt und Trends
### Navigation
- **🏠 Home** - Dashboard und Übersicht
- **⏱️ Zeiten** - Alle Ihre gemessenen Zeiten
- **🏆 Achievements** - Erfolge und Fortschritt
- **📊 Statistiken** - Detaillierte Analysen
- **👤 Profil** - Persönliche Einstellungen
## ⏱️ Zeitmessung
### Wie funktioniert es?
1. **Start:** RFID-Karte an Start-Reader halten
2. **Parkour:** Den Parcours absolvieren
3. **Ziel:** RFID-Karte an Ziel-Reader halten
4. **Ergebnis:** Zeit wird automatisch berechnet und gespeichert
### Zeitformat
Zeiten werden im Format `MM:SS.mmm` angezeigt:
- **Minuten:SSekunden.Millisekunden**
- Beispiel: `01:23.456` = 1 Minute, 23 Sekunden, 456 Millisekunden
### Gültige Zeiten
- **Minimum:** 30 Sekunden
- **Maximum:** 10 Minuten
- **Schwelle:** Konfigurierbar pro Standort
## 🏆 Achievement System
### Was sind Achievements?
Achievements sind Erfolge, die Sie durch verschiedene Aktivitäten freischalten können.
### Kategorien
#### 🎯 Konsistenz-basierte Achievements
- **Erste Schritte** 👶 - Erste Zeit aufgezeichnet (5 Punkte)
- **Durchhalter** 💪 - 3 Versuche an einem Tag (10 Punkte)
- **Fleißig** 🔥 - 5 Versuche an einem Tag (15 Punkte)
- **Besessen** 😤 - 10 Versuche an einem Tag (25 Punkte)
- **Regelmäßig** 📅 - 5 verschiedene Tage gespielt (20 Punkte)
- **Stammgast** ⭐ - 10 verschiedene Tage gespielt (30 Punkte)
- **Treue** 💎 - 20 verschiedene Tage gespielt (50 Punkte)
- **Veteran** 🏆 - 50 verschiedene Tage gespielt (100 Punkte)
#### 📈 Verbesserungs-basierte Achievements
- **Fortschritt** 📈 - Persönliche Bestzeit um 5 Sekunden verbessert (15 Punkte)
- **Durchbruch** ⚡ - Persönliche Bestzeit um 10 Sekunden verbessert (25 Punkte)
- **Transformation** 🔄 - Persönliche Bestzeit um 15 Sekunden verbessert (40 Punkte)
- **Perfektionist** ✨ - Persönliche Bestzeit um 20 Sekunden verbessert (60 Punkte)
#### 🌍 Saisonale Achievements
- **Wochenend-Krieger** 🏁 - Am Wochenende gespielt (10 Punkte)
- **Nachmittags-Sportler** ☀️ - Zwischen 14-18 Uhr gespielt (10 Punkte)
- **Frühaufsteher** 🌅 - Vor 10 Uhr gespielt (15 Punkte)
- **Abend-Sportler** 🌙 - Nach 18 Uhr gespielt (10 Punkte)
#### 📅 Monatliche Achievements
- **Januar-Krieger** ❄️ bis **Dezember-Dynamo** 🎄 (je 20 Punkte)
#### 🌸 Jahreszeiten-Achievements
- **Frühjahrs-Fighter** 🌱 - Im Frühling gespielt (30 Punkte)
- **Sommer-Sportler** ☀️ - Im Sommer gespielt (30 Punkte)
- **Herbst-Held** 🍂 - Im Herbst gespielt (30 Punkte)
- **Winter-Warrior** ❄️ - Im Winter gespielt (30 Punkte)
### Achievement-Status
- **✅ Abgeschlossen** - Achievement erreicht
- **🔄 In Bearbeitung** - Fortschritt wird gemacht
- **❌ Nicht freigeschaltet** - Noch nicht begonnen
## 📊 Statistiken
### Persönliche Statistiken
- **Gesamtzeiten** - Anzahl aller gemessenen Zeiten
- **Beste Zeit** - Schnellste gemessene Zeit
- **Durchschnittszeit** - Durchschnittliche Zeit
- **Verbesserung** - Zeitverbesserung seit dem ersten Lauf
- **Aktivitätstage** - Anzahl der Tage mit Aktivität
### Fortschritts-Tracking
- **Wöchentlicher Fortschritt** - Zeiten der letzten 7 Tage
- **Monatlicher Fortschritt** - Zeiten des aktuellen Monats
- **Jährlicher Fortschritt** - Zeiten des aktuellen Jahres
### Vergleiche
- **Persönliche Bestenliste** - Ihre eigenen Top-Zeiten
- **Standort-Vergleich** - Zeiten an verschiedenen Standorten
- **Zeitverlauf** - Entwicklung Ihrer Zeiten über die Zeit
## 🗺️ Standorte
### Verfügbare Standorte
Das System unterstützt mehrere Standorte:
- **Hauptstandort** - Hauptparkour
- **Training** - Übungsbereich
- **Wettkampf** - Wettkampfbereich
### Standort-Informationen
Jeder Standort zeigt:
- **Name** - Standortbezeichnung
- **Schwelle** - Mindestzeit für gültige Zeiten
- **Beste Zeit** - Rekordzeit an diesem Standort
- **Karte** - Geografische Position
## 🔔 Benachrichtigungen
### Push-Benachrichtigungen
Aktivieren Sie Push-Benachrichtigungen für:
- **Neue Rekorde** - Persönliche Bestzeiten
- **Achievements** - Neue Erfolge
- **System-Updates** - Wichtige Ankündigungen
### E-Mail-Benachrichtigungen
Konfigurieren Sie E-Mail-Benachrichtigungen für:
- **Wöchentliche Zusammenfassung** - Ihre Aktivitäten
- **Monatliche Statistiken** - Detaillierte Berichte
- **System-Updates** - Wichtige Änderungen
## 👤 Profil verwalten
### Persönliche Daten
- **Name** - Vor- und Nachname
- **Geburtsdatum** - Für Alterskategorien
- **E-Mail** - Für Benachrichtigungen
- **RFID-Karte** - Verknüpfte Karten
### Einstellungen
- **Zeitzone** - Für korrekte Zeitstempel
- **Sprache** - Interface-Sprache
- **Benachrichtigungen** - Push und E-Mail Einstellungen
- **Datenschutz** - Sichtbarkeit Ihrer Daten
### Datenschutz
- **Öffentliche Profile** - Sichtbar für andere Benutzer
- **Private Profile** - Nur für Sie sichtbar
- **Datenexport** - Ihre Daten herunterladen
- **Konto löschen** - Alle Daten entfernen
## 🏅 Bestenlisten
### Globale Bestenlisten
- **Schnellste Zeiten** - Alle Zeiten aller Benutzer
- **Meiste Achievements** - Benutzer mit den meisten Erfolgen
- **Aktivste Spieler** - Benutzer mit den meisten Läufen
### Kategorien
- **Gesamt** - Alle Altersgruppen
- **Jugend** - Unter 18 Jahren
- **Erwachsene** - 18-65 Jahre
- **Senioren** - Über 65 Jahre
### Zeiträume
- **Heute** - Beste Zeiten des Tages
- **Diese Woche** - Beste Zeiten der Woche
- **Dieser Monat** - Beste Zeiten des Monats
- **Dieses Jahr** - Beste Zeiten des Jahres
- **Alle Zeiten** - Historische Bestenliste
## 🔧 Troubleshooting
### Häufige Probleme
#### RFID-Karte wird nicht erkannt
1. Karte richtig positionieren
2. Reader auf Verschmutzung prüfen
3. Karte auf Beschädigungen prüfen
4. Administrator kontaktieren
#### Zeit wird nicht gespeichert
1. Gültige Zeit prüfen (innerhalb der Schwelle)
2. Standort korrekt ausgewählt
3. Internetverbindung prüfen
4. Seite neu laden
#### Achievements werden nicht vergeben
1. Tägliche Prüfung abwarten
2. Bedingungen erfüllt prüfen
3. System-Status prüfen
4. Administrator kontaktieren
### Support kontaktieren
- **E-Mail** - support@ninjaparkour.de
- **Telefon** - +49 (0) 123 456 789
- **Chat** - Verfügbar im Web-Interface
## 📱 Mobile Nutzung
### Responsive Design
Das System ist für alle Geräte optimiert:
- **Desktop** - Vollständige Funktionalität
- **Tablet** - Touch-optimierte Bedienung
- **Smartphone** - Kompakte Ansicht
### Mobile App (geplant)
- **Native App** - Für iOS und Android
- **Offline-Modus** - Zeiten ohne Internet
- **Push-Benachrichtigungen** - Sofortige Updates
## 🎓 Tipps und Tricks
### Bessere Zeiten erzielen
1. **Regelmäßig trainieren** - Konsistenz ist wichtig
2. **Technik verbessern** - Effiziente Bewegungen
3. **Kondition aufbauen** - Ausdauer trainieren
4. **Mental vorbereiten** - Konzentration und Fokus
### Achievements sammeln
1. **Verschiedene Zeiten** - Morgens, mittags, abends
2. **Wochenenden** - Zusätzliche Aktivität
3. **Konsistent bleiben** - Regelmäßige Teilnahme
4. **Verbesserungen** - Persönliche Bestzeiten brechen
### System optimal nutzen
1. **Profil vollständig** - Alle Daten ausfüllen
2. **Benachrichtigungen aktivieren** - Updates erhalten
3. **Statistiken verfolgen** - Fortschritt beobachten
4. **Community nutzen** - Mit anderen vergleichen
---
**Hinweis:** Bei technischen Problemen wenden Sie sich an den Systemadministrator oder konsultieren Sie die [Troubleshooting](Troubleshooting)-Seite.

588
wiki/Datenbank.md Normal file
View File

@@ -0,0 +1,588 @@
# 🗄️ Datenbank
Dokumentation der PostgreSQL-Datenbank des Ninja Cross Parkour Systems.
## 📋 Inhaltsverzeichnis
- [🏗️ Schema-Übersicht](#-schema-übersicht)
- [📊 Tabellen](#-tabellen)
- [🔗 Beziehungen](#-beziehungen)
- [📈 Indizes](#-indizes)
- [🔧 Funktionen](#-funktionen)
- [📊 Statistiken](#-statistiken)
- [🛠️ Wartung](#-wartung)
## 🏗️ Schema-Übersicht
### Datenbank-Name
`ninjaserver`
### Zeichensatz
`UTF-8`
### Zeitzone
`Europe/Berlin`
### Version
PostgreSQL 12 oder höher
## 📊 Tabellen
### `players` - Spieler
```sql
CREATE TABLE players (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
firstname VARCHAR(50) NOT NULL,
lastname VARCHAR(50) NOT NULL,
birthdate DATE NOT NULL,
rfiduid VARCHAR(20) UNIQUE,
supabase_user_id UUID,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
**Beschreibung:** Speichert alle Spieler-Informationen.
**Felder:**
- `id` - Eindeutige UUID
- `firstname` - Vorname (max. 50 Zeichen)
- `lastname` - Nachname (max. 50 Zeichen)
- `birthdate` - Geburtsdatum
- `rfiduid` - RFID-Karten-ID (eindeutig)
- `supabase_user_id` - Verknüpfung zu Supabase
- `created_at` - Erstellungszeitpunkt
- `updated_at` - Letzte Aktualisierung
### `locations` - Standorte
```sql
CREATE TABLE locations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) UNIQUE NOT NULL,
latitude DECIMAL(10, 8) NOT NULL,
longitude DECIMAL(11, 8) NOT NULL,
time_threshold JSONB DEFAULT '{"seconds": 120}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
**Beschreibung:** Speichert alle Parkour-Standorte.
**Felder:**
- `id` - Eindeutige UUID
- `name` - Standortname (eindeutig)
- `latitude` - Breitengrad (10,8 Dezimalstellen)
- `longitude` - Längengrad (11,8 Dezimalstellen)
- `time_threshold` - Zeit-Schwelle als JSON
- `created_at` - Erstellungszeitpunkt
- `updated_at` - Letzte Aktualisierung
### `times` - Zeiten
```sql
CREATE TABLE times (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
player_id UUID REFERENCES players(id) ON DELETE CASCADE,
location_id UUID REFERENCES locations(id) ON DELETE CASCADE,
recorded_time JSONB NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
**Beschreibung:** Speichert alle gemessenen Zeiten.
**Felder:**
- `id` - Eindeutige UUID
- `player_id` - Verweis auf Spieler
- `location_id` - Verweis auf Standort
- `recorded_time` - Zeit als JSON (Sekunden, Minuten, Millisekunden)
- `created_at` - Erstellungszeitpunkt
### `achievements` - Achievements
```sql
CREATE TABLE achievements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(50) NOT NULL,
condition_type VARCHAR(50) NOT NULL,
condition_value INTEGER NOT NULL,
icon VARCHAR(10),
points INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
**Beschreibung:** Definiert alle verfügbaren Achievements.
**Felder:**
- `id` - Eindeutige UUID
- `name` - Achievement-Name
- `description` - Beschreibung
- `category` - Kategorie (consistency, improvement, seasonal, monthly)
- `condition_type` - Bedingungstyp
- `condition_value` - Bedingungswert
- `icon` - Emoji-Icon
- `points` - Punkte
- `is_active` - Aktiv-Status
- `created_at` - Erstellungszeitpunkt
- `updated_at` - Letzte Aktualisierung
### `player_achievements` - Spieler-Achievements
```sql
CREATE TABLE player_achievements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
player_id UUID REFERENCES players(id) ON DELETE CASCADE,
achievement_id UUID REFERENCES achievements(id) ON DELETE CASCADE,
earned_at TIMESTAMP,
progress INTEGER DEFAULT 0,
is_completed BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(player_id, achievement_id)
);
```
**Beschreibung:** Verknüpft Spieler mit ihren Achievements.
**Felder:**
- `id` - Eindeutige UUID
- `player_id` - Verweis auf Spieler
- `achievement_id` - Verweis auf Achievement
- `earned_at` - Zeitpunkt der Verleihung
- `progress` - Fortschritt (0-100)
- `is_completed` - Abgeschlossen-Status
- `created_at` - Erstellungszeitpunkt
### `adminusers` - Admin-Benutzer
```sql
CREATE TABLE adminusers (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP
);
```
**Beschreibung:** Speichert Admin-Benutzer für das System.
**Felder:**
- `id` - Auto-increment ID
- `username` - Benutzername (eindeutig)
- `password_hash` - Gehashtes Passwort
- `is_active` - Aktiv-Status
- `created_at` - Erstellungszeitpunkt
- `last_login` - Letzter Login
### `api_tokens` - API-Tokens
```sql
CREATE TABLE api_tokens (
id SERIAL PRIMARY KEY,
token VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
standorte TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP,
is_active BOOLEAN DEFAULT true
);
```
**Beschreibung:** Speichert API-Tokens für Authentifizierung.
**Felder:**
- `id` - Auto-increment ID
- `token` - API-Token (eindeutig)
- `description` - Beschreibung
- `standorte` - Zugewiesene Standorte
- `created_at` - Erstellungszeitpunkt
- `expires_at` - Ablaufzeitpunkt
- `is_active` - Aktiv-Status
### `page_views` - Seitenaufrufe
```sql
CREATE TABLE page_views (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
page VARCHAR(255) NOT NULL,
user_agent TEXT,
ip_address INET,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
**Beschreibung:** Verfolgt Seitenaufrufe für Statistiken.
**Felder:**
- `id` - Eindeutige UUID
- `page` - Seitenname
- `user_agent` - Browser-Informationen
- `ip_address` - IP-Adresse
- `created_at` - Zeitpunkt des Aufrufs
## 🔗 Beziehungen
### Foreign Key Constraints
```sql
-- times -> players
ALTER TABLE times
ADD CONSTRAINT fk_times_player
FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE;
-- times -> locations
ALTER TABLE times
ADD CONSTRAINT fk_times_location
FOREIGN KEY (location_id) REFERENCES locations(id) ON DELETE CASCADE;
-- player_achievements -> players
ALTER TABLE player_achievements
ADD CONSTRAINT fk_player_achievements_player
FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE;
-- player_achievements -> achievements
ALTER TABLE player_achievements
ADD CONSTRAINT fk_player_achievements_achievement
FOREIGN KEY (achievement_id) REFERENCES achievements(id) ON DELETE CASCADE;
```
### Beziehungsdiagramm
```
players (1) -----> (N) times
players (1) -----> (N) player_achievements
locations (1) ---> (N) times
achievements (1) -> (N) player_achievements
```
## 📈 Indizes
### Primäre Indizes
```sql
-- Primärschlüssel (automatisch)
CREATE UNIQUE INDEX idx_players_pkey ON players(id);
CREATE UNIQUE INDEX idx_locations_pkey ON locations(id);
CREATE UNIQUE INDEX idx_times_pkey ON times(id);
CREATE UNIQUE INDEX idx_achievements_pkey ON achievements(id);
CREATE UNIQUE INDEX idx_player_achievements_pkey ON player_achievements(id);
```
### Performance-Indizes
```sql
-- Zeiten-Indizes
CREATE INDEX idx_times_player_id ON times(player_id);
CREATE INDEX idx_times_location_id ON times(location_id);
CREATE INDEX idx_times_created_at ON times(created_at);
CREATE INDEX idx_times_player_created ON times(player_id, created_at DESC);
-- Achievement-Indizes
CREATE INDEX idx_player_achievements_player_id ON player_achievements(player_id);
CREATE INDEX idx_player_achievements_achievement_id ON player_achievements(achievement_id);
CREATE INDEX idx_player_achievements_completed ON player_achievements(is_completed) WHERE is_completed = true;
-- Standort-Indizes
CREATE INDEX idx_locations_name ON locations(name);
CREATE INDEX idx_locations_coordinates ON locations(latitude, longitude);
-- Spieler-Indizes
CREATE INDEX idx_players_rfiduid ON players(rfiduid);
CREATE INDEX idx_players_supabase_user_id ON players(supabase_user_id);
CREATE INDEX idx_players_name ON players(firstname, lastname);
-- API-Token-Indizes
CREATE INDEX idx_api_tokens_token ON api_tokens(token);
CREATE INDEX idx_api_tokens_active ON api_tokens(is_active) WHERE is_active = true;
-- Seitenaufruf-Indizes
CREATE INDEX idx_page_views_page ON page_views(page);
CREATE INDEX idx_page_views_created_at ON page_views(created_at);
```
### Composite-Indizes
```sql
-- Für häufige Abfragen
CREATE INDEX idx_times_player_location_time ON times(player_id, location_id, created_at DESC);
CREATE INDEX idx_player_achievements_player_completed ON player_achievements(player_id, is_completed);
CREATE INDEX idx_achievements_category_active ON achievements(category, is_active);
```
## 🔧 Funktionen
### Achievement-Funktionen
```sql
-- Konsistenz-basierte Achievements prüfen
CREATE OR REPLACE FUNCTION check_consistency_achievements(player_uuid UUID)
RETURNS INTEGER AS $$
DECLARE
awarded_count INTEGER := 0;
total_runs INTEGER;
runs_today INTEGER;
unique_days INTEGER;
BEGIN
-- Gesamtläufe zählen
SELECT COUNT(*) INTO total_runs
FROM times WHERE player_id = player_uuid;
-- Läufe heute zählen
SELECT COUNT(*) INTO runs_today
FROM times
WHERE player_id = player_uuid
AND DATE(created_at) = CURRENT_DATE;
-- Verschiedene Tage zählen
SELECT COUNT(DISTINCT DATE(created_at)) INTO unique_days
FROM times WHERE player_id = player_uuid;
-- Achievements vergeben
-- (Detaillierte Logik hier...)
RETURN awarded_count;
END;
$$ LANGUAGE plpgsql;
-- Verbesserungs-basierte Achievements prüfen
CREATE OR REPLACE FUNCTION check_improvement_achievements(player_uuid UUID)
RETURNS INTEGER AS $$
-- (Implementierung...)
$$ LANGUAGE plpgsql;
-- Saisonale Achievements prüfen
CREATE OR REPLACE FUNCTION check_seasonal_achievements(player_uuid UUID)
RETURNS INTEGER AS $$
-- (Implementierung...)
$$ LANGUAGE plpgsql;
-- Alle Achievements prüfen
CREATE OR REPLACE FUNCTION check_all_achievements(player_uuid UUID)
RETURNS INTEGER AS $$
DECLARE
total_awarded INTEGER := 0;
BEGIN
total_awarded := total_awarded + check_consistency_achievements(player_uuid);
total_awarded := total_awarded + check_improvement_achievements(player_uuid);
total_awarded := total_awarded + check_seasonal_achievements(player_uuid);
RETURN total_awarded;
END;
$$ LANGUAGE plpgsql;
```
### Utility-Funktionen
```sql
-- Beste Zeit eines Spielers ermitteln
CREATE OR REPLACE FUNCTION get_best_time(player_uuid UUID)
RETURNS JSONB AS $$
DECLARE
best_time JSONB;
BEGIN
SELECT recorded_time INTO best_time
FROM times
WHERE player_id = player_uuid
ORDER BY (recorded_time->>'seconds')::INTEGER ASC
LIMIT 1;
RETURN COALESCE(best_time, '{"seconds": 0, "minutes": 0, "milliseconds": 0}');
END;
$$ LANGUAGE plpgsql;
-- Spieler-Statistiken berechnen
CREATE OR REPLACE FUNCTION get_player_stats(player_uuid UUID)
RETURNS JSONB AS $$
DECLARE
stats JSONB;
total_runs INTEGER;
best_time JSONB;
avg_time JSONB;
BEGIN
SELECT COUNT(*) INTO total_runs FROM times WHERE player_id = player_uuid;
SELECT get_best_time(player_uuid) INTO best_time;
-- Durchschnittszeit berechnen
SELECT jsonb_build_object(
'seconds', AVG((recorded_time->>'seconds')::INTEGER),
'minutes', AVG((recorded_time->>'minutes')::INTEGER),
'milliseconds', AVG((recorded_time->>'milliseconds')::INTEGER)
) INTO avg_time
FROM times WHERE player_id = player_uuid;
stats := jsonb_build_object(
'total_runs', total_runs,
'best_time', best_time,
'average_time', avg_time
);
RETURN stats;
END;
$$ LANGUAGE plpgsql;
```
## 📊 Statistiken
### Datenbank-Größe
```sql
-- Gesamtgröße der Datenbank
SELECT pg_size_pretty(pg_database_size('ninjaserver')) as database_size;
-- Größe der einzelnen Tabellen
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
```
### Tabellen-Statistiken
```sql
-- Anzahl der Datensätze pro Tabelle
SELECT
'players' as table_name, COUNT(*) as record_count FROM players
UNION ALL
SELECT 'locations', COUNT(*) FROM locations
UNION ALL
SELECT 'times', COUNT(*) FROM times
UNION ALL
SELECT 'achievements', COUNT(*) FROM achievements
UNION ALL
SELECT 'player_achievements', COUNT(*) FROM player_achievements
UNION ALL
SELECT 'adminusers', COUNT(*) FROM adminusers
UNION ALL
SELECT 'api_tokens', COUNT(*) FROM api_tokens
UNION ALL
SELECT 'page_views', COUNT(*) FROM page_views;
```
### Performance-Statistiken
```sql
-- Langsamste Queries
SELECT
query,
calls,
total_time,
mean_time,
rows
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;
-- Index-Nutzung
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
ORDER BY idx_scan DESC;
```
## 🛠️ Wartung
### Backup
```bash
# Vollständiges Backup
pg_dump -h localhost -U username -d ninjaserver > ninjaserver_backup.sql
# Nur Schema
pg_dump -h localhost -U username -d ninjaserver --schema-only > schema_backup.sql
# Nur Daten
pg_dump -h localhost -U username -d ninjaserver --data-only > data_backup.sql
# Komprimiertes Backup
pg_dump -h localhost -U username -d ninjaserver | gzip > ninjaserver_backup.sql.gz
```
### Wiederherstellung
```bash
# Vollständige Wiederherstellung
psql -h localhost -U username -d ninjaserver < ninjaserver_backup.sql
# Schema wiederherstellen
psql -h localhost -U username -d ninjaserver < schema_backup.sql
# Daten wiederherstellen
psql -h localhost -U username -d ninjaserver < data_backup.sql
```
### Wartungsaufgaben
```sql
-- Tabellen analysieren
ANALYZE;
-- Indizes neu aufbauen
REINDEX DATABASE ninjaserver;
-- Vakuum durchführen
VACUUM ANALYZE;
-- Speicher freigeben
VACUUM FULL;
```
### Monitoring
```sql
-- Aktive Verbindungen
SELECT
pid,
usename,
application_name,
client_addr,
state,
query_start,
query
FROM pg_stat_activity
WHERE state = 'active';
-- Locks
SELECT
pid,
mode,
locktype,
relation::regclass,
granted
FROM pg_locks
WHERE NOT granted;
-- Wartende Queries
SELECT
pid,
usename,
application_name,
state,
query_start,
query
FROM pg_stat_activity
WHERE state = 'waiting';
```
### Sicherheit
```sql
-- Benutzerrechte prüfen
SELECT
usename,
usesuper,
usecreatedb,
usebypassrls
FROM pg_user;
-- Tabellenrechte prüfen
SELECT
schemaname,
tablename,
tableowner
FROM pg_tables
WHERE schemaname = 'public';
-- Verbindungslimits
SELECT
usename,
connlimit
FROM pg_user;
```
---
**Hinweis:** Für detaillierte API-Dokumentation siehe [API Referenz](API-Referenz) und für Achievement-Details siehe [Achievement System](Achievement-System).

642
wiki/Deployment.md Normal file
View File

@@ -0,0 +1,642 @@
# 🚀 Deployment
Anleitung für das Deployment des Ninja Cross Parkour Systems in verschiedenen Umgebungen.
## 📋 Inhaltsverzeichnis
- [🏗️ Deployment-Übersicht](#-deployment-übersicht)
- [🔧 Vorbereitung](#-vorbereitung)
- [🐳 Docker-Deployment](#-docker-deployment)
- [☁️ Cloud-Deployment](#-cloud-deployment)
- [🖥️ VPS-Deployment](#-vps-deployment)
- [🔧 Konfiguration](#-konfiguration)
- [📊 Monitoring](#-monitoring)
- [🔄 CI/CD](#-cicd)
## 🏗️ Deployment-Übersicht
### Deployment-Optionen
- **Docker** - Containerisierte Bereitstellung
- **Cloud** - AWS, Azure, Google Cloud
- **VPS** - Virtuelle private Server
- **On-Premise** - Lokale Server
### System-Anforderungen
- **CPU:** 2+ Kerne
- **RAM:** 4+ GB
- **Storage:** 50+ GB SSD
- **Network:** 100+ Mbps
## 🔧 Vorbereitung
### Code vorbereiten
```bash
# Repository klonen
git clone <repository-url>
cd ninjaserver
# Abhängigkeiten installieren
npm install
# Produktions-Build erstellen
npm run build
# Tests ausführen
npm test
```
### Umgebungsvariablen
```bash
# .env.production erstellen
cp .env.example .env.production
# Produktionswerte setzen
NODE_ENV=production
PORT=3000
DB_HOST=production-db-host
DB_PORT=5432
DB_NAME=ninjaserver
DB_USER=ninja_user
DB_PASSWORD=secure_password
JWT_SECRET=your_jwt_secret_here
SESSION_SECRET=your_session_secret_here
```
### Datenbank vorbereiten
```sql
-- Produktionsdatenbank erstellen
CREATE DATABASE ninjaserver;
CREATE USER ninja_user WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE ninjaserver TO ninja_user;
-- Schema initialisieren
\c ninjaserver
\i scripts/init-db.sql
```
## 🐳 Docker-Deployment
### Dockerfile
```dockerfile
# Multi-stage build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine AS runtime
# Sicherheitsupdates
RUN apk update && apk upgrade
# Nicht-root Benutzer erstellen
RUN addgroup -g 1001 -S nodejs
RUN adduser -S ninja -u 1001
WORKDIR /app
# Abhängigkeiten kopieren
COPY --from=builder /app/node_modules ./node_modules
COPY . .
# Berechtigungen setzen
RUN chown -R ninja:nodejs /app
USER ninja
# Port freigeben
EXPOSE 3000
# Health Check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Anwendung starten
CMD ["npm", "start"]
```
### Docker Compose
```yaml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=ninjaserver
- DB_USER=ninja_user
- DB_PASSWORD=secure_password
depends_on:
- postgres
- redis
volumes:
- ./logs:/app/logs
restart: unless-stopped
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_DB=ninjaserver
- POSTGRES_USER=ninja_user
- POSTGRES_PASSWORD=secure_password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
ports:
- "5432:5432"
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: unless-stopped
volumes:
postgres_data:
redis_data:
```
### Deployment-Skript
```bash
#!/bin/bash
# deploy.sh
set -e
echo "🚀 Starting deployment..."
# Docker Images bauen
echo "📦 Building Docker images..."
docker-compose build
# Alte Container stoppen
echo "🛑 Stopping old containers..."
docker-compose down
# Neue Container starten
echo "▶️ Starting new containers..."
docker-compose up -d
# Health Check
echo "🔍 Checking health..."
sleep 30
curl -f http://localhost:3000/health || exit 1
echo "✅ Deployment completed successfully!"
```
## ☁️ Cloud-Deployment
### AWS Deployment
#### EC2-Instanz
```bash
# EC2-Instanz starten
aws ec2 run-instances \
--image-id ami-0c02fb55956c7d316 \
--instance-type t3.medium \
--key-name ninja-key \
--security-groups ninja-sg \
--user-data file://user-data.sh
```
#### RDS-Datenbank
```bash
# RDS-Instanz erstellen
aws rds create-db-instance \
--db-instance-identifier ninja-db \
--db-instance-class db.t3.micro \
--engine postgres \
--master-username ninja_user \
--master-user-password secure_password \
--allocated-storage 20
```
#### Load Balancer
```bash
# Application Load Balancer erstellen
aws elbv2 create-load-balancer \
--name ninja-alb \
--subnets subnet-12345 subnet-67890 \
--security-groups sg-12345
```
### Azure Deployment
#### App Service
```bash
# App Service erstellen
az webapp create \
--resource-group ninja-rg \
--plan ninja-plan \
--name ninja-app \
--runtime "NODE|18-lts"
```
#### PostgreSQL
```bash
# PostgreSQL-Server erstellen
az postgres flexible-server create \
--resource-group ninja-rg \
--name ninja-db \
--admin-user ninja_user \
--admin-password secure_password \
--sku-name Standard_B1ms
```
### Google Cloud Deployment
#### Cloud Run
```yaml
# cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/ninja-app', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/ninja-app']
- name: 'gcr.io/cloud-builders/gcloud'
args: ['run', 'deploy', 'ninja-app', '--image', 'gcr.io/$PROJECT_ID/ninja-app', '--region', 'europe-west1']
```
## 🖥️ VPS-Deployment
### Server-Setup
```bash
#!/bin/bash
# server-setup.sh
# System aktualisieren
sudo apt update && sudo apt upgrade -y
# Node.js installieren
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# PostgreSQL installieren
sudo apt install postgresql postgresql-contrib -y
# Nginx installieren
sudo apt install nginx -y
# PM2 installieren
sudo npm install -g pm2
# Firewall konfigurieren
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
```
### Anwendung deployen
```bash
#!/bin/bash
# deploy-vps.sh
# Code aktualisieren
git pull origin main
# Abhängigkeiten installieren
npm install --production
# Datenbank migrieren
npm run migrate
# Anwendung starten
pm2 start ecosystem.config.js
# Nginx konfigurieren
sudo cp nginx.conf /etc/nginx/sites-available/ninja
sudo ln -s /etc/nginx/sites-available/ninja /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
### PM2-Konfiguration
```javascript
// ecosystem.config.js
module.exports = {
apps: [{
name: 'ninja-app',
script: 'server.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true
}]
};
```
## 🔧 Konfiguration
### Nginx-Konfiguration
```nginx
# nginx.conf
upstream ninja_app {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name ninja.reptilfpv.de;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name ninja.reptilfpv.de;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# Security Headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Rate Limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
location / {
proxy_pass http://ninja_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
# Static Files
location /static/ {
alias /var/www/ninja/public/;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
```
### SSL-Zertifikat
```bash
# Let's Encrypt
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d ninja.reptilfpv.de
# Automatische Erneuerung
echo "0 12 * * * /usr/bin/certbot renew --quiet" | sudo crontab -
```
### Datenbank-Backup
```bash
#!/bin/bash
# backup.sh
# Tägliches Backup
pg_dump -h localhost -U ninja_user -d ninjaserver | gzip > backup_$(date +%Y%m%d).sql.gz
# Alte Backups löschen (älter als 30 Tage)
find /backups -name "backup_*.sql.gz" -mtime +30 -delete
# Backup nach S3 hochladen
aws s3 cp backup_$(date +%Y%m%d).sql.gz s3://ninja-backups/
```
## 📊 Monitoring
### Application Monitoring
```javascript
// monitoring.js
const prometheus = require('prom-client');
// Metriken definieren
const httpRequestDuration = new prometheus.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code']
});
const activeConnections = new prometheus.Gauge({
name: 'active_connections',
help: 'Number of active connections'
});
// Metriken-Endpoint
app.get('/metrics', (req, res) => {
res.set('Content-Type', prometheus.register.contentType);
res.end(prometheus.register.metrics());
});
```
### Log-Monitoring
```bash
# Logstash-Konfiguration
input {
file {
path => "/var/log/ninja/*.log"
type => "ninja-logs"
}
}
filter {
if [type] == "ninja-logs" {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:message}" }
}
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "ninja-logs-%{+YYYY.MM.dd}"
}
}
```
### Alerting
```yaml
# alertmanager.yml
global:
smtp_smarthost: 'localhost:587'
smtp_from: 'alerts@ninjaparkour.de'
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'web.hook'
receivers:
- name: 'web.hook'
email_configs:
- to: 'admin@ninjaparkour.de'
subject: 'Ninja Parkour Alert: {{ .GroupLabels.alertname }}'
body: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
```
## 🔄 CI/CD
### GitHub Actions
```yaml
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: |
ssh user@server 'cd /var/www/ninja && git pull && npm install && pm2 restart ninja-app'
```
### GitLab CI
```yaml
# .gitlab-ci.yml
stages:
- test
- deploy
test:
stage: test
script:
- npm ci
- npm test
deploy:
stage: deploy
script:
- ssh user@server 'cd /var/www/ninja && git pull && npm install && pm2 restart ninja-app'
only:
- main
```
### Jenkins Pipeline
```groovy
// Jenkinsfile
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'npm ci'
sh 'npm test'
}
}
stage('Deploy') {
steps {
sh 'ssh user@server "cd /var/www/ninja && git pull && npm install && pm2 restart ninja-app"'
}
}
}
}
```
## 🔧 Wartung
### Automatische Updates
```bash
#!/bin/bash
# auto-update.sh
# System-Updates
sudo apt update && sudo apt upgrade -y
# Anwendung-Updates
cd /var/www/ninja
git pull origin main
npm install --production
pm2 restart ninja-app
# Datenbank-Updates
npm run migrate
```
### Health Checks
```bash
#!/bin/bash
# health-check.sh
# Anwendung prüfen
curl -f http://localhost:3000/health || exit 1
# Datenbank prüfen
psql -d ninjaserver -c "SELECT NOW();" || exit 1
# Speicher prüfen
if [ $(df / | awk 'NR==2{print $5}' | sed 's/%//') -gt 80 ]; then
echo "Disk space low"
exit 1
fi
```
### Rollback-Strategie
```bash
#!/bin/bash
# rollback.sh
# Vorherige Version wiederherstellen
cd /var/www/ninja
git checkout HEAD~1
npm install --production
pm2 restart ninja-app
# Datenbank-Rollback
psql -d ninjaserver < backup_previous.sql
```
---
**Hinweis:** Diese Deployment-Anleitung sollte an Ihre spezifischen Anforderungen angepasst werden. Testen Sie alle Schritte in einer Staging-Umgebung vor der Produktionsbereitstellung.

589
wiki/Entwicklerhandbuch.md Normal file
View File

@@ -0,0 +1,589 @@
# 🔧 Entwicklerhandbuch
Technische Dokumentation für Entwickler des Ninja Cross Parkour Systems.
## 📋 Inhaltsverzeichnis
- [🏗️ System-Architektur](#-system-architektur)
- [🛠️ Entwicklungsumgebung](#-entwicklungsumgebung)
- [📡 API-Integration](#-api-integration)
- [🗄️ Datenbank-Schema](#-datenbank-schema)
- [🔐 Authentifizierung](#-authentifizierung)
- [🧪 Testing](#-testing)
- [🚀 Deployment](#-deployment)
- [📊 Monitoring](#-monitoring)
- [🔧 Wartung](#-wartung)
## 🏗️ System-Architektur
### Übersicht
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Backend │ │ Database │
│ (Web UI) │◄──►│ (Node.js) │◄──►│ (PostgreSQL) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ RFID Reader │ │ API Endpoints │ │ Achievement │
│ (Hardware) │ │ (REST) │ │ System │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### Technologie-Stack
- **Backend:** Node.js + Express.js
- **Datenbank:** PostgreSQL
- **Frontend:** HTML5 + CSS3 + JavaScript
- **Authentifizierung:** JWT + bcrypt
- **API:** RESTful API
- **Maps:** Leaflet.js + OpenStreetMap
- **RFID:** Hardware-Integration
### Projektstruktur
```
ninjaserver/
├── server.js # Hauptserver-Datei
├── routes/
│ ├── api.js # API-Routen
│ ├── public.js # Öffentliche Routen
│ ├── private.js # Private Routen
│ ├── web.js # Web-Routen
│ └── admin.js # Admin-Routen
├── middleware/
│ ├── auth.js # Authentifizierung
│ ├── validation.js # Eingabe-Validierung
│ └── logging.js # Logging
├── models/
│ ├── Player.js # Spieler-Modell
│ ├── Time.js # Zeit-Modell
│ ├── Location.js # Standort-Modell
│ └── Achievement.js # Achievement-Modell
├── scripts/
│ ├── init-db.js # Datenbankinitialisierung
│ ├── create-user.js # Benutzer-Erstellung
│ └── daily_achievements.js # Tägliche Achievements
├── public/
│ ├── index.html # Hauptanwendung
│ ├── login.html # Login-Seite
│ ├── css/ # Stylesheets
│ └── js/ # JavaScript
├── test/
│ ├── api.test.js # API-Tests
│ ├── unit.test.js # Unit-Tests
│ └── integration.test.js # Integration-Tests
└── docs/
├── API.md # API-Dokumentation
├── ACHIEVEMENTS.md # Achievement-Dokumentation
└── wiki/ # Wiki-Dokumentation
```
## 🛠️ Entwicklungsumgebung
### Voraussetzungen
- **Node.js** v16 oder höher
- **PostgreSQL** 12 oder höher
- **Git** für Versionskontrolle
- **npm** oder **yarn** für Paketverwaltung
### Setup
```bash
# Repository klonen
git clone <repository-url>
cd ninjaserver
# Abhängigkeiten installieren
npm install
# Umgebungsvariablen konfigurieren
cp .env.example .env
# .env-Datei bearbeiten
# Datenbank initialisieren
npm run init-db
# Entwicklungsserver starten
npm run dev
```
### Entwicklungsskripte
```bash
# Entwicklungsserver mit Auto-Reload
npm run dev
# Tests ausführen
npm test
# Linting
npm run lint
# Datenbank zurücksetzen
npm run reset-db
# API-Dokumentation generieren
npm run docs
```
### IDE-Empfehlungen
- **Visual Studio Code** mit Extensions:
- ES6 code snippets
- PostgreSQL
- REST Client
- GitLens
## 📡 API-Integration
### Authentifizierung
```javascript
// API-Key Authentifizierung
const headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
};
// Session-basierte Authentifizierung
const session = await authenticateUser(username, password);
```
### API-Client Beispiel
```javascript
class NinjaParkourAPI {
constructor(apiKey, baseURL = 'http://localhost:3000') {
this.apiKey = apiKey;
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
...options.headers
},
...options
};
const response = await fetch(url, config);
return response.json();
}
// Spieler erstellen
async createPlayer(playerData) {
return this.request('/api/v1/public/players', {
method: 'POST',
body: JSON.stringify(playerData)
});
}
// Zeit messen
async recordTime(timeData) {
return this.request('/api/v1/private/create-time', {
method: 'POST',
body: JSON.stringify(timeData)
});
}
// Achievements abrufen
async getAchievements(playerId) {
return this.request(`/api/achievements/player/${playerId}`);
}
}
// Verwendung
const api = new NinjaParkourAPI('your-api-key');
const player = await api.createPlayer({
firstname: 'Max',
lastname: 'Mustermann',
birthdate: '1990-01-01',
rfiduid: 'AA:BB:CC:DD'
});
```
### WebSocket Integration
```javascript
// Real-time Updates
const socket = io('http://localhost:3000');
socket.on('timeRecorded', (data) => {
console.log('Neue Zeit:', data);
updateLeaderboard(data);
});
socket.on('achievementEarned', (data) => {
console.log('Neues Achievement:', data);
showNotification(data);
});
```
## 🗄️ Datenbank-Schema
### Tabellen-Übersicht
```sql
-- Spieler
CREATE TABLE players (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
firstname VARCHAR(50) NOT NULL,
lastname VARCHAR(50) NOT NULL,
birthdate DATE NOT NULL,
rfiduid VARCHAR(20) UNIQUE,
supabase_user_id UUID,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Standorte
CREATE TABLE locations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) UNIQUE NOT NULL,
latitude DECIMAL(10, 8) NOT NULL,
longitude DECIMAL(11, 8) NOT NULL,
time_threshold JSONB DEFAULT '{"seconds": 120}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Zeiten
CREATE TABLE times (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
player_id UUID REFERENCES players(id),
location_id UUID REFERENCES locations(id),
recorded_time JSONB NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Achievements
CREATE TABLE achievements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(50) NOT NULL,
condition_type VARCHAR(50) NOT NULL,
condition_value INTEGER NOT NULL,
icon VARCHAR(10),
points INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Spieler-Achievements
CREATE TABLE player_achievements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
player_id UUID REFERENCES players(id),
achievement_id UUID REFERENCES achievements(id),
earned_at TIMESTAMP,
progress INTEGER DEFAULT 0,
is_completed BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### Indizes
```sql
-- Performance-Indizes
CREATE INDEX idx_times_player_id ON times(player_id);
CREATE INDEX idx_times_location_id ON times(location_id);
CREATE INDEX idx_times_created_at ON times(created_at);
CREATE INDEX idx_player_achievements_player_id ON player_achievements(player_id);
CREATE INDEX idx_player_achievements_achievement_id ON player_achievements(achievement_id);
```
### PostgreSQL Funktionen
```sql
-- Achievement-Prüfung
CREATE OR REPLACE FUNCTION check_all_achievements(player_uuid UUID)
RETURNS VOID AS $$
BEGIN
PERFORM check_consistency_achievements(player_uuid);
PERFORM check_improvement_achievements(player_uuid);
PERFORM check_seasonal_achievements(player_uuid);
END;
$$ LANGUAGE plpgsql;
```
## 🔐 Authentifizierung
### API-Key Authentifizierung
```javascript
// Middleware für API-Key
const authenticateAPIKey = (req, res, next) => {
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'API-Key erforderlich' });
}
// Token validieren
const isValid = validateAPIKey(token);
if (!isValid) {
return res.status(401).json({ error: 'Ungültiger API-Key' });
}
req.apiKey = token;
next();
};
```
### Session-basierte Authentifizierung
```javascript
// Session-Middleware
const authenticateSession = (req, res, next) => {
if (!req.session || !req.session.userId) {
return res.status(401).json({ error: 'Nicht authentifiziert' });
}
req.userId = req.session.userId;
next();
};
```
### JWT-Token
```javascript
// JWT-Token generieren
const generateJWT = (user) => {
return jwt.sign(
{ userId: user.id, username: user.username },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
};
// JWT-Token validieren
const validateJWT = (token) => {
try {
return jwt.verify(token, process.env.JWT_SECRET);
} catch (error) {
return null;
}
};
```
## 🧪 Testing
### Unit-Tests
```javascript
// test/unit/Player.test.js
const { Player } = require('../../models/Player');
describe('Player Model', () => {
test('should create player with valid data', () => {
const playerData = {
firstname: 'Max',
lastname: 'Mustermann',
birthdate: '1990-01-01',
rfiduid: 'AA:BB:CC:DD'
};
const player = new Player(playerData);
expect(player.firstname).toBe('Max');
expect(player.lastname).toBe('Mustermann');
});
});
```
### Integration-Tests
```javascript
// test/integration/api.test.js
const request = require('supertest');
const app = require('../../server');
describe('API Endpoints', () => {
test('POST /api/v1/public/players', async () => {
const playerData = {
firstname: 'Max',
lastname: 'Mustermann',
birthdate: '1990-01-01',
rfiduid: 'AA:BB:CC:DD'
};
const response = await request(app)
.post('/api/v1/public/players')
.send(playerData)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.data.firstname).toBe('Max');
});
});
```
### API-Tests ausführen
```bash
# Alle Tests
npm test
# Unit-Tests
npm run test:unit
# Integration-Tests
npm run test:integration
# Coverage-Report
npm run test:coverage
```
## 🚀 Deployment
### Produktionsumgebung
```bash
# Abhängigkeiten installieren
npm install --production
# Umgebungsvariablen setzen
export NODE_ENV=production
export DB_HOST=production-db-host
export DB_PASSWORD=secure-password
# Server starten
npm start
```
### Docker-Container
```dockerfile
# Dockerfile
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
```
### Nginx-Konfiguration
```nginx
server {
listen 80;
server_name ninja.reptilfpv.de;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
```
### SSL-Zertifikat
```bash
# Let's Encrypt
certbot --nginx -d ninja.reptilfpv.de
```
## 📊 Monitoring
### Logging
```javascript
// Winston Logger
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' })
]
});
```
### Health-Checks
```javascript
// Health-Check Endpoint
app.get('/health', async (req, res) => {
try {
// Datenbank-Verbindung prüfen
await db.query('SELECT 1');
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
} catch (error) {
res.status(500).json({
status: 'unhealthy',
error: error.message
});
}
});
```
### Metriken
```javascript
// Prometheus-Metriken
const prometheus = require('prom-client');
const httpRequestDuration = new prometheus.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code']
});
const activeConnections = new prometheus.Gauge({
name: 'active_connections',
help: 'Number of active connections'
});
```
## 🔧 Wartung
### Datenbank-Backup
```bash
# Backup erstellen
pg_dump -h localhost -U username -d ninjaserver > backup.sql
# Backup wiederherstellen
psql -h localhost -U username -d ninjaserver < backup.sql
```
### Log-Rotation
```bash
# Logrotate-Konfiguration
/var/log/ninjaserver/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 644 node node
postrotate
systemctl reload ninjaserver
endscript
}
```
### Performance-Optimierung
```sql
-- Query-Performance analysieren
EXPLAIN ANALYZE SELECT * FROM times
WHERE player_id = 'uuid'
ORDER BY created_at DESC;
-- Indizes hinzufügen
CREATE INDEX CONCURRENTLY idx_times_player_created
ON times(player_id, created_at DESC);
```
### Sicherheits-Updates
```bash
# Abhängigkeiten aktualisieren
npm audit
npm audit fix
# Sicherheits-Updates
npm update
```
---
**Hinweis:** Für detaillierte API-Dokumentation siehe [API Referenz](API-Referenz) und für Achievement-Details siehe [Achievement System](Achievement-System).

380
wiki/FAQ.md Normal file
View File

@@ -0,0 +1,380 @@
# ❓ FAQ - Häufige Fragen
Antworten auf häufig gestellte Fragen zum Ninja Cross Parkour System.
## 🚀 Installation und Setup
### Wie installiere ich das System?
Siehe [Schnellstart](Schnellstart) für eine detaillierte Anleitung. Kurz gesagt:
1. Repository klonen
2. `npm install` ausführen
3. `.env`-Datei konfigurieren
4. `npm run init-db` ausführen
5. `npm start` starten
### Welche Voraussetzungen benötige ich?
- Node.js v16 oder höher
- PostgreSQL 12 oder höher
- npm oder yarn
- Git für die Installation
### Wie konfiguriere ich die Datenbank?
Bearbeiten Sie die `.env`-Datei mit Ihren Datenbankdaten:
```env
DB_HOST=localhost
DB_PORT=5432
DB_NAME=ninjaserver
DB_USER=your_username
DB_PASSWORD=your_password
```
### Wie erstelle ich den ersten Admin-Benutzer?
```bash
npm run create-user
```
Standard-Anmeldedaten: `admin` / `admin123`
## 🔐 Authentifizierung
### Wie funktioniert die API-Authentifizierung?
Das System verwendet API-Keys für die Authentifizierung. Generieren Sie einen Key:
```bash
curl -X POST http://localhost:3000/api/v1/web/generate-api-key \
-H "Content-Type: application/json" \
-d '{"description": "Mein API Key"}'
```
### Wie verwende ich den API-Key?
Fügen Sie den Key in den Authorization Header ein:
```http
Authorization: Bearer YOUR_API_KEY_HERE
```
### Wie lange sind API-Keys gültig?
API-Keys sind standardmäßig unbegrenzt gültig, können aber mit einem Ablaufdatum versehen werden.
### Kann ich mehrere API-Keys haben?
Ja, Sie können beliebig viele API-Keys erstellen, z.B. für verschiedene Anwendungen oder Benutzer.
## 🏃‍♂️ Spieler und Zeiten
### Wie registriere ich einen neuen Spieler?
```bash
curl -X POST http://localhost:3000/api/v1/public/players \
-H "Content-Type: application/json" \
-d '{"firstname": "Max", "lastname": "Mustermann", "birthdate": "1990-01-01", "rfiduid": "AA:BB:CC:DD"}'
```
### Wie verknüpfe ich eine RFID-Karte mit einem Spieler?
```bash
curl -X POST http://localhost:3000/api/v1/public/link-by-rfid \
-H "Content-Type: application/json" \
-d '{"rfiduid": "AA:BB:CC:DD", "supabase_user_id": "uuid-here"}'
```
### Wie messe ich eine Zeit?
```bash
curl -X POST http://localhost:3000/api/v1/private/create-time \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"player_id": "AA:BB:CC:DD", "location_id": "Standort-Name", "recorded_time": "01:23.456"}'
```
### Welches Zeitformat wird verwendet?
Zeiten werden im Format `MM:SS.mmm` gespeichert:
- Minuten:Sekunden.Millisekunden
- Beispiel: `01:23.456` = 1 Minute, 23 Sekunden, 456 Millisekunden
### Was ist eine gültige Zeit?
- **Minimum:** 30 Sekunden
- **Maximum:** 10 Minuten
- **Schwelle:** Konfigurierbar pro Standort (Standard: 120 Sekunden)
## 🏆 Achievements
### Wie funktioniert das Achievement-System?
Das System überprüft automatisch täglich alle Spieler auf neue Achievements. Siehe [Achievement System](Achievement-System) für Details.
### Wann werden Achievements vergeben?
- **Automatisch:** Täglich um 23:59 Uhr
- **Manuell:** Über API-Endpoints
- **Sofort:** Bei bestimmten Aktionen
### Wie viele Achievements gibt es?
Das System hat 32 verschiedene Achievements in 4 Kategorien:
- Konsistenz-basierte (8)
- Verbesserungs-basierte (4)
- Saisonale (4)
- Monatliche (12)
- Jahreszeiten (4)
### Wie rufe ich Achievements ab?
```bash
# Alle Achievements
curl http://localhost:3000/api/achievements
# Spieler-Achievements
curl http://localhost:3000/api/achievements/player/{playerId}
# Spieler-Statistiken
curl http://localhost:3000/api/achievements/player/{playerId}/stats
```
## 📍 Standorte
### Wie erstelle ich einen neuen Standort?
```bash
curl -X POST http://localhost:3000/api/v1/private/create-location \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "München", "latitude": 48.1351, "longitude": 11.5820}'
```
### Wie ändere ich die Zeit-Schwelle eines Standorts?
```bash
curl -X PUT http://localhost:3000/api/v1/private/locations/{id}/threshold \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"threshold_seconds": 120}'
```
### Wie viele Standorte kann ich haben?
Es gibt keine Begrenzung der Anzahl der Standorte.
## 🔧 Technische Fragen
### Wie starte ich den Server?
```bash
# Entwicklung
npm run dev
# Produktion
npm start
```
### Wie überwache ich den Server?
```bash
# Logs anzeigen
tail -f logs/server.log
# Achievement-Logs
tail -f /var/log/ninjaserver_achievements.log
# Datenbank-Status
psql -d ninjaserver -c "SELECT NOW();"
```
### Wie teste ich die API?
```bash
# Test-Skript ausführen
node test-api.js
# Einzelne Endpoints testen
curl http://localhost:3000/api/v1/public/locations
```
### Wie sichere ich die Datenbank?
```bash
# Vollständiges Backup
pg_dump -h localhost -U username -d ninjaserver > backup.sql
# Wiederherstellung
psql -h localhost -U username -d ninjaserver < backup.sql
```
## 🐛 Fehlerbehebung
### "Port 3000 bereits belegt"
```bash
# Port freigeben
sudo lsof -ti:3000 | xargs kill -9
# Oder anderen Port verwenden
PORT=3001 npm start
```
### "Datenbank-Verbindung fehlgeschlagen"
1. PostgreSQL-Service prüfen: `sudo systemctl status postgresql`
2. Datenbank-Credentials in `.env` prüfen
3. Firewall-Einstellungen prüfen
### "API-Key funktioniert nicht"
1. API-Key neu generieren
2. Authorization Header prüfen: `Bearer YOUR_API_KEY`
3. Token-Ablaufzeit prüfen
### "Zeit wird nicht gespeichert"
1. Gültige Zeit prüfen (innerhalb der Schwelle)
2. Standort korrekt ausgewählt
3. Internetverbindung prüfen
4. Seite neu laden
### "Achievements werden nicht vergeben"
1. Tägliche Prüfung abwarten
2. Bedingungen erfüllt prüfen
3. System-Status prüfen
4. Administrator kontaktieren
## 📊 Statistiken und Monitoring
### Wie rufe ich Statistiken ab?
```bash
# Admin-Statistiken
curl -H "Authorization: Bearer ADMIN_TOKEN" http://localhost:3000/api/v1/admin/stats
# Seiten-Statistiken
curl -H "Authorization: Bearer ADMIN_TOKEN" http://localhost:3000/api/v1/admin/page-stats
```
### Wie überwache ich die Performance?
```sql
-- Langsamste Queries
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY mean_time DESC LIMIT 10;
-- Index-Nutzung
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
ORDER BY idx_scan DESC;
```
### Wie prüfe ich die Datenbank-Größe?
```sql
-- Gesamtgröße
SELECT pg_size_pretty(pg_database_size('ninjaserver'));
-- Tabellen-Größen
SELECT tablename, pg_size_pretty(pg_total_relation_size(tablename)) as size
FROM pg_tables WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(tablename) DESC;
```
## 🔒 Sicherheit
### Wie sichere ich das System?
- Standardpasswörter ändern
- HTTPS in der Produktion verwenden
- Regelmäßige Backups
- API-Keys regelmäßig rotieren
- Firewall konfigurieren
### Wie ändere ich das Admin-Passwort?
```bash
# Neuen Admin-Benutzer erstellen
npm run create-user
# Oder direkt in der Datenbank
UPDATE adminusers SET password_hash = '$2b$10$...' WHERE username = 'admin';
```
### Wie deaktiviere ich einen API-Key?
```sql
UPDATE api_tokens SET is_active = false WHERE token = 'YOUR_TOKEN';
```
## 🌐 Deployment
### Wie deploye ich in die Produktion?
1. Server vorbereiten (Node.js, PostgreSQL)
2. Code deployen
3. Umgebungsvariablen setzen
4. Datenbank initialisieren
5. Nginx konfigurieren
6. SSL-Zertifikat einrichten
### Wie konfiguriere ich Nginx?
```nginx
server {
listen 80;
server_name ninja.reptilfpv.de;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
```
### Wie richte ich SSL ein?
```bash
# Let's Encrypt
certbot --nginx -d ninja.reptilfpv.de
```
## 📱 Frontend
### Wie integriere ich das System in meine Website?
Verwenden Sie die REST API oder das Web-Interface. Siehe [API Referenz](API-Referenz) für Details.
### Gibt es eine mobile App?
Eine native mobile App ist geplant, aber noch nicht verfügbar. Das Web-Interface ist responsive und funktioniert auf allen Geräten.
### Wie passe ich das Design an?
Bearbeiten Sie die CSS-Dateien im `public/css/` Verzeichnis oder erstellen Sie ein eigenes Frontend mit der API.
## 🔄 Updates und Wartung
### Wie aktualisiere ich das System?
```bash
# Code aktualisieren
git pull origin main
# Abhängigkeiten aktualisieren
npm install
# Datenbank-Migrationen (falls vorhanden)
npm run migrate
# Server neu starten
npm restart
```
### Wie führe ich Wartungsaufgaben durch?
```sql
-- Tabellen analysieren
ANALYZE;
-- Indizes neu aufbauen
REINDEX DATABASE ninjaserver;
-- Vakuum durchführen
VACUUM ANALYZE;
```
### Wie lösche ich alte Daten?
```sql
-- Alte Zeiten löschen (älter als 1 Jahr)
DELETE FROM times WHERE created_at < NOW() - INTERVAL '1 year';
-- Alte Seitenaufrufe löschen (älter als 6 Monate)
DELETE FROM page_views WHERE created_at < NOW() - INTERVAL '6 months';
```
## 📞 Support
### Wo bekomme ich Hilfe?
- 📖 Konsultieren Sie diese Dokumentation
- 🔍 Schauen Sie in [Troubleshooting](Troubleshooting)
- 📧 Kontaktieren Sie den Systemadministrator
- 🐛 Melden Sie Bugs über das Issue-System
### Wie melde ich einen Bug?
1. Beschreiben Sie das Problem
2. Fügen Sie Logs hinzu
3. Geben Sie Schritte zur Reproduktion an
4. Erwähnen Sie Ihre Systemkonfiguration
### Wie schlage ich eine Verbesserung vor?
1. Beschreiben Sie die gewünschte Funktion
2. Erklären Sie den Nutzen
3. Geben Sie Beispiele an
4. Erwähnen Sie mögliche Implementierung
---
**Hinweis:** Diese FAQ wird regelmäßig aktualisiert. Bei Fragen, die hier nicht beantwortet werden, wenden Sie sich an den Support.

92
wiki/Home.md Normal file
View File

@@ -0,0 +1,92 @@
# 🏊‍♂️ Ninja Cross Parkour System Wiki
Willkommen zum **Ninja Cross Parkour System** - einem interaktiven Zeitmessungssystem für das Schwimmbad!
## 📋 Inhaltsverzeichnis
- [🏠 Home](Home) - Diese Seite
- [🚀 Schnellstart](Schnellstart) - Installation und erste Schritte
- [📖 Benutzerhandbuch](Benutzerhandbuch) - Anleitung für Endbenutzer
- [🔧 Entwicklerhandbuch](Entwicklerhandbuch) - Technische Dokumentation
- [📡 API Referenz](API-Referenz) - Vollständige API-Dokumentation
- [🏆 Achievement System](Achievement-System) - Gamification Features
- [🗄️ Datenbank](Datenbank) - Schema und Struktur
- [🔒 Sicherheit](Sicherheit) - Authentifizierung und Berechtigungen
- [🚀 Deployment](Deployment) - Produktionsumgebung
- [❓ FAQ](FAQ) - Häufige Fragen
- [🐛 Troubleshooting](Troubleshooting) - Problembehandlung
## 🎯 Was ist das Ninja Cross Parkour System?
Das **Ninja Cross Parkour System** ist ein innovatives Zeitmessungssystem, das speziell für Schwimmbäder entwickelt wurde. Es ermöglicht es Besuchern, ihre Parkour-Zeiten zu messen, zu verfolgen und sich mit anderen zu vergleichen.
### ✨ Hauptfunktionen
- **⏱️ Präzise Zeitmessung** mit RFID-Technologie
- **🗺️ Interaktive Karte** mit Standortverwaltung
- **🏆 Achievement-System** mit 32 verschiedenen Erfolgen
- **📊 Statistiken** und Bestenlisten
- **🔔 Push-Benachrichtigungen** für neue Rekorde
- **🌐 REST API** für Integrationen
- **📱 Responsive Web-Interface** für alle Geräte
### 🎮 Wie funktioniert es?
1. **Spieler registrieren** sich über das Web-Interface
2. **RFID-Karten** werden mit Spielerprofilen verknüpft
3. **Zeitmessung** erfolgt automatisch beim Start/Stopp
4. **Achievements** werden automatisch vergeben
5. **Statistiken** werden in Echtzeit aktualisiert
## 🏗️ System-Architektur
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Backend │ │ Database │
│ (Web UI) │◄──►│ (Node.js) │◄──►│ (PostgreSQL) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ RFID Reader │ │ API Endpoints │ │ Achievement │
│ (Hardware) │ │ (REST) │ │ System │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
## 🎯 Zielgruppen
### 👥 Endbenutzer (Schwimmbadbesucher)
- Zeitmessung und -verfolgung
- Achievement-Sammlung
- Statistiken und Fortschritt
- Bestenlisten
### 👨‍💼 Administratoren
- Spieler- und Standortverwaltung
- System-Monitoring
- Statistiken und Berichte
- API-Key Management
### 👨‍💻 Entwickler
- API-Integration
- Custom Frontend
- Datenbank-Zugriff
- System-Erweiterungen
## 🚀 Schnellstart
Für einen schnellen Einstieg siehe [Schnellstart](Schnellstart).
## 📞 Support
Bei Fragen oder Problemen:
- 📖 Konsultieren Sie die [FAQ](FAQ)
- 🔍 Schauen Sie in [Troubleshooting](Troubleshooting)
- 📧 Kontaktieren Sie den Systemadministrator
---
**Version:** 1.0.0
**Letzte Aktualisierung:** $(date)
**Autor:** Carsten Graf

220
wiki/Schnellstart.md Normal file
View File

@@ -0,0 +1,220 @@
# 🚀 Schnellstart
Diese Anleitung führt Sie durch die Installation und den ersten Start des Ninja Cross Parkour Systems.
## 📋 Voraussetzungen
### System-Anforderungen
- **Node.js** v16 oder höher
- **PostgreSQL** 12 oder höher
- **npm** oder **yarn**
- **Git** (für Installation)
### Hardware-Anforderungen
- **RFID-Reader** für Zeitmessung
- **RFID-Karten** für Spieler
- **Server** (Linux empfohlen)
- **Netzwerk** für API-Zugriff
## 🔧 Installation
### 1. Repository klonen
```bash
git clone <repository-url>
cd ninjaserver
```
### 2. Abhängigkeiten installieren
```bash
npm install
```
### 3. Umgebungsvariablen konfigurieren
```bash
cp .env.example .env
```
Bearbeiten Sie die `.env`-Datei:
```env
# Datenbank
DB_HOST=localhost
DB_PORT=5432
DB_NAME=ninjaserver
DB_USER=your_username
DB_PASSWORD=your_password
# Server
PORT=3000
NODE_ENV=development
# JWT Secret
JWT_SECRET=your_jwt_secret_here
# Supabase (optional)
SUPABASE_URL=your_supabase_url
SUPABASE_ANON_KEY=your_supabase_key
```
### 4. Datenbank initialisieren
```bash
npm run init-db
```
### 5. Standardbenutzer erstellen
```bash
npm run create-user
```
## 🚀 Server starten
### Entwicklungsumgebung
```bash
npm run dev
```
Server läuft auf: `http://localhost:3000`
### Produktionsumgebung
```bash
npm start
```
## 🔐 Erste Anmeldung
### Web-Interface
1. Öffnen Sie `http://localhost:3000`
2. Melden Sie sich an mit:
- **Benutzername:** `admin`
- **Passwort:** `admin123`
### API-Key generieren
```bash
curl -X POST http://localhost:3000/api/v1/web/generate-api-key \
-H "Content-Type: application/json" \
-d '{"description": "Mein erster API Key", "standorte": "Test"}'
```
## 🎮 Erste Schritte
### 1. Standort erstellen
```bash
curl -X POST http://localhost:3000/api/v1/private/create-location \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Test-Standort", "latitude": 48.1351, "longitude": 11.5820}'
```
### 2. Spieler erstellen
```bash
curl -X POST http://localhost:3000/api/v1/public/players \
-H "Content-Type: application/json" \
-d '{"firstname": "Max", "lastname": "Mustermann", "birthdate": "1990-01-01", "rfiduid": "AA:BB:CC:DD"}'
```
### 3. Zeit messen
```bash
curl -X POST http://localhost:3000/api/v1/private/create-time \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"player_id": "AA:BB:CC:DD", "location_id": "Test-Standort", "recorded_time": "01:23.456"}'
```
## 🧪 System testen
### API-Test ausführen
```bash
# Test-Skript bearbeiten
nano test-api.js
# API_KEY in der Datei setzen
node test-api.js
```
### Web-Interface testen
1. Öffnen Sie `http://localhost:3000`
2. Melden Sie sich an
3. Erstellen Sie einen Standort
4. Fügen Sie einen Spieler hinzu
5. Messen Sie eine Zeit
## 📊 Monitoring
### Logs überwachen
```bash
# Server-Logs
tail -f logs/server.log
# Achievement-Logs
tail -f /var/log/ninjaserver_achievements.log
```
### Datenbank-Status prüfen
```sql
-- Verbindung testen
SELECT NOW();
-- Tabellen anzeigen
\dt
-- Spieler zählen
SELECT COUNT(*) FROM players;
```
## 🔧 Konfiguration
### Standort-Schwellenwerte
```bash
curl -X PUT http://localhost:3000/api/v1/private/locations/{id}/threshold \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"threshold_seconds": 120}'
```
### Achievement-System aktivieren
```bash
# Tägliche Prüfung einrichten
node scripts/setup_cron.js setup
# Status prüfen
node scripts/setup_cron.js status
```
## 🚨 Häufige Probleme
### Port bereits belegt
```bash
# Port 3000 freigeben
sudo lsof -ti:3000 | xargs kill -9
# Oder anderen Port verwenden
PORT=3001 npm start
```
### Datenbank-Verbindung fehlgeschlagen
1. PostgreSQL-Service prüfen: `sudo systemctl status postgresql`
2. Datenbank-Credentials in `.env` prüfen
3. Firewall-Einstellungen prüfen
### API-Key funktioniert nicht
1. API-Key neu generieren
2. Authorization Header prüfen: `Bearer YOUR_API_KEY`
3. Token-Ablaufzeit prüfen
## 📚 Nächste Schritte
Nach der erfolgreichen Installation:
1. 📖 Lesen Sie das [Benutzerhandbuch](Benutzerhandbuch)
2. 🔧 Konsultieren Sie die [Entwicklerhandbuch](Entwicklerhandbuch)
3. 📡 Schauen Sie in die [API Referenz](API-Referenz)
4. 🏆 Entdecken Sie das [Achievement System](Achievement-System)
## 🆘 Hilfe
Bei Problemen:
- 📖 [FAQ](FAQ) konsultieren
- 🔍 [Troubleshooting](Troubleshooting) durchgehen
- 📧 Support kontaktieren
---
**Tipp:** Verwenden Sie `npm run dev` für die Entwicklung - der Server startet automatisch neu bei Änderungen!

621
wiki/Sicherheit.md Normal file
View File

@@ -0,0 +1,621 @@
# 🔒 Sicherheit
Sicherheitsrichtlinien und Best Practices für das Ninja Cross Parkour System.
## 📋 Inhaltsverzeichnis
- [🛡️ Sicherheitsübersicht](#-sicherheitsübersicht)
- [🔐 Authentifizierung](#-authentifizierung)
- [🔑 Autorisierung](#-autorisierung)
- [🛡️ Datenverschlüsselung](#-datenverschlüsselung)
- [🌐 Netzwerksicherheit](#-netzwerksicherheit)
- [🗄️ Datenbanksicherheit](#-datenbanksicherheit)
- [📱 API-Sicherheit](#-api-sicherheit)
- [🔍 Monitoring](#-monitoring)
- [🚨 Incident Response](#-incident-response)
## 🛡️ Sicherheitsübersicht
### Sicherheitsprinzipien
- **Defense in Depth** - Mehrschichtige Sicherheit
- **Least Privilege** - Minimale Berechtigungen
- **Zero Trust** - Kein Vertrauen ohne Verifikation
- **Security by Design** - Sicherheit von Anfang an
### Bedrohungsmodell
- **Externe Angriffe** - Unbefugter Zugriff von außen
- **Interne Bedrohungen** - Missbrauch durch autorisierte Benutzer
- **Datenlecks** - Unbefugte Offenlegung von Daten
- **Service-Ausfälle** - Verfügbarkeitsprobleme
## 🔐 Authentifizierung
### Passwort-Sicherheit
```javascript
// Passwort-Hashing mit bcrypt
const bcrypt = require('bcrypt');
// Passwort hashen
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Passwort verifizieren
const isValid = await bcrypt.compare(password, hashedPassword);
```
### Passwort-Richtlinien
- **Mindestlänge:** 12 Zeichen
- **Komplexität:** Groß-/Kleinbuchstaben, Zahlen, Sonderzeichen
- **Keine Wörterbuchwörter**
- **Regelmäßige Rotation** (alle 90 Tage)
### Multi-Faktor-Authentifizierung (MFA)
```javascript
// TOTP-Implementierung
const speakeasy = require('speakeasy');
// Secret generieren
const secret = speakeasy.generateSecret({
name: 'Ninja Parkour',
account: 'admin@example.com'
});
// Token verifizieren
const verified = speakeasy.totp.verify({
secret: secret.base32,
encoding: 'base32',
token: userToken,
window: 2
});
```
### Session-Management
```javascript
// Sichere Session-Konfiguration
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 Stunden
sameSite: 'strict'
}
}));
```
## 🔑 Autorisierung
### Rollenbasierte Zugriffskontrolle (RBAC)
```javascript
// Rollen definieren
const ROLES = {
ADMIN: 'admin',
MODERATOR: 'moderator',
USER: 'user',
GUEST: 'guest'
};
// Berechtigungen definieren
const PERMISSIONS = {
READ_PLAYERS: 'read:players',
WRITE_PLAYERS: 'write:players',
DELETE_PLAYERS: 'delete:players',
READ_TIMES: 'read:times',
WRITE_TIMES: 'write:times',
ADMIN_ACCESS: 'admin:access'
};
// Rollen-Berechtigungen
const rolePermissions = {
[ROLES.ADMIN]: Object.values(PERMISSIONS),
[ROLES.MODERATOR]: [
PERMISSIONS.READ_PLAYERS,
PERMISSIONS.WRITE_PLAYERS,
PERMISSIONS.READ_TIMES,
PERMISSIONS.WRITE_TIMES
],
[ROLES.USER]: [
PERMISSIONS.READ_PLAYERS,
PERMISSIONS.READ_TIMES
]
};
```
### API-Key-Management
```javascript
// API-Key generieren
const generateAPIKey = () => {
return crypto.randomBytes(32).toString('hex');
};
// API-Key validieren
const validateAPIKey = async (apiKey) => {
const token = await db.query(
'SELECT * FROM api_tokens WHERE token = $1 AND is_active = true',
[apiKey]
);
return token.rows.length > 0;
};
// Berechtigungen prüfen
const checkPermission = (userRole, requiredPermission) => {
const userPermissions = rolePermissions[userRole] || [];
return userPermissions.includes(requiredPermission);
};
```
### Middleware für Autorisierung
```javascript
// Authentifizierung prüfen
const requireAuth = (req, res, next) => {
if (!req.session || !req.session.userId) {
return res.status(401).json({ error: 'Nicht authentifiziert' });
}
next();
};
// Rolle prüfen
const requireRole = (role) => {
return (req, res, next) => {
if (req.session.role !== role) {
return res.status(403).json({ error: 'Keine Berechtigung' });
}
next();
};
};
// Berechtigung prüfen
const requirePermission = (permission) => {
return (req, res, next) => {
const userRole = req.session.role;
if (!checkPermission(userRole, permission)) {
return res.status(403).json({ error: 'Keine Berechtigung' });
}
next();
};
};
```
## 🛡️ Datenverschlüsselung
### Verschlüsselung im Ruhezustand
```javascript
// Datenbank-Verschlüsselung
const crypto = require('crypto');
// Verschlüsselung
const encrypt = (text, key) => {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher('aes-256-cbc', key);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted;
};
// Entschlüsselung
const decrypt = (text, key) => {
const textParts = text.split(':');
const iv = Buffer.from(textParts.shift(), 'hex');
const encryptedText = textParts.join(':');
const decipher = crypto.createDecipher('aes-256-cbc', key);
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
};
```
### Verschlüsselung in Bewegung
```javascript
// HTTPS-Konfiguration
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem'),
ciphers: [
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES256-SHA384',
'ECDHE-RSA-AES128-SHA256'
].join(':'),
honorCipherOrder: true
};
https.createServer(options, app).listen(443);
```
### Passwort-Verschlüsselung
```javascript
// Starke Passwort-Generierung
const generatePassword = (length = 16) => {
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
let password = '';
for (let i = 0; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length));
}
return password;
};
// Passwort-Stärke prüfen
const validatePassword = (password) => {
const minLength = 12;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return password.length >= minLength &&
hasUpperCase &&
hasLowerCase &&
hasNumbers &&
hasSpecialChar;
};
```
## 🌐 Netzwerksicherheit
### Firewall-Konfiguration
```bash
# UFW-Firewall (Ubuntu)
sudo ufw enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Erlaubte Ports
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 3000/tcp # App (nur intern)
# Rate Limiting
sudo ufw limit ssh
```
### DDoS-Schutz
```javascript
// Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 Minuten
max: 100, // Max 100 Requests pro IP
message: 'Zu viele Anfragen von dieser IP',
standardHeaders: true,
legacyHeaders: false
});
app.use('/api/', limiter);
// Strikte Rate Limits für sensible Endpoints
const strictLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Zu viele Login-Versuche'
});
app.use('/api/login', strictLimiter);
```
### CORS-Konfiguration
```javascript
// Sichere CORS-Einstellungen
const cors = require('cors');
const corsOptions = {
origin: (origin, callback) => {
const allowedOrigins = [
'https://ninja.reptilfpv.de',
'https://www.ninja.reptilfpv.de'
];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Nicht erlaubt durch CORS'));
}
},
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
```
### SSL/TLS-Konfiguration
```javascript
// SSL-Redirect
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});
// Security Headers
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
```
## 🗄️ Datenbanksicherheit
### Verbindungssicherheit
```javascript
// Sichere Datenbankverbindung
const dbConfig = {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: {
rejectUnauthorized: true,
ca: fs.readFileSync('ca-certificate.pem')
},
connectionTimeoutMillis: 5000,
idleTimeoutMillis: 30000,
max: 20
};
```
### SQL-Injection-Schutz
```javascript
// Parametrisierte Queries
const getUser = async (userId) => {
const query = 'SELECT * FROM players WHERE id = $1';
const values = [userId];
const result = await db.query(query, values);
return result.rows[0];
};
// Input-Validierung
const validateInput = (input) => {
if (typeof input !== 'string') {
throw new Error('Invalid input type');
}
// SQL-Injection-Patterns erkennen
const sqlPatterns = [
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT)\b)/gi,
/(\b(OR|AND)\s+\d+\s*=\s*\d+)/gi,
/(\b(OR|AND)\s+['"]\s*=\s*['"])/gi
];
for (const pattern of sqlPatterns) {
if (pattern.test(input)) {
throw new Error('Potential SQL injection detected');
}
}
return input;
};
```
### Datenbank-Berechtigungen
```sql
-- Benutzer mit minimalen Rechten erstellen
CREATE USER ninja_app WITH PASSWORD 'secure_password';
-- Nur notwendige Berechtigungen gewähren
GRANT SELECT, INSERT, UPDATE, DELETE ON players TO ninja_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON times TO ninja_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON locations TO ninja_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON achievements TO ninja_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON player_achievements TO ninja_app;
-- Keine Admin-Rechte
REVOKE ALL ON SCHEMA public FROM ninja_app;
```
## 📱 API-Sicherheit
### Input-Validierung
```javascript
// Joi-Schema für Validierung
const Joi = require('joi');
const playerSchema = Joi.object({
firstname: Joi.string().min(2).max(50).required(),
lastname: Joi.string().min(2).max(50).required(),
birthdate: Joi.date().max('now').required(),
rfiduid: Joi.string().pattern(/^[A-F0-9:]{11}$/).optional()
});
const validatePlayer = (req, res, next) => {
const { error } = playerSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
next();
};
```
### API-Versionierung
```javascript
// API-Versionierung
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);
// Deprecation-Warnungen
app.use('/api/v1', (req, res, next) => {
res.set('X-API-Version', '1.0.0');
res.set('X-API-Deprecated', 'false');
next();
});
```
### Request-Logging
```javascript
// Sicherheitsrelevante Logs
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'logs/security.log' })
]
});
// Login-Versuche loggen
app.post('/api/login', (req, res) => {
const { username, ip } = req.body;
securityLogger.info('Login attempt', { username, ip, timestamp: new Date() });
// ... Login-Logik
});
```
## 🔍 Monitoring
### Sicherheits-Monitoring
```javascript
// Anomalie-Erkennung
const detectAnomalies = (req, res, next) => {
const ip = req.ip;
const userAgent = req.get('User-Agent');
// Verdächtige Patterns erkennen
const suspiciousPatterns = [
/sqlmap/i,
/nikto/i,
/nmap/i,
/masscan/i
];
for (const pattern of suspiciousPatterns) {
if (pattern.test(userAgent)) {
securityLogger.warn('Suspicious user agent detected', { ip, userAgent });
return res.status(403).json({ error: 'Request blocked' });
}
}
next();
};
```
### Log-Analyse
```bash
# Sicherheitsrelevante Logs analysieren
grep "ERROR\|WARN\|SECURITY" logs/server.log | tail -100
# Failed Login-Versuche
grep "Login attempt" logs/security.log | grep "failed"
# Verdächtige Aktivitäten
grep "suspicious\|anomaly\|attack" logs/security.log
```
### Alerting
```javascript
// E-Mail-Benachrichtigungen
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransporter({
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
});
const sendSecurityAlert = async (message) => {
await transporter.sendMail({
from: process.env.EMAIL_USER,
to: process.env.ADMIN_EMAIL,
subject: 'Security Alert - Ninja Parkour',
text: message
});
};
```
## 🚨 Incident Response
### Incident-Response-Plan
1. **Erkennung** - Monitoring und Alerts
2. **Bewertung** - Schweregrad bestimmen
3. **Eindämmung** - Schaden begrenzen
4. **Eliminierung** - Bedrohung entfernen
5. **Wiederherstellung** - System reparieren
6. **Lektionen** - Aus Fehlern lernen
### Notfall-Kontakte
- **Systemadministrator:** +49 123 456 789
- **Sicherheitsbeauftragter:** security@ninjaparkour.de
- **Management:** management@ninjaparkour.de
### Backup-Strategie
```bash
# Tägliche Backups
pg_dump -h localhost -U username -d ninjaserver | gzip > backup_$(date +%Y%m%d).sql.gz
# Wöchentliche Vollständige Backups
tar -czf full_backup_$(date +%Y%m%d).tar.gz /var/lib/postgresql/data/
# Backup-Verifizierung
psql -d ninjaserver < backup_$(date +%Y%m%d).sql
```
### Recovery-Prozeduren
```bash
# System wiederherstellen
sudo systemctl stop ninjaserver
psql -d ninjaserver < latest_backup.sql
sudo systemctl start ninjaserver
# Datenbank reparieren
psql -d ninjaserver -c "VACUUM FULL;"
psql -d ninjaserver -c "REINDEX DATABASE ninjaserver;"
```
## 🔧 Sicherheits-Checkliste
### Regelmäßige Überprüfungen
- [ ] Passwörter geändert (alle 90 Tage)
- [ ] API-Keys rotiert (alle 180 Tage)
- [ ] SSL-Zertifikate gültig
- [ ] Sicherheits-Updates installiert
- [ ] Logs überprüft
- [ ] Backups getestet
- [ ] Penetrationstests durchgeführt
### Wöchentliche Aufgaben
- [ ] Sicherheits-Logs analysiert
- [ ] System-Updates geprüft
- [ ] Backup-Status überprüft
- [ ] Performance-Metriken analysiert
### Monatliche Aufgaben
- [ ] Sicherheits-Audit durchgeführt
- [ ] Berechtigungen überprüft
- [ ] Incident-Response-Plan getestet
- [ ] Sicherheitsschulungen durchgeführt
---
**Wichtig:** Diese Sicherheitsrichtlinien müssen regelmäßig überprüft und aktualisiert werden. Bei Sicherheitsvorfällen wenden Sie sich sofort an den Sicherheitsbeauftragten.

687
wiki/Troubleshooting.md Normal file
View File

@@ -0,0 +1,687 @@
# 🐛 Troubleshooting
Problembehandlung und Lösungen für häufige Probleme im Ninja Cross Parkour System.
## 📋 Inhaltsverzeichnis
- [🚨 Kritische Probleme](#-kritische-probleme)
- [🔧 Installation](#-installation)
- [🗄️ Datenbank](#-datenbank)
- [🌐 API](#-api)
- [🏆 Achievements](#-achievements)
- [📊 Performance](#-performance)
- [🔒 Sicherheit](#-sicherheit)
- [📱 Frontend](#-frontend)
## 🚨 Kritische Probleme
### Server startet nicht
**Symptome:**
- `npm start` schlägt fehl
- Port-Fehler
- Abhängigkeitsfehler
**Lösungen:**
```bash
# Port freigeben
sudo lsof -ti:3000 | xargs kill -9
# Abhängigkeiten neu installieren
rm -rf node_modules package-lock.json
npm install
# Anderen Port verwenden
PORT=3001 npm start
# Logs prüfen
tail -f logs/server.log
```
### Datenbank-Verbindung fehlgeschlagen
**Symptome:**
- `ECONNREFUSED` Fehler
- `database connection failed`
- Timeout-Fehler
**Lösungen:**
```bash
# PostgreSQL-Status prüfen
sudo systemctl status postgresql
# PostgreSQL starten
sudo systemctl start postgresql
# Verbindung testen
psql -h localhost -U username -d ninjaserver
# .env-Datei prüfen
cat .env | grep DB_
```
### API antwortet nicht
**Symptome:**
- 500 Internal Server Error
- Timeout-Fehler
- Leere Antworten
**Lösungen:**
```bash
# Server-Status prüfen
curl http://localhost:3000/health
# Logs analysieren
tail -f logs/server.log
# Datenbank-Verbindung prüfen
psql -d ninjaserver -c "SELECT NOW();"
# Speicher prüfen
free -h
df -h
```
## 🔧 Installation
### Node.js-Version inkompatibel
**Symptom:**
```
Error: Node.js version 14.x is not supported. Please use Node.js 16 or higher.
```
**Lösung:**
```bash
# Node.js-Version prüfen
node --version
# Node.js aktualisieren (Ubuntu/Debian)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# Node.js aktualisieren (macOS)
brew install node@18
# Node.js aktualisieren (Windows)
# Download von https://nodejs.org/
```
### npm install schlägt fehl
**Symptome:**
- Permission denied
- ENOENT Fehler
- Network timeout
**Lösungen:**
```bash
# Berechtigungen prüfen
sudo chown -R $(whoami) ~/.npm
# Cache leeren
npm cache clean --force
# Registry wechseln
npm config set registry https://registry.npmjs.org/
# Mit sudo installieren (nicht empfohlen)
sudo npm install
```
### PostgreSQL nicht installiert
**Symptom:**
```
Error: connect ECONNREFUSED 127.0.0.1:5432
```
**Lösung:**
```bash
# PostgreSQL installieren (Ubuntu/Debian)
sudo apt update
sudo apt install postgresql postgresql-contrib
# PostgreSQL starten
sudo systemctl start postgresql
sudo systemctl enable postgresql
# Benutzer erstellen
sudo -u postgres createuser --interactive
sudo -u postgres createdb ninjaserver
```
### Umgebungsvariablen fehlen
**Symptom:**
```
Error: Missing required environment variable: DB_HOST
```
**Lösung:**
```bash
# .env-Datei erstellen
cp .env.example .env
# .env-Datei bearbeiten
nano .env
# Beispiel-Inhalt:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=ninjaserver
DB_USER=username
DB_PASSWORD=password
JWT_SECRET=your-secret-key
```
## 🗄️ Datenbank
### Datenbank existiert nicht
**Symptom:**
```
Error: database "ninjaserver" does not exist
```
**Lösung:**
```bash
# Datenbank erstellen
sudo -u postgres createdb ninjaserver
# Oder mit psql
psql -U postgres -c "CREATE DATABASE ninjaserver;"
```
### Tabellen fehlen
**Symptom:**
```
Error: relation "players" does not exist
```
**Lösung:**
```bash
# Datenbank initialisieren
npm run init-db
# Oder manuell
psql -d ninjaserver -f scripts/init-db.js
```
### Verbindungslimit erreicht
**Symptom:**
```
Error: too many connections
```
**Lösung:**
```sql
-- Aktive Verbindungen prüfen
SELECT count(*) FROM pg_stat_activity;
-- Verbindungen beenden
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle' AND query_start < NOW() - INTERVAL '5 minutes';
-- Verbindungslimit erhöhen
ALTER SYSTEM SET max_connections = 200;
SELECT pg_reload_conf();
```
### Datenbank-Corruption
**Symptom:**
```
Error: database is not accepting commands
```
**Lösung:**
```bash
# Datenbank reparieren
pg_ctl stop
pg_ctl start -D /var/lib/postgresql/data
# Vakuum durchführen
psql -d ninjaserver -c "VACUUM FULL;"
# Backup wiederherstellen
psql -d ninjaserver < backup.sql
```
## 🌐 API
### 401 Unauthorized
**Symptom:**
```
{"success": false, "message": "API-Key erforderlich"}
```
**Lösung:**
```bash
# API-Key generieren
curl -X POST http://localhost:3000/api/v1/web/generate-api-key \
-H "Content-Type: application/json" \
-d '{"description": "Test Key"}'
# API-Key verwenden
curl -H "Authorization: Bearer YOUR_API_KEY" \
http://localhost:3000/api/v1/private/locations
```
### 403 Forbidden
**Symptom:**
```
{"success": false, "message": "Keine Berechtigung"}
```
**Lösung:**
```bash
# Admin-Token verwenden
curl -H "Authorization: Bearer ADMIN_TOKEN" \
http://localhost:3000/api/v1/admin/players
# Oder API-Key mit Admin-Rechten generieren
```
### 404 Not Found
**Symptom:**
```
{"success": false, "message": "Ressource nicht gefunden"}
```
**Lösung:**
```bash
# Korrekte URL verwenden
curl http://localhost:3000/api/v1/public/locations
# Endpoint-Liste prüfen
curl http://localhost:3000/api-docs
```
### 500 Internal Server Error
**Symptom:**
```
{"success": false, "message": "Internal Server Error"}
```
**Lösung:**
```bash
# Logs prüfen
tail -f logs/server.log
# Datenbank-Verbindung prüfen
psql -d ninjaserver -c "SELECT NOW();"
# Server neu starten
npm restart
```
### CORS-Fehler
**Symptom:**
```
Access to fetch at 'http://localhost:3000/api' from origin 'http://localhost:8080' has been blocked by CORS policy
```
**Lösung:**
```javascript
// CORS-Middleware konfigurieren
app.use(cors({
origin: ['http://localhost:8080', 'https://yourdomain.com'],
credentials: true
}));
```
## 🏆 Achievements
### Achievements werden nicht vergeben
**Symptom:**
- Spieler erfüllt Bedingungen, aber erhält kein Achievement
- Tägliche Prüfung läuft nicht
**Lösung:**
```bash
# Tägliche Prüfung manuell ausführen
node scripts/daily_achievements.js
# Cron-Job prüfen
node scripts/setup_cron.js status
# Cron-Job einrichten
node scripts/setup_cron.js setup
# Logs prüfen
tail -f /var/log/ninjaserver_achievements.log
```
### Achievement-Daten korrupt
**Symptom:**
- Achievements werden mehrfach vergeben
- Falsche Fortschrittswerte
**Lösung:**
```sql
-- Doppelte Achievements entfernen
DELETE FROM player_achievements
WHERE id NOT IN (
SELECT MIN(id)
FROM player_achievements
GROUP BY player_id, achievement_id
);
-- Fortschritt zurücksetzen
UPDATE player_achievements
SET progress = 0, is_completed = false
WHERE is_completed = false;
```
### Tägliche Prüfung schlägt fehl
**Symptom:**
```
Error: Daily achievement check failed
```
**Lösung:**
```bash
# Logs analysieren
grep "ERROR" /var/log/ninjaserver_achievements.log
# Datenbank-Verbindung prüfen
psql -d ninjaserver -c "SELECT NOW();"
# Berechtigungen prüfen
ls -la /var/log/ninjaserver_achievements.log
# Script manuell ausführen
node scripts/daily_achievements.js
```
## 📊 Performance
### Langsame API-Antworten
**Symptom:**
- API-Antworten dauern > 5 Sekunden
- Timeout-Fehler
**Lösung:**
```sql
-- Indizes prüfen
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
ORDER BY idx_scan DESC;
-- Langsame Queries identifizieren
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY mean_time DESC LIMIT 10;
-- Indizes hinzufügen
CREATE INDEX CONCURRENTLY idx_times_player_created
ON times(player_id, created_at DESC);
```
### Hohe CPU-Last
**Symptom:**
- Server verbraucht > 80% CPU
- Langsame Antwortzeiten
**Lösung:**
```bash
# Prozesse prüfen
top -p $(pgrep node)
# Speicher prüfen
free -h
# Datenbank-Verbindungen prüfen
psql -d ninjaserver -c "SELECT count(*) FROM pg_stat_activity;"
# Vakuum durchführen
psql -d ninjaserver -c "VACUUM ANALYZE;"
```
### Speicher-Leaks
**Symptom:**
- Speicherverbrauch steigt kontinuierlich
- Server stürzt ab
**Lösung:**
```bash
# Speicher-Usage prüfen
ps aux | grep node
# Server neu starten
pm2 restart ninjaserver
# Oder mit systemd
sudo systemctl restart ninjaserver
```
## 🔒 Sicherheit
### Schwache Passwörter
**Symptom:**
- Standardpasswörter in Produktion
- Sicherheitswarnungen
**Lösung:**
```bash
# Admin-Passwort ändern
npm run create-user
# Oder direkt in der Datenbank
psql -d ninjaserver -c "UPDATE adminusers SET password_hash = '\$2b\$10\$...' WHERE username = 'admin';"
```
### API-Key kompromittiert
**Symptom:**
- Unerwartete API-Aufrufe
- Unbekannte Aktivitäten
**Lösung:**
```sql
-- API-Key deaktivieren
UPDATE api_tokens SET is_active = false WHERE token = 'COMPROMISED_TOKEN';
-- Neuen API-Key generieren
-- (über API oder direkt in der Datenbank)
```
### SQL-Injection
**Symptom:**
- Unerwartete Datenbank-Abfragen
- Fehlerhafte API-Antworten
**Lösung:**
```javascript
// Parametrisierte Queries verwenden
const result = await db.query(
'SELECT * FROM players WHERE id = $1',
[playerId]
);
// Input-Validierung
if (!isValidUUID(playerId)) {
throw new Error('Invalid player ID');
}
```
## 📱 Frontend
### JavaScript-Fehler
**Symptom:**
- Konsole zeigt Fehler
- Funktionen funktionieren nicht
**Lösung:**
```javascript
// Browser-Konsole prüfen
console.error('Error details:', error);
// API-Verbindung testen
fetch('/api/v1/public/locations')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('API Error:', error));
```
### CORS-Probleme
**Symptom:**
```
Access to fetch at 'http://localhost:3000/api' from origin 'http://localhost:8080' has been blocked by CORS policy
```
**Lösung:**
```javascript
// CORS-Header setzen
fetch('/api/v1/public/locations', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
mode: 'cors'
});
```
### Session-Probleme
**Symptom:**
- Login funktioniert nicht
- Session wird nicht gespeichert
**Lösung:**
```javascript
// Session-Cookie prüfen
document.cookie
// Login-Request prüfen
fetch('/api/v1/public/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
username: 'admin',
password: 'admin123'
})
});
```
## 🔧 Allgemeine Lösungen
### Logs analysieren
```bash
# Server-Logs
tail -f logs/server.log
# Error-Logs
grep "ERROR" logs/server.log
# Achievement-Logs
tail -f /var/log/ninjaserver_achievements.log
# System-Logs
journalctl -u ninjaserver -f
```
### System-Status prüfen
```bash
# Server-Status
curl http://localhost:3000/health
# Datenbank-Status
psql -d ninjaserver -c "SELECT NOW();"
# Speicher-Status
free -h
df -h
# Prozess-Status
ps aux | grep node
```
### Backup wiederherstellen
```bash
# Vollständiges Backup
psql -d ninjaserver < backup.sql
# Nur Daten
psql -d ninjaserver < data_backup.sql
# Nur Schema
psql -d ninjaserver < schema_backup.sql
```
### System neu starten
```bash
# Mit npm
npm restart
# Mit pm2
pm2 restart ninjaserver
# Mit systemd
sudo systemctl restart ninjaserver
# Mit Docker
docker restart ninjaserver
```
## 📞 Support kontaktieren
Wenn diese Lösungen nicht helfen:
1. **Logs sammeln:**
```bash
# Alle relevanten Logs
tail -n 100 logs/server.log > error_log.txt
tail -n 100 /var/log/ninjaserver_achievements.log >> error_log.txt
```
2. **System-Info sammeln:**
```bash
# System-Informationen
uname -a > system_info.txt
node --version >> system_info.txt
npm --version >> system_info.txt
psql --version >> system_info.txt
```
3. **Problem beschreiben:**
- Was passiert ist
- Wann es passiert ist
- Welche Schritte Sie unternommen haben
- Fehlermeldungen
4. **Support kontaktieren:**
- E-Mail: support@ninjaparkour.de
- Issue-System verwenden
- Logs und System-Info anhängen
---
**Hinweis:** Diese Anleitung wird regelmäßig aktualisiert. Bei neuen Problemen wenden Sie sich an den Support.