Update
This commit is contained in:
752
wiki/API-Referenz.md
Normal file
752
wiki/API-Referenz.md
Normal 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
479
wiki/Achievement-System.md
Normal 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
261
wiki/Benutzerhandbuch.md
Normal 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
588
wiki/Datenbank.md
Normal 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
642
wiki/Deployment.md
Normal 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
589
wiki/Entwicklerhandbuch.md
Normal 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
380
wiki/FAQ.md
Normal 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
92
wiki/Home.md
Normal 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
220
wiki/Schnellstart.md
Normal 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
621
wiki/Sicherheit.md
Normal 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
687
wiki/Troubleshooting.md
Normal 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.
|
||||
Reference in New Issue
Block a user