AdminDashboard
This commit is contained in:
278
routes/api.js
278
routes/api.js
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user