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

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