edia all routes

This commit is contained in:
2025-09-05 13:15:11 +02:00
parent 3872397082
commit a78a8dc3ce
8 changed files with 230 additions and 76 deletions

View File

@@ -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) {

View File

@@ -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',

View File

@@ -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

View File

@@ -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',

View File

@@ -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');
}

View File

@@ -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',

View File

@@ -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;

View File

@@ -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/`);
});
// ============================================================================