diff --git a/public/js/admin-dashboard.js b/public/js/admin-dashboard.js index b34a1eb..5a70d17 100644 --- a/public/js/admin-dashboard.js +++ b/public/js/admin-dashboard.js @@ -46,7 +46,7 @@ function setupEventListeners() { async function checkAuth() { try { - const response = await fetch('/api/check-session'); + const response = await fetch('/api/v1/web/check-session'); const result = await response.json(); if (!result.success) { @@ -74,7 +74,7 @@ async function checkAuth() { async function logout() { try { - await fetch('/api/logout', { method: 'POST' }); + await fetch('/api/v1/public/logout', { method: 'POST' }); window.location.href = '/adminlogin.html'; } catch (error) { console.error('Logout failed:', error); @@ -84,7 +84,7 @@ async function logout() { async function loadStatistics() { try { - const response = await fetch('/api/admin-stats'); + const response = await fetch('/api/v1/admin/stats'); const stats = await response.json(); if (stats.success) { @@ -100,7 +100,7 @@ async function loadStatistics() { async function loadPageStatistics() { try { - const response = await fetch('/api/admin-page-stats'); + const response = await fetch('/api/v1/admin/page-stats'); const result = await response.json(); if (result.success) { @@ -202,7 +202,7 @@ async function loadUsers() { async function loadPlayers() { try { - const response = await fetch('/api/admin-players'); + const response = await fetch('/api/v1/admin/players'); const result = await response.json(); if (result.success) { @@ -219,7 +219,7 @@ async function loadPlayers() { async function loadRuns() { try { - const response = await fetch('/api/admin-runs'); + const response = await fetch('/api/v1/admin/runs'); const result = await response.json(); if (result.success) { @@ -236,7 +236,7 @@ async function loadRuns() { async function loadLocations() { try { - const response = await fetch('/api/admin-locations'); + const response = await fetch('/api/v1/admin/locations'); const result = await response.json(); if (result.success) { @@ -253,7 +253,7 @@ async function loadLocations() { async function loadAdminUsers() { try { - const response = await fetch('/api/admin-adminusers'); + const response = await fetch('/api/v1/admin/adminusers'); const result = await response.json(); if (result.success) { @@ -537,22 +537,22 @@ async function handleAddSubmit(e) { switch(currentDataType) { case 'players': - endpoint = isEdit ? `/api/admin-players/${editId}` : '/api/admin-players'; + endpoint = isEdit ? `/api/v1/admin/players/${editId}` : '/api/v1/admin/players'; successMessage = isEdit ? 'Spieler erfolgreich aktualisiert' : 'Spieler erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; case 'locations': - endpoint = isEdit ? `/api/admin-locations/${editId}` : '/api/admin-locations'; + endpoint = isEdit ? `/api/v1/admin/locations/${editId}` : '/api/v1/admin/locations'; successMessage = isEdit ? 'Standort erfolgreich aktualisiert' : 'Standort erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; case 'adminusers': - endpoint = isEdit ? `/api/admin-adminusers/${editId}` : '/api/admin-adminusers'; + endpoint = isEdit ? `/api/v1/admin/adminusers/${editId}` : '/api/v1/admin/adminusers'; successMessage = isEdit ? 'Admin-Benutzer erfolgreich aktualisiert' : 'Admin-Benutzer erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; case 'runs': - endpoint = isEdit ? `/api/admin-runs/${editId}` : '/api/admin-runs'; + endpoint = isEdit ? `/api/v1/admin/runs/${editId}` : '/api/v1/admin/runs'; successMessage = isEdit ? 'Lauf erfolgreich aktualisiert' : 'Lauf erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; @@ -697,7 +697,7 @@ async function editRun(id) { async function deletePlayer(id) { if (await confirmDelete(`Spieler mit ID ${id} löschen?`)) { try { - const response = await fetch(`/api/admin-players/${id}`, { method: 'DELETE' }); + const response = await fetch(`/api/v1/admin/players/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { @@ -716,7 +716,7 @@ async function deletePlayer(id) { async function deleteRun(id) { if (await confirmDelete(`Lauf mit ID ${id} löschen?`)) { try { - const response = await fetch(`/api/admin-runs/${id}`, { method: 'DELETE' }); + const response = await fetch(`/api/v1/admin/runs/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { @@ -735,7 +735,7 @@ async function deleteRun(id) { async function deleteLocation(id) { if (await confirmDelete(`Standort mit ID ${id} löschen?`)) { try { - const response = await fetch(`/api/admin-locations/${id}`, { method: 'DELETE' }); + const response = await fetch(`/api/v1/admin/locations/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { @@ -754,7 +754,7 @@ async function deleteLocation(id) { async function deleteAdminUser(id) { if (await confirmDelete(`Admin-Benutzer mit ID ${id} löschen?`)) { try { - const response = await fetch(`/api/admin-adminusers/${id}`, { method: 'DELETE' }); + const response = await fetch(`/api/v1/admin/adminusers/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { diff --git a/public/js/adminlogin.js b/public/js/adminlogin.js index 76785ee..cbdbe31 100644 --- a/public/js/adminlogin.js +++ b/public/js/adminlogin.js @@ -42,7 +42,7 @@ async function handleLogin(event) { setLoading(true); try { - const response = await fetch('/api/login', { + const response = await fetch('/api/v1/public/login', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/public/js/dashboard.js b/public/js/dashboard.js index da9c3d2..3b87f9a 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -314,7 +314,7 @@ async function linkUserByRfidUid(rfidUid) { try { // First, find the player with this RFID UID - const response = await fetch('/api/link-by-rfid', { + const response = await fetch('/api/v1/public/link-by-rfid', { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -362,7 +362,7 @@ async function loadUserTimesSection(playerData) { showTimesLoading(); try { - const response = await fetch(`/api/user-times/${currentUser.id}`); + const response = await fetch(`/api/v1/public/user-times/${currentUser.id}`); const times = await response.json(); // Update stats diff --git a/public/js/generator.js b/public/js/generator.js index 810e1ee..73326e9 100644 --- a/public/js/generator.js +++ b/public/js/generator.js @@ -114,7 +114,7 @@ function toggleTokenFields() { const standorte = document.getElementById("standorte").value.trim(); try { - const response = await fetch('/api/web/save-token', { + const response = await fetch('/api/v1/web/save-token', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -477,7 +477,7 @@ function toggleTokenFields() { saveBtn.disabled = true; // Web-authenticated API für Standortverwaltung aufrufen - const response = await fetch('/api/web/create-location', { + const response = await fetch('/api/v1/web/create-location', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -524,7 +524,7 @@ function toggleTokenFields() { // Logout-Funktion async function logout() { try { - const response = await fetch('/api/logout', { + const response = await fetch('/api/v1/public/logout', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/public/js/leaderboard.js b/public/js/leaderboard.js index 4a606dc..caa31cb 100644 --- a/public/js/leaderboard.js +++ b/public/js/leaderboard.js @@ -106,7 +106,7 @@ async function logout() { // Load locations from database async function loadLocations() { try { - const response = await fetch('/public-api/locations'); + const response = await fetch('/api/v1/public/locations'); if (!response.ok) { throw new Error('Failed to fetch locations'); } @@ -330,7 +330,7 @@ async function loadData() { } // Fetch times with player and location data from local database - const response = await fetch(`/public-api/times-with-details?${params.toString()}`); + const response = await fetch(`/api/v1/public/times-with-details?${params.toString()}`); if (!response.ok) { throw new Error('Failed to fetch times'); } diff --git a/public/js/page-tracking.js b/public/js/page-tracking.js index 327d95a..0d6e3d4 100644 --- a/public/js/page-tracking.js +++ b/public/js/page-tracking.js @@ -5,7 +5,7 @@ function trackPageView(pageName) { const referer = document.referrer || ''; // Send tracking data to server - fetch('/api/track-page-view', { + fetch('/api/v1/public/track-page-view', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/routes/api.js b/routes/api.js index 625d694..6235f5c 100644 --- a/routes/api.js +++ b/routes/api.js @@ -139,8 +139,12 @@ async function requireApiKey(req, res, next) { } } +// ============================================================================ +// PUBLIC API ROUTES (/api/v1/public/) +// ============================================================================ + // Login-Route (bleibt für Web-Interface) -router.post('/login', async (req, res) => { +router.post('/v1/public/login', async (req, res) => { const { username, password } = req.body; if (!username || !password) { @@ -209,7 +213,7 @@ router.post('/login', async (req, res) => { }); // Logout-Route (bleibt für Web-Interface) -router.post('/logout', (req, res) => { +router.post('/v1/public/logout', (req, res) => { req.session.destroy((err) => { if (err) { return res.status(500).json({ @@ -224,8 +228,12 @@ router.post('/logout', (req, res) => { }); }); +// ============================================================================ +// PRIVATE API ROUTES (/api/v1/private/) +// ============================================================================ + // API Endpunkt zum Speichern der Tokens (geschützt mit API-Key) -router.post('/save-token', requireApiKey, async (req, res) => { +router.post('/v1/private/save-token', requireApiKey, async (req, res) => { const { token, description, standorte } = req.body; // Validierung @@ -298,7 +306,7 @@ router.post('/save-token', requireApiKey, async (req, res) => { }); // API Endpunkt zum Abrufen aller Tokens (geschützt mit API-Key) -router.get('/tokens', requireApiKey, async (req, res) => { +router.get('/v1/private/tokens', requireApiKey, async (req, res) => { try { const result = await pool.query( `SELECT id, token, description, standorte, created_at, expires_at, is_active @@ -321,7 +329,7 @@ router.get('/tokens', requireApiKey, async (req, res) => { }); // API Endpunkt zum Validieren eines Tokens (geschützt mit API-Key) -router.post('/validate-token', requireApiKey, async (req, res) => { +router.post('/v1/private/validate-token', requireApiKey, async (req, res) => { const { token } = req.body; if (!token) { @@ -378,7 +386,7 @@ router.post('/validate-token', requireApiKey, async (req, res) => { }); // Neue API-Route für Standortverwaltung (geschützt mit API-Key) -router.post('/create-location', requireApiKey, async (req, res) => { +router.post('/v1/private/create-location', requireApiKey, async (req, res) => { const { name, lat, lon } = req.body; // Validierung @@ -449,7 +457,7 @@ router.post('/create-location', requireApiKey, async (req, res) => { }); // API Endpunkt zum Abrufen aller Standorte (geschützt mit API-Key) -router.get('/locations', requireApiKey, async (req, res) => { +router.get('/v1/private/locations', requireApiKey, async (req, res) => { try { const result = await pool.query( `SELECT id, name, latitude, longitude, time_threshold, created_at @@ -472,7 +480,7 @@ router.get('/locations', requireApiKey, async (req, res) => { }); // API Endpunkt zum Aktualisieren des Zeit-Schwellenwerts für einen Standort -router.put('/locations/:id/threshold', requireApiKey, async (req, res) => { +router.put('/v1/private/locations/:id/threshold', requireApiKey, async (req, res) => { const { id } = req.params; const { time_threshold } = req.body; @@ -525,8 +533,12 @@ router.put('/locations/:id/threshold', requireApiKey, async (req, res) => { } }); +// ============================================================================ +// WEB-AUTHENTICATED ROUTES (/api/v1/web/) +// ============================================================================ + // Neue Route zum Generieren eines API-Keys (nur für authentifizierte Web-Benutzer) -router.post('/generate-api-key', async (req, res) => { +router.post('/v1/web/generate-api-key', async (req, res) => { // Diese Route bleibt für das Web-Interface verfügbar // Hier können Sie einen neuen API-Key generieren try { @@ -570,7 +582,7 @@ router.post('/generate-api-key', async (req, res) => { // These endpoints use session authentication instead of API key authentication // Web-authenticated endpoint for creating locations -router.post('/web/create-location', async (req, res) => { +router.post('/v1/web/create-location', async (req, res) => { // Check if user is authenticated via web session if (!req.session || !req.session.userId) { return res.status(401).json({ @@ -649,7 +661,7 @@ router.post('/web/create-location', async (req, res) => { }); // Web-authenticated endpoint for saving tokens -router.post('/web/save-token', async (req, res) => { +router.post('/v1/web/save-token', async (req, res) => { // Check if user is authenticated via web session if (!req.session || !req.session.userId) { return res.status(401).json({ @@ -713,7 +725,7 @@ router.post('/web/save-token', async (req, res) => { }); // API Endpunkt für GetLocations (geschützt mit API-Key) -router.get('/get-locations', requireApiKey, async (req, res) => { +router.get('/v1/private/get-locations', requireApiKey, async (req, res) => { try { const result = await pool.query('SELECT * FROM "GetLocations"'); @@ -732,7 +744,7 @@ router.get('/get-locations', requireApiKey, async (req, res) => { }); // API Entpunkt zum erstellen eines neuen Spielers -router.post('/create-player', requireApiKey, async (req, res) => { +router.post('/v1/private/create-player', requireApiKey, async (req, res) => { const { firstname, lastname, birthdate, rfiduid } = req.body; // Validierung @@ -790,7 +802,7 @@ router.post('/create-player', requireApiKey, async (req, res) => { }); // API Endpunkt zum erstellen einer neuen Zeit mit RFID UID und Location Name -router.post('/create-time', requireApiKey, async (req, res) => { +router.post('/v1/private/create-time', requireApiKey, async (req, res) => { const { rfiduid, location_name, recorded_time } = req.body; // Validierung @@ -928,7 +940,7 @@ router.post('/create-time', requireApiKey, async (req, res) => { }); // API Endpunkt zum Überprüfen eines Benutzers anhand der RFID UID -router.post('/users/find', requireApiKey, async (req, res) => { +router.post('/v1/private/users/find', requireApiKey, async (req, res) => { const { uid } = req.body; // Validierung @@ -1005,7 +1017,7 @@ router.post('/users/find', requireApiKey, async (req, res) => { // ============================================================================ // Get all players for RFID linking (no auth required for dashboard) -router.get('/players', async (req, res) => { +router.get('/v1/public/players', async (req, res) => { try { const result = await pool.query( `SELECT id, firstname, lastname, birthdate, rfiduid, created_at @@ -1025,7 +1037,7 @@ router.get('/players', async (req, res) => { }); // Create new player with optional Supabase user linking (no auth required for dashboard) -router.post('/players', async (req, res) => { +router.post('/v1/public/players', async (req, res) => { const { firstname, lastname, birthdate, rfiduid, supabase_user_id } = req.body; // Validierung @@ -1099,7 +1111,7 @@ router.post('/players', async (req, res) => { }); // Link existing player to Supabase user (no auth required for dashboard) -router.post('/link-player', async (req, res) => { +router.post('/v1/public/link-player', async (req, res) => { const { player_id, supabase_user_id } = req.body; // Validierung @@ -1159,7 +1171,7 @@ router.post('/link-player', async (req, res) => { }); // Get user times by Supabase user ID (no auth required for dashboard) -router.get('/user-times/:supabase_user_id', async (req, res) => { +router.get('/v1/public/user-times/:supabase_user_id', async (req, res) => { const { supabase_user_id } = req.params; try { @@ -1202,7 +1214,7 @@ router.get('/user-times/:supabase_user_id', async (req, res) => { }); // Get player info by Supabase user ID (no auth required for dashboard) -router.get('/user-player/:supabase_user_id', async (req, res) => { +router.get('/v1/public/user-player/:supabase_user_id', async (req, res) => { const { supabase_user_id } = req.params; try { @@ -1233,7 +1245,7 @@ router.get('/user-player/:supabase_user_id', async (req, res) => { }); // Link user by RFID UID (scanned from QR code) -router.post('/link-by-rfid', async (req, res) => { +router.post('/v1/public/link-by-rfid', async (req, res) => { const { rfiduid, supabase_user_id } = req.body; // Validierung @@ -1336,7 +1348,7 @@ function requireLevel2Access(req, res, next) { } // Session-Check für Dashboard -router.get('/check-session', (req, res) => { +router.get('/v1/web/check-session', (req, res) => { if (req.session.userId) { res.json({ success: true, @@ -1354,8 +1366,12 @@ router.get('/check-session', (req, res) => { } }); +// ============================================================================ +// ADMIN DASHBOARD ROUTES (/api/v1/admin/) +// ============================================================================ + // Admin Statistiken -router.get('/admin-stats', requireAdminAuth, async (req, res) => { +router.get('/v1/admin/stats', requireAdminAuth, async (req, res) => { try { const playersResult = await pool.query('SELECT COUNT(*) FROM players'); const runsResult = await pool.query('SELECT COUNT(*) FROM times'); @@ -1381,7 +1397,7 @@ router.get('/admin-stats', requireAdminAuth, async (req, res) => { }); // Admin Spieler-Verwaltung -router.get('/admin-players', requireAdminAuth, async (req, res) => { +router.get('/v1/admin/players', requireAdminAuth, async (req, res) => { try { const result = await pool.query(` SELECT @@ -1405,7 +1421,7 @@ router.get('/admin-players', requireAdminAuth, async (req, res) => { } }); -router.delete('/admin-players/:id', requireAdminAuth, async (req, res) => { +router.delete('/v1/admin/players/:id', requireAdminAuth, async (req, res) => { const playerId = req.params.id; try { @@ -1430,7 +1446,7 @@ router.delete('/admin-players/:id', requireAdminAuth, async (req, res) => { }); // Admin Läufe-Verwaltung -router.get('/admin-runs', requireAdminAuth, async (req, res) => { +router.get('/v1/admin/runs', requireAdminAuth, async (req, res) => { try { const result = await pool.query(` SELECT @@ -1463,7 +1479,7 @@ router.get('/admin-runs', requireAdminAuth, async (req, res) => { }); // GET einzelner Lauf -router.get('/admin-runs/:id', requireAdminAuth, async (req, res) => { +router.get('/v1/admin/runs/:id', requireAdminAuth, async (req, res) => { try { const { id } = req.params; @@ -1503,7 +1519,7 @@ router.get('/admin-runs/:id', requireAdminAuth, async (req, res) => { } }); -router.delete('/admin-runs/:id', requireAdminAuth, async (req, res) => { +router.delete('/v1/admin/runs/:id', requireAdminAuth, async (req, res) => { const runId = req.params.id; try { @@ -1524,7 +1540,7 @@ router.delete('/admin-runs/:id', requireAdminAuth, async (req, res) => { }); // Admin Standort-Verwaltung -router.get('/admin-locations', requireAdminAuth, async (req, res) => { +router.get('/v1/admin/locations', requireAdminAuth, async (req, res) => { try { const result = await pool.query('SELECT * FROM locations ORDER BY name'); @@ -1541,7 +1557,7 @@ router.get('/admin-locations', requireAdminAuth, async (req, res) => { } }); -router.delete('/admin-locations/:id', requireAdminAuth, async (req, res) => { +router.delete('/v1/admin/locations/:id', requireAdminAuth, async (req, res) => { const locationId = req.params.id; try { @@ -1573,7 +1589,7 @@ router.delete('/admin-locations/:id', requireAdminAuth, async (req, res) => { }); // Admin-Benutzer-Verwaltung -router.get('/admin-adminusers', requireAdminAuth, async (req, res) => { +router.get('/v1/admin/adminusers', requireAdminAuth, async (req, res) => { try { const result = await pool.query(` SELECT id, username, access_level, is_active, created_at, last_login @@ -1594,7 +1610,7 @@ router.get('/admin-adminusers', requireAdminAuth, async (req, res) => { } }); -router.delete('/admin-adminusers/:id', requireAdminAuth, async (req, res) => { +router.delete('/v1/admin/adminusers/:id', requireAdminAuth, async (req, res) => { const userId = req.params.id; // Verhindern, dass sich selbst löscht @@ -1627,7 +1643,7 @@ router.delete('/admin-adminusers/:id', requireAdminAuth, async (req, res) => { // ============================================================================ // Track page view -router.post('/track-page-view', async (req, res) => { +router.post('/v1/public/track-page-view', async (req, res) => { try { const { page, userAgent, ipAddress, referer } = req.body; @@ -1646,8 +1662,145 @@ router.post('/track-page-view', async (req, res) => { } }); +// ============================================================================ +// LEADERBOARD ROUTES (moved from public.js) +// ============================================================================ + +// Public endpoint für Standorte (keine Authentifizierung erforderlich) +router.get('/v1/public/locations', async (req, res) => { + try { + const result = await pool.query('SELECT * FROM "GetLocations"'); + + res.json({ + success: true, + data: result.rows + }); + + } catch (error) { + console.error('Fehler beim Abrufen der getlocations:', error); + res.status(500).json({ + success: false, + message: 'Fehler beim Abrufen der Standorte' + }); + } +}); + +// Public route to get times for location with parameter +router.get('/v1/public/times', async (req, res) => { + const { location } = req.query; + + try { + // First, let's check if the view exists and has data + const viewCheck = await pool.query('SELECT COUNT(*) as count FROM "GetTimesWithPlayerAndLocation"'); + + // Check what location names are available + const availableLocations = await pool.query('SELECT DISTINCT location_name FROM "GetTimesWithPlayerAndLocation"'); + + // Now search for the specific location + const result = await pool.query('SELECT * FROM "GetTimesWithPlayerAndLocation" WHERE location_name = $1', [location]); + + res.json({ + success: true, + data: result.rows, + debug: { + searchedFor: location, + totalRecords: viewCheck.rows[0].count, + availableLocations: availableLocations.rows.map(r => r.location_name), + foundRecords: result.rows.length + } + }); + + } catch (error) { + console.error('❌ Fehler beim Abrufen der Zeiten:', error); + res.status(500).json({ + success: false, + message: 'Fehler beim Abrufen der Zeiten', + error: error.message + }); + } +}); + +// Public route to get all times with player and location details for leaderboard +router.get('/v1/public/times-with-details', async (req, res) => { + try { + const { location, period } = req.query; + + // Build WHERE clause for location filter + let locationFilter = ''; + if (location && location !== 'all') { + locationFilter = `AND l.name ILIKE '%${location}%'`; + } + + // Build WHERE clause for date filter using PostgreSQL timezone functions + let dateFilter = ''; + if (period === 'today') { + // Today in local timezone (UTC+2) + dateFilter = `AND DATE(t.created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Berlin') = CURRENT_DATE`; + } else if (period === 'week') { + // This week starting from Monday in local timezone + dateFilter = `AND DATE(t.created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Berlin') >= DATE_TRUNC('week', CURRENT_DATE)`; + } else if (period === 'month') { + // This month starting from 1st in local timezone + dateFilter = `AND DATE(t.created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Berlin') >= DATE_TRUNC('month', CURRENT_DATE)`; + } + + // Get all times with player and location details, ordered by time (fastest first) + const result = await pool.query(` + SELECT + t.id, + EXTRACT(EPOCH FROM t.recorded_time) as recorded_time_seconds, + t.created_at, + json_build_object( + 'id', p.id, + 'firstname', p.firstname, + 'lastname', p.lastname, + 'rfiduid', p.rfiduid + ) as player, + json_build_object( + 'id', l.id, + 'name', l.name, + 'latitude', l.latitude, + 'longitude', l.longitude + ) as location + FROM times t + LEFT JOIN players p ON t.player_id = p.id + LEFT JOIN locations l ON t.location_id = l.id + WHERE 1=1 ${locationFilter} ${dateFilter} + ORDER BY t.recorded_time ASC + LIMIT 50 + `); + + // Convert seconds to minutes:seconds.milliseconds format + const formattedResults = result.rows.map(row => { + const totalSeconds = parseFloat(row.recorded_time_seconds); + const minutes = Math.floor(totalSeconds / 60); + const seconds = Math.floor(totalSeconds % 60); + const milliseconds = Math.floor((totalSeconds % 1) * 1000); + + return { + ...row, + recorded_time: { + minutes: minutes, + seconds: seconds, + milliseconds: milliseconds + } + }; + }); + + res.json(formattedResults); + + } catch (error) { + console.error('❌ Fehler beim Abrufen der Zeiten mit Details:', error); + res.status(500).json({ + success: false, + message: 'Fehler beim Abrufen der Zeiten mit Details', + error: error.message + }); + } +}); + // Get page statistics -router.get('/admin-page-stats', requireAdminAuth, async (req, res) => { +router.get('/v1/admin/page-stats', requireAdminAuth, async (req, res) => { try { // Page views for today, this week, this month const today = new Date(); @@ -1729,7 +1882,7 @@ router.get('/admin-page-stats', requireAdminAuth, async (req, res) => { // ============================================================================ // Admin Spieler - POST (Hinzufügen) -router.post('/admin-players', requireAdminAuth, async (req, res) => { +router.post('/v1/admin/players', requireAdminAuth, async (req, res) => { try { const { full_name, rfiduid, supabase_user_id } = req.body; @@ -1760,7 +1913,7 @@ router.post('/admin-players', requireAdminAuth, async (req, res) => { }); // Admin Spieler - PUT (Bearbeiten) -router.put('/admin-players/:id', requireAdminAuth, async (req, res) => { +router.put('/v1/admin/players/:id', requireAdminAuth, async (req, res) => { try { const playerId = req.params.id; const { full_name, rfiduid, supabase_user_id } = req.body; @@ -1797,7 +1950,7 @@ router.put('/admin-players/:id', requireAdminAuth, async (req, res) => { }); // Admin Standorte - POST (Hinzufügen) -router.post('/admin-locations', requireAdminAuth, async (req, res) => { +router.post('/v1/admin/locations', requireAdminAuth, async (req, res) => { try { const { name, latitude, longitude, time_threshold } = req.body; @@ -1823,7 +1976,7 @@ router.post('/admin-locations', requireAdminAuth, async (req, res) => { }); // Admin Standorte - PUT (Bearbeiten) -router.put('/admin-locations/:id', requireAdminAuth, async (req, res) => { +router.put('/v1/admin/locations/:id', requireAdminAuth, async (req, res) => { try { const locationId = req.params.id; const { name, latitude, longitude, time_threshold } = req.body; @@ -1855,7 +2008,7 @@ router.put('/admin-locations/:id', requireAdminAuth, async (req, res) => { }); // Admin Läufe - POST (Hinzufügen) -router.post('/admin-runs', requireAdminAuth, async (req, res) => { +router.post('/v1/admin/runs', requireAdminAuth, async (req, res) => { try { const { player_id, location_id, time_seconds } = req.body; @@ -1884,7 +2037,7 @@ router.post('/admin-runs', requireAdminAuth, async (req, res) => { }); // Admin Läufe - PUT (Bearbeiten) -router.put('/admin-runs/:id', requireAdminAuth, async (req, res) => { +router.put('/v1/admin/runs/:id', requireAdminAuth, async (req, res) => { try { const runId = req.params.id; const { player_id, location_id, time_seconds } = req.body; @@ -1919,7 +2072,7 @@ router.put('/admin-runs/:id', requireAdminAuth, async (req, res) => { }); // Admin-Benutzer - POST (Hinzufügen) -router.post('/admin-adminusers', requireAdminAuth, async (req, res) => { +router.post('/v1/admin/adminusers', requireAdminAuth, async (req, res) => { try { const { username, password, access_level } = req.body; @@ -1956,7 +2109,7 @@ router.post('/admin-adminusers', requireAdminAuth, async (req, res) => { }); // Admin-Benutzer - PUT (Bearbeiten) -router.put('/admin-adminusers/:id', requireAdminAuth, async (req, res) => { +router.put('/v1/admin/adminusers/:id', requireAdminAuth, async (req, res) => { try { const userId = req.params.id; const { username, password, access_level } = req.body; diff --git a/server.js b/server.js index f652392..3e85b44 100644 --- a/server.js +++ b/server.js @@ -25,7 +25,6 @@ require('dotenv').config(); // Route Imports const { router: apiRoutes, requireApiKey } = require('./routes/api'); -const publicRoutes = require('./routes/public'); // ============================================================================ // SERVER CONFIGURATION @@ -83,12 +82,11 @@ function requireWebAuth(req, res, next) { // ROUTE SETUP // ============================================================================ -// Public API Routes (no authentication required) -// Diese Routen sind für das Frontend-Leaderboard gedacht -app.use('/public-api', publicRoutes); - -// Private API Routes (API-Key authentication required) -// Diese Routen sind für die Timer-Geräte und Admin-Interface +// Unified API Routes (all under /api/v1/) +// - /api/v1/public/* - Public routes (no authentication) +// - /api/v1/private/* - API-Key protected routes +// - /api/v1/web/* - Session protected routes +// - /api/v1/admin/* - Admin protected routes app.use('/api', apiRoutes); // ============================================================================ @@ -242,8 +240,11 @@ server.listen(port, () => { console.log(`🔐 API-Key Authentifizierung aktiviert`); console.log(`🔌 WebSocket-Server aktiviert`); console.log(`📁 Static files: /public`); - console.log(`🌐 Public API: /public-api`); - console.log(`🔑 Private API: /api`); + console.log(`🌐 Unified API: /api/v1/`); + console.log(` 📖 Public: /api/v1/public/`); + console.log(` 🔒 Private: /api/v1/private/`); + console.log(` 🔐 Web: /api/v1/web/`); + console.log(` 👑 Admin: /api/v1/admin/`); }); // ============================================================================