AdminDashboard

This commit is contained in:
2025-09-03 17:13:18 +02:00
parent e4f6218066
commit 1a377517a6
4 changed files with 1198 additions and 4 deletions

View File

@@ -152,7 +152,7 @@ router.post('/login', async (req, res) => {
try {
const result = await pool.query(
'SELECT id, username, password_hash FROM adminusers WHERE username = $1 AND is_active = true',
'SELECT id, username, password_hash, access_level FROM adminusers WHERE username = $1 AND is_active = true',
[username]
);
@@ -176,6 +176,7 @@ router.post('/login', async (req, res) => {
// Session setzen
req.session.userId = user.id;
req.session.username = user.username;
req.session.accessLevel = user.access_level;
// Session speichern
req.session.save((err) => {
@@ -192,7 +193,8 @@ router.post('/login', async (req, res) => {
message: 'Erfolgreich angemeldet',
user: {
id: user.id,
username: user.username
username: user.username,
access_level: user.access_level
}
});
});
@@ -1307,4 +1309,276 @@ router.post('/link-by-rfid', async (req, res) => {
}
});
// ============================================================================
// ADMIN DASHBOARD ROUTES
// ============================================================================
// Middleware für Admin-Authentifizierung
function requireAdminAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({
success: false,
message: 'Authentifizierung erforderlich'
});
}
next();
}
// Middleware für Level 2 Zugriff
function requireLevel2Access(req, res, next) {
if (!req.session.userId || req.session.accessLevel < 2) {
return res.status(403).json({
success: false,
message: 'Insufficient access level'
});
}
next();
}
// Session-Check für Dashboard
router.get('/check-session', (req, res) => {
if (req.session.userId) {
res.json({
success: true,
user: {
id: req.session.userId,
username: req.session.username,
access_level: req.session.accessLevel || 1
}
});
} else {
res.status(401).json({
success: false,
message: 'Not authenticated'
});
}
});
// Admin Statistiken
router.get('/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');
const locationsResult = await pool.query('SELECT COUNT(*) FROM locations');
const adminUsersResult = await pool.query('SELECT COUNT(*) FROM adminusers');
res.json({
success: true,
data: {
players: parseInt(playersResult.rows[0].count),
runs: parseInt(runsResult.rows[0].count),
locations: parseInt(locationsResult.rows[0].count),
adminUsers: parseInt(adminUsersResult.rows[0].count)
}
});
} catch (error) {
console.error('Error loading admin stats:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Laden der Statistiken'
});
}
});
// Admin Spieler-Verwaltung
router.get('/admin-players', requireAdminAuth, async (req, res) => {
try {
const result = await pool.query(`
SELECT
p.*,
COALESCE(CONCAT(p.firstname, ' ', p.lastname), p.firstname, p.lastname) as full_name,
CASE WHEN p.supabase_user_id IS NOT NULL THEN true ELSE false END as has_supabase_link
FROM players p
ORDER BY p.created_at DESC
`);
res.json({
success: true,
data: result.rows
});
} catch (error) {
console.error('Error loading players:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Laden der Spieler'
});
}
});
router.delete('/admin-players/:id', requireAdminAuth, async (req, res) => {
const playerId = req.params.id;
try {
// Erst alle zugehörigen Zeiten löschen
await pool.query('DELETE FROM times WHERE player_id = $1', [playerId]);
// Dann den Spieler löschen
const result = await pool.query('DELETE FROM players WHERE id = $1', [playerId]);
if (result.rowCount > 0) {
res.json({ success: true, message: 'Spieler erfolgreich gelöscht' });
} else {
res.status(404).json({ success: false, message: 'Spieler nicht gefunden' });
}
} catch (error) {
console.error('Error deleting player:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Löschen des Spielers'
});
}
});
// Admin Läufe-Verwaltung
router.get('/admin-runs', requireAdminAuth, async (req, res) => {
try {
const result = await pool.query(`
SELECT
t.id,
t.player_id,
t.location_id,
t.recorded_time,
EXTRACT(EPOCH FROM t.recorded_time) as time_seconds,
t.created_at,
COALESCE(CONCAT(p.firstname, ' ', p.lastname), p.firstname, p.lastname) as player_name,
l.name as location_name
FROM times t
LEFT JOIN players p ON t.player_id = p.id
LEFT JOIN locations l ON t.location_id = l.id
ORDER BY t.created_at DESC
LIMIT 1000
`);
res.json({
success: true,
data: result.rows
});
} catch (error) {
console.error('Error loading runs:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Laden der Läufe'
});
}
});
router.delete('/admin-runs/:id', requireAdminAuth, async (req, res) => {
const runId = req.params.id;
try {
const result = await pool.query('DELETE FROM times WHERE id = $1', [runId]);
if (result.rowCount > 0) {
res.json({ success: true, message: 'Lauf erfolgreich gelöscht' });
} else {
res.status(404).json({ success: false, message: 'Lauf nicht gefunden' });
}
} catch (error) {
console.error('Error deleting run:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Löschen des Laufs'
});
}
});
// Admin Standort-Verwaltung
router.get('/admin-locations', requireAdminAuth, async (req, res) => {
try {
const result = await pool.query('SELECT * FROM locations ORDER BY name');
res.json({
success: true,
data: result.rows
});
} catch (error) {
console.error('Error loading locations:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Laden der Standorte'
});
}
});
router.delete('/admin-locations/:id', requireAdminAuth, async (req, res) => {
const locationId = req.params.id;
try {
// Prüfen ob noch Läufe an diesem Standort existieren
const timesResult = await pool.query('SELECT COUNT(*) FROM times WHERE location_id = $1', [locationId]);
const timesCount = parseInt(timesResult.rows[0].count);
if (timesCount > 0) {
return res.status(400).json({
success: false,
message: `Standort kann nicht gelöscht werden. Es existieren noch ${timesCount} Läufe an diesem Standort.`
});
}
const result = await pool.query('DELETE FROM locations WHERE id = $1', [locationId]);
if (result.rowCount > 0) {
res.json({ success: true, message: 'Standort erfolgreich gelöscht' });
} else {
res.status(404).json({ success: false, message: 'Standort nicht gefunden' });
}
} catch (error) {
console.error('Error deleting location:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Löschen des Standorts'
});
}
});
// Admin-Benutzer-Verwaltung
router.get('/admin-adminusers', requireAdminAuth, async (req, res) => {
try {
const result = await pool.query(`
SELECT id, username, access_level, is_active, created_at, last_login
FROM adminusers
ORDER BY created_at DESC
`);
res.json({
success: true,
data: result.rows
});
} catch (error) {
console.error('Error loading admin users:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Laden der Admin-Benutzer'
});
}
});
router.delete('/admin-adminusers/:id', requireAdminAuth, async (req, res) => {
const userId = req.params.id;
// Verhindern, dass sich selbst löscht
if (parseInt(userId) === req.session.userId) {
return res.status(400).json({
success: false,
message: 'Sie können sich nicht selbst löschen'
});
}
try {
const result = await pool.query('DELETE FROM adminusers WHERE id = $1', [userId]);
if (result.rowCount > 0) {
res.json({ success: true, message: 'Admin-Benutzer erfolgreich gelöscht' });
} else {
res.status(404).json({ success: false, message: 'Admin-Benutzer nicht gefunden' });
}
} catch (error) {
console.error('Error deleting admin user:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Löschen des Admin-Benutzers'
});
}
});
module.exports = { router, requireApiKey };