# πŸ”§ 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 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).