This commit is contained in:
2025-09-23 14:13:24 +02:00
commit 58b5e6b074
103 changed files with 44000 additions and 0 deletions

4969
routes/api.js Normal file

File diff suppressed because it is too large Load Diff

176
routes/public.js Normal file
View File

@@ -0,0 +1,176 @@
// routes/public.js
const express = require('express');
const { Pool } = require('pg');
const router = express.Router();
// PostgreSQL Pool mit .env Konfiguration
const pool = new Pool({
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: process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : false
});
// Fehlerbehandlung für Pool
pool.on('error', (err) => {
console.error('PostgreSQL Pool Fehler:', err);
});
// Public endpoint für Standorte (keine Authentifizierung erforderlich)
router.get('/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('/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('/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)
// SECURITY: Only return data needed for leaderboard display
const result = await pool.query(`
SELECT
EXTRACT(EPOCH FROM t.recorded_time) as recorded_time_seconds,
t.created_at,
json_build_object(
'firstname', p.firstname,
'lastname', p.lastname
) as player,
json_build_object(
'name', l.name
) 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
});
}
});
// Public route to get all locations for filter dropdown
router.get('/locations', async (req, res) => {
try {
const result = await pool.query(`
SELECT id, name, latitude, longitude
FROM locations
ORDER BY name ASC
`);
res.json({
success: true,
data: result.rows
});
} catch (error) {
console.error('❌ Fehler beim Abrufen der Standorte:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Abrufen der Standorte',
error: error.message
});
}
});
module.exports = router;