2173 lines
71 KiB
JavaScript
2173 lines
71 KiB
JavaScript
// routes/api.js
|
|
const express = require('express');
|
|
const { Pool } = require('pg');
|
|
const bcrypt = require('bcrypt');
|
|
const { start } = require('repl');
|
|
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);
|
|
});
|
|
|
|
// Helper function to convert time string to seconds
|
|
function convertTimeToSeconds(timeStr) {
|
|
if (!timeStr) return 0;
|
|
|
|
// Handle different time formats
|
|
if (typeof timeStr === 'string') {
|
|
// Handle MM:SS.mmm format
|
|
if (/^\d{1,2}:\d{2}\.\d{3}$/.test(timeStr)) {
|
|
const [minutes, secondsWithMs] = timeStr.split(':');
|
|
const [seconds, milliseconds] = secondsWithMs.split('.');
|
|
return parseInt(minutes) * 60 + parseInt(seconds) + parseInt(milliseconds) / 1000;
|
|
}
|
|
|
|
// Handle HH:MM:SS.mmm format
|
|
if (/^\d{1,2}:\d{2}:\d{2}\.\d{3}$/.test(timeStr)) {
|
|
const [hours, minutes, secondsWithMs] = timeStr.split(':');
|
|
const [seconds, milliseconds] = secondsWithMs.split('.');
|
|
return parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(seconds) + parseInt(milliseconds) / 1000;
|
|
}
|
|
|
|
// Handle HH:MM:SS format
|
|
if (/^\d{1,2}:\d{2}:\d{2}$/.test(timeStr)) {
|
|
const [hours, minutes, seconds] = timeStr.split(':');
|
|
return parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(seconds);
|
|
}
|
|
|
|
// Handle MM:SS format
|
|
if (/^\d{1,2}:\d{2}$/.test(timeStr)) {
|
|
const [minutes, seconds] = timeStr.split(':');
|
|
return parseInt(minutes) * 60 + parseInt(seconds);
|
|
}
|
|
}
|
|
|
|
// If it's already a number (seconds)
|
|
if (typeof timeStr === 'number') {
|
|
return timeStr;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Helper function to convert PostgreSQL interval to seconds
|
|
function convertIntervalToSeconds(interval) {
|
|
if (!interval) return 0;
|
|
|
|
// PostgreSQL interval format: "HH:MM:SS" or "MM:SS" or just seconds
|
|
if (typeof interval === 'string') {
|
|
const parts = interval.split(':');
|
|
if (parts.length === 3) {
|
|
// HH:MM:SS format
|
|
const [hours, minutes, seconds] = parts;
|
|
return parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseFloat(seconds);
|
|
} else if (parts.length === 2) {
|
|
// MM:SS format
|
|
const [minutes, seconds] = parts;
|
|
return parseInt(minutes) * 60 + parseFloat(seconds);
|
|
}
|
|
}
|
|
|
|
// If it's already a number (seconds)
|
|
if (typeof interval === 'number') {
|
|
return interval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Middleware für API-Key Authentifizierung
|
|
async function requireApiKey(req, res, next) {
|
|
const authHeader = req.headers.authorization;
|
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'API-Key erforderlich. Verwenden Sie: Authorization: Bearer YOUR_API_KEY'
|
|
});
|
|
}
|
|
|
|
const apiKey = authHeader.substring(7); // "Bearer " entfernen
|
|
|
|
try {
|
|
// API-Key in der Datenbank validieren
|
|
const result = await pool.query(
|
|
`SELECT id, description, standorte, created_at, expires_at, is_active
|
|
FROM api_tokens
|
|
WHERE token = $1 AND is_active = true`,
|
|
[apiKey]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Ungültiger oder inaktiver API-Key'
|
|
});
|
|
}
|
|
|
|
const tokenData = result.rows[0];
|
|
|
|
// Prüfen ob Token abgelaufen ist
|
|
if (tokenData.expires_at && new Date() > new Date(tokenData.expires_at)) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'API-Key ist abgelaufen'
|
|
});
|
|
}
|
|
|
|
// Token-Daten für weitere Verwendung speichern
|
|
req.apiToken = tokenData;
|
|
next();
|
|
|
|
} catch (error) {
|
|
console.error('Fehler bei API-Key Validierung:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler bei der API-Key Validierung'
|
|
});
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// PUBLIC API ROUTES (/api/v1/public/)
|
|
// ============================================================================
|
|
|
|
// Login-Route (bleibt für Web-Interface)
|
|
router.post('/v1/public/login', async (req, res) => {
|
|
const { username, password } = req.body;
|
|
|
|
if (!username || !password) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Benutzername und Passwort sind erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
const result = await pool.query(
|
|
'SELECT id, username, password_hash, access_level FROM adminusers WHERE username = $1 AND is_active = true',
|
|
[username]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Ungültige Anmeldedaten'
|
|
});
|
|
}
|
|
|
|
const user = result.rows[0];
|
|
const isValidPassword = await bcrypt.compare(password, user.password_hash);
|
|
|
|
if (!isValidPassword) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Ungültige Anmeldedaten'
|
|
});
|
|
}
|
|
|
|
// Session setzen
|
|
req.session.userId = user.id;
|
|
req.session.username = user.username;
|
|
req.session.accessLevel = user.access_level;
|
|
|
|
// Session speichern
|
|
req.session.save((err) => {
|
|
if (err) {
|
|
console.error('Fehler beim Speichern der Session:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Speichern der Session'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Erfolgreich angemeldet',
|
|
user: {
|
|
id: user.id,
|
|
username: user.username,
|
|
access_level: user.access_level
|
|
}
|
|
});
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler bei der Anmeldung:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler bei der Anmeldung'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Logout-Route (bleibt für Web-Interface)
|
|
router.post('/v1/public/logout', (req, res) => {
|
|
req.session.destroy((err) => {
|
|
if (err) {
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Abmelden'
|
|
});
|
|
}
|
|
res.json({
|
|
success: true,
|
|
message: 'Erfolgreich abgemeldet'
|
|
});
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// PRIVATE API ROUTES (/api/v1/private/)
|
|
// ============================================================================
|
|
|
|
// API Endpunkt zum Speichern der Tokens (geschützt mit API-Key)
|
|
router.post('/v1/private/save-token', requireApiKey, async (req, res) => {
|
|
const { token, description, standorte } = req.body;
|
|
|
|
// Validierung
|
|
if (!token) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Token ist erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Prüfen ob Token bereits existiert
|
|
const existingToken = await pool.query(
|
|
'SELECT id FROM api_tokens WHERE token = $1',
|
|
[token]
|
|
);
|
|
|
|
if (existingToken.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'Token existiert bereits in der Datenbank'
|
|
});
|
|
}
|
|
|
|
// Token in Datenbank einfügen
|
|
const result = await pool.query(
|
|
`INSERT INTO api_tokens (token, description, standorte, expires_at)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, created_at`,
|
|
[
|
|
token,
|
|
description,
|
|
standorte,
|
|
new Date(Date.now() + 2 * 365 * 24 * 60 * 60 * 1000) // 2 Jahre gültig
|
|
]
|
|
);
|
|
|
|
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Token erfolgreich als API-Token gespeichert',
|
|
data: {
|
|
id: result.rows[0].id,
|
|
created_at: result.rows[0].created_at
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Speichern des Tokens:', error);
|
|
|
|
// Spezifische Fehlermeldungen
|
|
if (error.code === '23505') { // Duplicate key
|
|
res.status(409).json({
|
|
success: false,
|
|
message: 'Token existiert bereits'
|
|
});
|
|
} else if (error.code === 'ECONNREFUSED') {
|
|
res.status(503).json({
|
|
success: false,
|
|
message: 'Datenbankverbindung fehlgeschlagen'
|
|
});
|
|
} else {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Speichern des Tokens'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// API Endpunkt zum Abrufen aller Tokens (geschützt mit API-Key)
|
|
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
|
|
FROM api_tokens
|
|
ORDER BY created_at DESC`
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.rows
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Abrufen der Tokens:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Abrufen der Tokens'
|
|
});
|
|
}
|
|
});
|
|
|
|
// API Endpunkt zum Validieren eines Tokens (geschützt mit API-Key)
|
|
router.post('/v1/private/validate-token', requireApiKey, async (req, res) => {
|
|
const { token } = req.body;
|
|
|
|
if (!token) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Token ist erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
const result = await pool.query(
|
|
`SELECT id, description, standorte, created_at, expires_at, is_active
|
|
FROM api_tokens
|
|
WHERE token = $1 AND is_active = true`,
|
|
[token]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Ungültiger oder inaktiver Token'
|
|
});
|
|
}
|
|
|
|
const tokenData = result.rows[0];
|
|
|
|
// Prüfen ob Token abgelaufen ist
|
|
if (tokenData.expires_at && new Date() > new Date(tokenData.expires_at)) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Token ist abgelaufen'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Token ist gültig',
|
|
data: {
|
|
id: tokenData.id,
|
|
description: tokenData.description,
|
|
standorte: tokenData.standorte,
|
|
created_at: tokenData.created_at,
|
|
expires_at: tokenData.expires_at
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler bei Token-Validierung:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler bei der Token-Validierung'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Neue API-Route für Standortverwaltung (geschützt mit API-Key)
|
|
router.post('/v1/private/create-location', requireApiKey, async (req, res) => {
|
|
const { name, lat, lon } = req.body;
|
|
|
|
// Validierung
|
|
if (!name || lat === undefined || lon === undefined) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Name, Breitengrad und Längengrad sind erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Prüfen ob Standort bereits existiert
|
|
const existingLocation = await pool.query(
|
|
'SELECT id FROM locations WHERE name = $1',
|
|
[name]
|
|
);
|
|
|
|
if (existingLocation.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'Standort existiert bereits in der Datenbank'
|
|
});
|
|
}
|
|
|
|
// Standort in Datenbank einfügen
|
|
const result = await pool.query(
|
|
`INSERT INTO locations (name, latitude, longitude, created_at)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, created_at`,
|
|
[name, lat, lon, new Date()]
|
|
);
|
|
|
|
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Standort erfolgreich gespeichert',
|
|
data: {
|
|
id: result.rows[0].id,
|
|
name: name,
|
|
latitude: lat,
|
|
longitude: lon,
|
|
created_at: result.rows[0].created_at
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Speichern des Standorts:', error);
|
|
|
|
// Spezifische Fehlermeldungen
|
|
if (error.code === '23505') { // Duplicate key
|
|
res.status(409).json({
|
|
success: false,
|
|
message: 'Standort existiert bereits'
|
|
});
|
|
} else if (error.code === 'ECONNREFUSED') {
|
|
res.status(503).json({
|
|
success: false,
|
|
message: 'Datenbankverbindung fehlgeschlagen'
|
|
});
|
|
} else {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Speichern des Standorts'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// API Endpunkt zum Abrufen aller Standorte (geschützt mit API-Key)
|
|
router.get('/v1/private/locations', requireApiKey, async (req, res) => {
|
|
try {
|
|
const result = await pool.query(
|
|
`SELECT id, name, latitude, longitude, time_threshold, created_at
|
|
FROM locations
|
|
ORDER BY created_at DESC`
|
|
);
|
|
|
|
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'
|
|
});
|
|
}
|
|
});
|
|
|
|
// API Endpunkt zum Aktualisieren des Zeit-Schwellenwerts für einen Standort
|
|
router.put('/v1/private/locations/:id/threshold', requireApiKey, async (req, res) => {
|
|
const { id } = req.params;
|
|
const { time_threshold } = req.body;
|
|
|
|
// Validierung
|
|
if (!time_threshold) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'time_threshold ist erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Prüfen ob Standort existiert
|
|
const existingLocation = await pool.query(
|
|
'SELECT id, name FROM locations WHERE id = $1',
|
|
[id]
|
|
);
|
|
|
|
if (existingLocation.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Standort nicht gefunden'
|
|
});
|
|
}
|
|
|
|
// Schwellenwert aktualisieren
|
|
const result = await pool.query(
|
|
'UPDATE locations SET time_threshold = $1 WHERE id = $2 RETURNING id, name, time_threshold',
|
|
[time_threshold, id]
|
|
);
|
|
|
|
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Schwellenwert erfolgreich aktualisiert',
|
|
data: {
|
|
id: result.rows[0].id,
|
|
name: result.rows[0].name,
|
|
time_threshold: result.rows[0].time_threshold
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Aktualisieren des Schwellenwerts:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Aktualisieren des Schwellenwerts'
|
|
});
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// WEB-AUTHENTICATED ROUTES (/api/v1/web/)
|
|
// ============================================================================
|
|
|
|
// Neue Route zum Generieren eines API-Keys (nur für authentifizierte Web-Benutzer)
|
|
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 {
|
|
// Generiere einen zufälligen API-Key
|
|
const crypto = require('crypto');
|
|
const apiKey = crypto.randomBytes(32).toString('hex');
|
|
|
|
// Speichere den API-Key in der Datenbank
|
|
const result = await pool.query(
|
|
`INSERT INTO api_tokens (token, description, standorte, expires_at)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, created_at`,
|
|
[
|
|
apiKey,
|
|
req.body.description || 'Generierter API-Key',
|
|
req.body.standorte || '',
|
|
new Date(Date.now() + 2 * 365 * 24 * 60 * 60 * 1000) // 2 Jahre gültig
|
|
]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'API-Key erfolgreich generiert',
|
|
data: {
|
|
id: result.rows[0].id,
|
|
apiKey: apiKey,
|
|
created_at: result.rows[0].created_at
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Generieren des API-Keys:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Generieren des API-Keys'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Web-authenticated endpoints for location management (for frontend use)
|
|
// These endpoints use session authentication instead of API key authentication
|
|
|
|
// Web-authenticated endpoint for creating locations
|
|
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({
|
|
success: false,
|
|
message: 'Nicht angemeldet. Bitte melden Sie sich an.'
|
|
});
|
|
}
|
|
|
|
const { name, lat, lon } = req.body;
|
|
|
|
// Validierung
|
|
if (!name || lat === undefined || lon === undefined) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Name, Breitengrad und Längengrad sind erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Prüfen ob Standort bereits existiert
|
|
const existingLocation = await pool.query(
|
|
'SELECT id FROM locations WHERE name = $1',
|
|
[name]
|
|
);
|
|
|
|
if (existingLocation.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'Standort existiert bereits in der Datenbank'
|
|
});
|
|
}
|
|
|
|
// Standort in Datenbank einfügen
|
|
const result = await pool.query(
|
|
`INSERT INTO locations (name, latitude, longitude, created_at)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, created_at`,
|
|
[name, lat, lon, new Date()]
|
|
);
|
|
|
|
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Standort erfolgreich gespeichert',
|
|
data: {
|
|
id: result.rows[0].id,
|
|
name: name,
|
|
latitude: lat,
|
|
longitude: lon,
|
|
created_at: result.rows[0].created_at
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Speichern des Standorts:', error);
|
|
|
|
// Spezifische Fehlermeldungen
|
|
if (error.code === '23505') { // Duplicate key
|
|
res.status(409).json({
|
|
success: false,
|
|
message: 'Standort existiert bereits'
|
|
});
|
|
} else if (error.code === 'ECONNREFUSED') {
|
|
res.status(503).json({
|
|
success: false,
|
|
message: 'Datenbankverbindung fehlgeschlagen'
|
|
});
|
|
} else {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Speichern des Standorts'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// Web-authenticated endpoint for saving tokens
|
|
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({
|
|
success: false,
|
|
message: 'Nicht angemeldet. Bitte melden Sie sich an.'
|
|
});
|
|
}
|
|
|
|
const { token, description, standorte } = req.body;
|
|
|
|
if (!token) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Token ist erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Prüfen ob Token bereits existiert
|
|
const existingToken = await pool.query(
|
|
'SELECT id FROM api_tokens WHERE token = $1',
|
|
[token]
|
|
);
|
|
|
|
if (existingToken.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'Token existiert bereits in der Datenbank'
|
|
});
|
|
}
|
|
|
|
// Token in Datenbank einfügen
|
|
const result = await pool.query(
|
|
`INSERT INTO api_tokens (token, description, standorte, created_at)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, created_at`,
|
|
[token, description || 'API-Token', standorte || '', new Date()]
|
|
);
|
|
|
|
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Token erfolgreich gespeichert',
|
|
data: {
|
|
id: result.rows[0].id,
|
|
token: token,
|
|
description: description || 'API-Token',
|
|
standorte: standorte || '',
|
|
created_at: result.rows[0].created_at
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Speichern des Tokens:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Speichern des Tokens'
|
|
});
|
|
}
|
|
});
|
|
|
|
// API Endpunkt für GetLocations (geschützt mit API-Key)
|
|
router.get('/v1/private/get-locations', requireApiKey, 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 GetLocations'
|
|
});
|
|
}
|
|
});
|
|
|
|
// API Entpunkt zum erstellen eines neuen Spielers
|
|
router.post('/v1/private/create-player', requireApiKey, async (req, res) => {
|
|
const { firstname, lastname, birthdate, rfiduid } = req.body;
|
|
|
|
// Validierung
|
|
if (!firstname || !lastname || !birthdate || !rfiduid) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Firstname, Lastname, Birthdate und RFIDUID sind erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Prüfen ob Spieler bereits existiert
|
|
const existingPlayer = await pool.query(
|
|
'SELECT id FROM players WHERE rfiduid = $1',
|
|
[rfiduid]
|
|
);
|
|
|
|
if (existingPlayer.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'Spieler existiert bereits in der Datenbank'
|
|
});
|
|
}
|
|
|
|
// Spieler in Datenbank einfügen
|
|
const result = await pool.query(
|
|
`INSERT INTO players (firstname, lastname, birthdate, rfiduid, created_at)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
RETURNING id, created_at`,
|
|
[firstname, lastname, birthdate, rfiduid, new Date()]
|
|
);
|
|
|
|
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Spieler erfolgreich gespeichert',
|
|
data: {
|
|
id: result.rows[0].id,
|
|
firstname: firstname,
|
|
lastname: lastname,
|
|
birthdate: birthdate,
|
|
rfiduid: rfiduid,
|
|
created_at: result.rows[0].created_at
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Speichern des Spielers:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Speichern des Spielers'
|
|
});
|
|
}
|
|
});
|
|
|
|
// API Endpunkt zum erstellen einer neuen Zeit mit RFID UID und Location Name
|
|
router.post('/v1/private/create-time', requireApiKey, async (req, res) => {
|
|
const { rfiduid, location_name, recorded_time } = req.body;
|
|
|
|
// Validierung
|
|
if (!rfiduid || !location_name || !recorded_time) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'RFIDUID, Location_name und Recorded_time sind erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Spieler anhand der RFID UID finden
|
|
const playerResult = await pool.query(
|
|
'SELECT id, firstname, lastname FROM players WHERE rfiduid = $1',
|
|
[rfiduid]
|
|
);
|
|
|
|
if (playerResult.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Spieler mit dieser RFID UID nicht gefunden'
|
|
});
|
|
}
|
|
|
|
const player = playerResult.rows[0];
|
|
const player_id = player.id;
|
|
|
|
// Location anhand des Namens finden (inklusive time_threshold)
|
|
const locationResult = await pool.query(
|
|
'SELECT id, name, time_threshold FROM locations WHERE name = $1',
|
|
[location_name]
|
|
);
|
|
|
|
if (locationResult.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: `Standort '${location_name}' nicht gefunden`
|
|
});
|
|
}
|
|
|
|
const location = locationResult.rows[0];
|
|
const location_id = location.id;
|
|
|
|
// Prüfen ob die Zeit über dem Schwellenwert liegt
|
|
if (location.time_threshold) {
|
|
// Konvertiere recorded_time zu Sekunden für Vergleich
|
|
const recordedTimeSeconds = convertTimeToSeconds(recorded_time);
|
|
const thresholdSeconds = convertIntervalToSeconds(location.time_threshold);
|
|
|
|
if (recordedTimeSeconds < thresholdSeconds) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: `Zeit ${recorded_time} liegt unter dem Schwellenwert von ${location.time_threshold} für diesen Standort`,
|
|
data: {
|
|
recorded_time: recorded_time,
|
|
threshold: location.time_threshold,
|
|
location_name: location_name
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Prüfen ob Zeit bereits existiert (optional - kann entfernt werden)
|
|
const existingTime = await pool.query(
|
|
'SELECT id FROM times WHERE player_id = $1 AND location_id = $2 AND recorded_time = $3',
|
|
[player_id, location_id, recorded_time]
|
|
);
|
|
|
|
if (existingTime.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'Zeit existiert bereits in der Datenbank'
|
|
});
|
|
}
|
|
|
|
// Zeit in Datenbank einfügen
|
|
const result = await pool.query(
|
|
`INSERT INTO times (player_id, location_id, recorded_time, created_at)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id, created_at`,
|
|
[player_id, location_id, recorded_time, new Date()]
|
|
);
|
|
|
|
|
|
|
|
// WebSocket-Event senden für Live-Updates
|
|
const io = req.app.get('io');
|
|
if (io) {
|
|
// Hole die aktuelle Platzierung für diese Zeit
|
|
const rankResult = await pool.query(
|
|
`SELECT COUNT(*) + 1 as rank
|
|
FROM times t1
|
|
WHERE t1.location_id = $1
|
|
AND t1.recorded_time < $2`,
|
|
[location_id, recorded_time]
|
|
);
|
|
|
|
const rank = rankResult.rows[0].rank;
|
|
|
|
// Sende WebSocket-Event an alle verbundenen Clients
|
|
io.emit('newTime', {
|
|
id: result.rows[0].id,
|
|
player_name: `${player.firstname} ${player.lastname}`,
|
|
location_name: location.name,
|
|
recorded_time: recorded_time,
|
|
rank: rank,
|
|
created_at: result.rows[0].created_at
|
|
});
|
|
|
|
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Zeit erfolgreich gespeichert',
|
|
data: {
|
|
id: result.rows[0].id,
|
|
player_id: player_id,
|
|
player_name: `${player.firstname} ${player.lastname}`,
|
|
rfiduid: rfiduid,
|
|
location_id: location_id,
|
|
location_name: location.name,
|
|
recorded_time: recorded_time,
|
|
created_at: result.rows[0].created_at
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Speichern der Zeit:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Speichern der Zeit'
|
|
});
|
|
}
|
|
});
|
|
|
|
// API Endpunkt zum Überprüfen eines Benutzers anhand der RFID UID
|
|
router.post('/v1/private/users/find', requireApiKey, async (req, res) => {
|
|
const { uid } = req.body;
|
|
|
|
// Validierung
|
|
if (!uid) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'UID ist erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Spieler anhand der RFID UID finden
|
|
const result = await pool.query(
|
|
'SELECT rfiduid, firstname, lastname, birthdate FROM players WHERE rfiduid = $1',
|
|
[uid]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
// Benutzer nicht gefunden - gibt leere UserData zurück
|
|
return res.json({
|
|
success: true,
|
|
data: {
|
|
uid: "",
|
|
firstname: "",
|
|
lastname: "",
|
|
alter: 0,
|
|
exists: false
|
|
}
|
|
});
|
|
}
|
|
|
|
const player = result.rows[0];
|
|
|
|
// Alter aus dem Geburtsdatum berechnen
|
|
let age = 0;
|
|
if (player.birthdate) {
|
|
const today = new Date();
|
|
const birthDate = new Date(player.birthdate);
|
|
age = today.getFullYear() - birthDate.getFullYear();
|
|
|
|
// Prüfen ob der Geburtstag dieses Jahr noch nicht war
|
|
const monthDiff = today.getMonth() - birthDate.getMonth();
|
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
|
|
age--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
uid: player.rfiduid,
|
|
firstname: player.firstname,
|
|
lastname: player.lastname,
|
|
alter: age,
|
|
exists: true
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Überprüfen des Benutzers:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Überprüfen des Benutzers'
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// ============================================================================
|
|
// RFID LINKING & USER MANAGEMENT ENDPOINTS (No API Key required for dashboard)
|
|
// ============================================================================
|
|
|
|
// Get all players for RFID linking (no auth required for dashboard)
|
|
router.get('/v1/public/players', async (req, res) => {
|
|
try {
|
|
const result = await pool.query(
|
|
`SELECT id, firstname, lastname, birthdate, rfiduid, created_at
|
|
FROM players
|
|
ORDER BY created_at DESC`
|
|
);
|
|
|
|
res.json(result.rows);
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Abrufen der Spieler:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Abrufen der Spieler'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Create new player with optional Supabase user linking (no auth required for dashboard)
|
|
router.post('/v1/public/players', async (req, res) => {
|
|
const { firstname, lastname, birthdate, rfiduid, supabase_user_id } = req.body;
|
|
|
|
// Validierung
|
|
if (!firstname || !lastname || !birthdate) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Firstname, Lastname und Birthdate sind erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Prüfen ob RFID UID bereits existiert (falls angegeben)
|
|
if (rfiduid) {
|
|
const existingRfid = await pool.query(
|
|
'SELECT id FROM players WHERE rfiduid = $1',
|
|
[rfiduid]
|
|
);
|
|
|
|
if (existingRfid.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'RFID UID existiert bereits'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Prüfen ob Supabase User bereits verknüpft ist
|
|
if (supabase_user_id) {
|
|
const existingUser = await pool.query(
|
|
'SELECT id FROM players WHERE supabase_user_id = $1',
|
|
[supabase_user_id]
|
|
);
|
|
|
|
if (existingUser.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'Dieser Benutzer ist bereits mit einem Spieler verknüpft'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Spieler in Datenbank einfügen
|
|
const result = await pool.query(
|
|
`INSERT INTO players (firstname, lastname, birthdate, rfiduid, supabase_user_id, created_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
RETURNING id, created_at`,
|
|
[firstname, lastname, birthdate, rfiduid, supabase_user_id, new Date()]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Spieler erfolgreich erstellt',
|
|
data: {
|
|
id: result.rows[0].id,
|
|
firstname: firstname,
|
|
lastname: lastname,
|
|
birthdate: birthdate,
|
|
rfiduid: rfiduid,
|
|
supabase_user_id: supabase_user_id,
|
|
created_at: result.rows[0].created_at
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Erstellen des Spielers:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Erstellen des Spielers'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Link existing player to Supabase user (no auth required for dashboard)
|
|
router.post('/v1/public/link-player', async (req, res) => {
|
|
const { player_id, supabase_user_id } = req.body;
|
|
|
|
// Validierung
|
|
if (!player_id || !supabase_user_id) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Player ID und Supabase User ID sind erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Prüfen ob Spieler existiert
|
|
const playerExists = await pool.query(
|
|
'SELECT id, firstname, lastname FROM players WHERE id = $1',
|
|
[player_id]
|
|
);
|
|
|
|
if (playerExists.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Spieler nicht gefunden'
|
|
});
|
|
}
|
|
|
|
// Prüfen ob Supabase User bereits verknüpft ist
|
|
const existingLink = await pool.query(
|
|
'SELECT id FROM players WHERE supabase_user_id = $1',
|
|
[supabase_user_id]
|
|
);
|
|
|
|
if (existingLink.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'Dieser Benutzer ist bereits mit einem Spieler verknüpft'
|
|
});
|
|
}
|
|
|
|
// Verknüpfung erstellen
|
|
const result = await pool.query(
|
|
'UPDATE players SET supabase_user_id = $1 WHERE id = $2 RETURNING id, firstname, lastname, rfiduid',
|
|
[supabase_user_id, player_id]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Spieler erfolgreich verknüpft',
|
|
data: result.rows[0]
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Verknüpfen des Spielers:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Verknüpfen des Spielers'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Get user times by Supabase user ID (no auth required for dashboard)
|
|
router.get('/v1/public/user-times/:supabase_user_id', async (req, res) => {
|
|
const { supabase_user_id } = req.params;
|
|
|
|
try {
|
|
// Finde verknüpften Spieler
|
|
const playerResult = await pool.query(
|
|
'SELECT id FROM players WHERE supabase_user_id = $1',
|
|
[supabase_user_id]
|
|
);
|
|
|
|
if (playerResult.rows.length === 0) {
|
|
return res.json([]); // Noch keine Verknüpfung
|
|
}
|
|
|
|
const player_id = playerResult.rows[0].id;
|
|
|
|
// Hole alle Zeiten für diesen Spieler mit Location-Namen
|
|
const timesResult = await pool.query(`
|
|
SELECT
|
|
t.id,
|
|
t.recorded_time,
|
|
t.created_at,
|
|
l.name as location_name,
|
|
l.latitude,
|
|
l.longitude
|
|
FROM times t
|
|
JOIN locations l ON t.location_id = l.id
|
|
WHERE t.player_id = $1
|
|
ORDER BY t.created_at DESC
|
|
`, [player_id]);
|
|
|
|
res.json(timesResult.rows);
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Abrufen der Benutzerzeiten:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Abrufen der Benutzerzeiten'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Get player info by Supabase user ID (no auth required for dashboard)
|
|
router.get('/v1/public/user-player/:supabase_user_id', async (req, res) => {
|
|
const { supabase_user_id } = req.params;
|
|
|
|
try {
|
|
const result = await pool.query(
|
|
'SELECT id, firstname, lastname, birthdate, rfiduid FROM players WHERE supabase_user_id = $1',
|
|
[supabase_user_id]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Kein verknüpfter Spieler gefunden'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.rows[0]
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Abrufen der Spielerdaten:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Abrufen der Spielerdaten'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Link user by RFID UID (scanned from QR code)
|
|
router.post('/v1/public/link-by-rfid', async (req, res) => {
|
|
const { rfiduid, supabase_user_id } = req.body;
|
|
|
|
// Validierung
|
|
if (!rfiduid || !supabase_user_id) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'RFID UID und Supabase User ID sind erforderlich'
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Prüfen ob Spieler mit dieser RFID UID existiert
|
|
const playerResult = await pool.query(
|
|
'SELECT id, firstname, lastname, rfiduid, supabase_user_id FROM players WHERE rfiduid = $1',
|
|
[rfiduid]
|
|
);
|
|
|
|
if (playerResult.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: `Kein Spieler mit RFID UID '${rfiduid}' gefunden. Bitte erstelle zuerst einen Spieler mit dieser RFID UID.`
|
|
});
|
|
}
|
|
|
|
const player = playerResult.rows[0];
|
|
|
|
// Prüfen ob dieser Spieler bereits mit einem anderen Benutzer verknüpft ist
|
|
if (player.supabase_user_id && player.supabase_user_id !== supabase_user_id) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'Dieser Spieler ist bereits mit einem anderen Benutzer verknüpft'
|
|
});
|
|
}
|
|
|
|
// Prüfen ob dieser Benutzer bereits mit einem anderen Spieler verknüpft ist
|
|
const existingLink = await pool.query(
|
|
'SELECT id, firstname, lastname FROM players WHERE supabase_user_id = $1 AND id != $2',
|
|
[supabase_user_id, player.id]
|
|
);
|
|
|
|
if (existingLink.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: `Du bist bereits mit dem Spieler '${existingLink.rows[0].firstname} ${existingLink.rows[0].lastname}' verknüpft. Ein Benutzer kann nur mit einem Spieler verknüpft sein.`
|
|
});
|
|
}
|
|
|
|
// Verknüpfung erstellen (falls noch nicht vorhanden)
|
|
if (!player.supabase_user_id) {
|
|
await pool.query(
|
|
'UPDATE players SET supabase_user_id = $1 WHERE id = $2',
|
|
[supabase_user_id, player.id]
|
|
);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'RFID erfolgreich verknüpft',
|
|
data: {
|
|
id: player.id,
|
|
firstname: player.firstname,
|
|
lastname: player.lastname,
|
|
rfiduid: player.rfiduid
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Verknüpfen per RFID UID:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Interner Serverfehler beim Verknüpfen per RFID UID'
|
|
});
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// 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('/v1/web/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 DASHBOARD ROUTES (/api/v1/admin/)
|
|
// ============================================================================
|
|
|
|
// Admin Statistiken
|
|
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');
|
|
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('/v1/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('/v1/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('/v1/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'
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET einzelner Lauf
|
|
router.get('/v1/admin/runs/:id', requireAdminAuth, async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
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
|
|
WHERE t.id = $1
|
|
`, [id]);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Lauf nicht gefunden'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.rows[0]
|
|
});
|
|
} catch (error) {
|
|
console.error('Error loading run:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Laden des Laufs'
|
|
});
|
|
}
|
|
});
|
|
|
|
router.delete('/v1/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('/v1/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('/v1/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('/v1/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('/v1/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'
|
|
});
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// PAGE VIEWS TRACKING
|
|
// ============================================================================
|
|
|
|
// Track page view
|
|
router.post('/v1/public/track-page-view', async (req, res) => {
|
|
try {
|
|
const { page, userAgent, ipAddress, referer } = req.body;
|
|
|
|
await pool.query(`
|
|
INSERT INTO page_views (page, user_agent, ip_address, referer)
|
|
VALUES ($1, $2, $3, $4)
|
|
`, [page, userAgent, ipAddress, referer]);
|
|
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error('Error tracking page view:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Tracking der Seitenaufrufe'
|
|
});
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// 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('/v1/admin/page-stats', requireAdminAuth, async (req, res) => {
|
|
try {
|
|
// Page views for today, this week, this month
|
|
const today = new Date();
|
|
const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
const startOfWeek = new Date(today);
|
|
startOfWeek.setDate(today.getDate() - today.getDay());
|
|
startOfWeek.setHours(0, 0, 0, 0);
|
|
const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
|
|
// Today's page views
|
|
const todayViews = await pool.query(`
|
|
SELECT page, COUNT(*) as count
|
|
FROM page_views
|
|
WHERE created_at >= $1
|
|
GROUP BY page
|
|
ORDER BY count DESC
|
|
`, [startOfDay]);
|
|
|
|
// This week's page views
|
|
const weekViews = await pool.query(`
|
|
SELECT page, COUNT(*) as count
|
|
FROM page_views
|
|
WHERE created_at >= $1
|
|
GROUP BY page
|
|
ORDER BY count DESC
|
|
`, [startOfWeek]);
|
|
|
|
// This month's page views
|
|
const monthViews = await pool.query(`
|
|
SELECT page, COUNT(*) as count
|
|
FROM page_views
|
|
WHERE created_at >= $1
|
|
GROUP BY page
|
|
ORDER BY count DESC
|
|
`, [startOfMonth]);
|
|
|
|
// Total page views
|
|
const totalViews = await pool.query(`
|
|
SELECT page, COUNT(*) as count
|
|
FROM page_views
|
|
GROUP BY page
|
|
ORDER BY count DESC
|
|
`);
|
|
|
|
// Player/Supabase link statistics
|
|
const linkStats = await pool.query(`
|
|
SELECT
|
|
COUNT(*) as total_players,
|
|
COUNT(CASE WHEN supabase_user_id IS NOT NULL THEN 1 END) as linked_players,
|
|
CAST(
|
|
ROUND(
|
|
(COUNT(CASE WHEN supabase_user_id IS NOT NULL THEN 1 END)::numeric / COUNT(*)) * 100, 2
|
|
) AS DECIMAL(5,2)
|
|
) as link_percentage
|
|
FROM players
|
|
`);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
today: todayViews.rows,
|
|
week: weekViews.rows,
|
|
month: monthViews.rows,
|
|
total: totalViews.rows,
|
|
linkStats: linkStats.rows[0]
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error loading page statistics:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Laden der Seitenstatistiken'
|
|
});
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// POST/PUT ROUTES FÜR CRUD-OPERATIONEN
|
|
// ============================================================================
|
|
|
|
// Admin Spieler - POST (Hinzufügen)
|
|
router.post('/v1/admin/players', requireAdminAuth, async (req, res) => {
|
|
try {
|
|
const { full_name, rfiduid, supabase_user_id } = req.body;
|
|
|
|
// Name in firstname und lastname aufteilen
|
|
const nameParts = full_name ? full_name.trim().split(' ') : [];
|
|
const firstname = nameParts[0] || '';
|
|
const lastname = nameParts.slice(1).join(' ') || '';
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO players (firstname, lastname, rfiduid, supabase_user_id, created_at)
|
|
VALUES ($1, $2, $3, $4, NOW())
|
|
RETURNING *`,
|
|
[firstname, lastname, rfiduid || null, supabase_user_id || null]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Spieler erfolgreich hinzugefügt',
|
|
data: result.rows[0]
|
|
});
|
|
} catch (error) {
|
|
console.error('Error creating player:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Hinzufügen des Spielers'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Admin Spieler - PUT (Bearbeiten)
|
|
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;
|
|
|
|
// Name in firstname und lastname aufteilen
|
|
const nameParts = full_name ? full_name.trim().split(' ') : [];
|
|
const firstname = nameParts[0] || '';
|
|
const lastname = nameParts.slice(1).join(' ') || '';
|
|
|
|
const result = await pool.query(
|
|
`UPDATE players
|
|
SET firstname = $1, lastname = $2, rfiduid = $3, supabase_user_id = $4
|
|
WHERE id = $5
|
|
RETURNING *`,
|
|
[firstname, lastname, rfiduid || null, supabase_user_id || null, playerId]
|
|
);
|
|
|
|
if (result.rowCount > 0) {
|
|
res.json({
|
|
success: true,
|
|
message: 'Spieler erfolgreich aktualisiert',
|
|
data: result.rows[0]
|
|
});
|
|
} else {
|
|
res.status(404).json({ success: false, message: 'Spieler nicht gefunden' });
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating player:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Aktualisieren des Spielers'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Admin Standorte - POST (Hinzufügen)
|
|
router.post('/v1/admin/locations', requireAdminAuth, async (req, res) => {
|
|
try {
|
|
const { name, latitude, longitude, time_threshold } = req.body;
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO locations (name, latitude, longitude, time_threshold, created_at)
|
|
VALUES ($1, $2, $3, $4, NOW())
|
|
RETURNING *`,
|
|
[name, latitude, longitude, time_threshold || null]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Standort erfolgreich hinzugefügt',
|
|
data: result.rows[0]
|
|
});
|
|
} catch (error) {
|
|
console.error('Error creating location:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Hinzufügen des Standorts'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Admin Standorte - PUT (Bearbeiten)
|
|
router.put('/v1/admin/locations/:id', requireAdminAuth, async (req, res) => {
|
|
try {
|
|
const locationId = req.params.id;
|
|
const { name, latitude, longitude, time_threshold } = req.body;
|
|
|
|
const result = await pool.query(
|
|
`UPDATE locations
|
|
SET name = $1, latitude = $2, longitude = $3, time_threshold = $4
|
|
WHERE id = $5
|
|
RETURNING *`,
|
|
[name, latitude, longitude, time_threshold || null, locationId]
|
|
);
|
|
|
|
if (result.rowCount > 0) {
|
|
res.json({
|
|
success: true,
|
|
message: 'Standort erfolgreich aktualisiert',
|
|
data: result.rows[0]
|
|
});
|
|
} else {
|
|
res.status(404).json({ success: false, message: 'Standort nicht gefunden' });
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating location:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Aktualisieren des Standorts'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Admin Läufe - POST (Hinzufügen)
|
|
router.post('/v1/admin/runs', requireAdminAuth, async (req, res) => {
|
|
try {
|
|
const { player_id, location_id, time_seconds } = req.body;
|
|
|
|
// Zeit in INTERVAL konvertieren
|
|
const timeInterval = `${time_seconds} seconds`;
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO times (player_id, location_id, recorded_time, created_at)
|
|
VALUES ($1, $2, $3, NOW())
|
|
RETURNING *`,
|
|
[player_id, location_id, timeInterval]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Lauf erfolgreich hinzugefügt',
|
|
data: result.rows[0]
|
|
});
|
|
} catch (error) {
|
|
console.error('Error creating run:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Hinzufügen des Laufs'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Admin Läufe - PUT (Bearbeiten)
|
|
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;
|
|
|
|
// Zeit in INTERVAL konvertieren
|
|
const timeInterval = `${time_seconds} seconds`;
|
|
|
|
const result = await pool.query(
|
|
`UPDATE times
|
|
SET player_id = $1, location_id = $2, recorded_time = $3
|
|
WHERE id = $4
|
|
RETURNING *`,
|
|
[player_id, location_id, timeInterval, runId]
|
|
);
|
|
|
|
if (result.rowCount > 0) {
|
|
res.json({
|
|
success: true,
|
|
message: 'Lauf erfolgreich aktualisiert',
|
|
data: result.rows[0]
|
|
});
|
|
} else {
|
|
res.status(404).json({ success: false, message: 'Lauf nicht gefunden' });
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating run:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Aktualisieren des Laufs'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Admin-Benutzer - POST (Hinzufügen)
|
|
router.post('/v1/admin/adminusers', requireAdminAuth, async (req, res) => {
|
|
try {
|
|
const { username, password, access_level } = req.body;
|
|
|
|
// Passwort hashen
|
|
const saltRounds = 10;
|
|
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO adminusers (username, password_hash, access_level, is_active, created_at)
|
|
VALUES ($1, $2, $3, true, NOW())
|
|
RETURNING id, username, access_level, is_active, created_at`,
|
|
[username, hashedPassword, access_level]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Admin-Benutzer erfolgreich hinzugefügt',
|
|
data: result.rows[0]
|
|
});
|
|
} catch (error) {
|
|
console.error('Error creating admin user:', error);
|
|
if (error.code === '23505') { // Unique constraint violation
|
|
res.status(400).json({
|
|
success: false,
|
|
message: 'Benutzername bereits vergeben'
|
|
});
|
|
} else {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Hinzufügen des Admin-Benutzers'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// Admin-Benutzer - PUT (Bearbeiten)
|
|
router.put('/v1/admin/adminusers/:id', requireAdminAuth, async (req, res) => {
|
|
try {
|
|
const userId = req.params.id;
|
|
const { username, password, access_level } = req.body;
|
|
|
|
// Verhindern, dass sich selbst bearbeitet
|
|
if (parseInt(userId) === req.session.userId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Sie können sich nicht selbst bearbeiten'
|
|
});
|
|
}
|
|
|
|
let query, params;
|
|
|
|
if (password) {
|
|
// Passwort hashen
|
|
const saltRounds = 10;
|
|
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
|
|
|
query = `UPDATE adminusers
|
|
SET username = $1, password_hash = $2, access_level = $3
|
|
WHERE id = $4
|
|
RETURNING id, username, access_level, is_active, created_at`;
|
|
params = [username, hashedPassword, access_level, userId];
|
|
} else {
|
|
query = `UPDATE adminusers
|
|
SET username = $1, access_level = $2
|
|
WHERE id = $3
|
|
RETURNING id, username, access_level, is_active, created_at`;
|
|
params = [username, access_level, userId];
|
|
}
|
|
|
|
const result = await pool.query(query, params);
|
|
|
|
if (result.rowCount > 0) {
|
|
res.json({
|
|
success: true,
|
|
message: 'Admin-Benutzer erfolgreich aktualisiert',
|
|
data: result.rows[0]
|
|
});
|
|
} else {
|
|
res.status(404).json({ success: false, message: 'Admin-Benutzer nicht gefunden' });
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating admin user:', error);
|
|
if (error.code === '23505') { // Unique constraint violation
|
|
res.status(400).json({
|
|
success: false,
|
|
message: 'Benutzername bereits vergeben'
|
|
});
|
|
} else {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Fehler beim Aktualisieren des Admin-Benutzers'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
module.exports = { router, requireApiKey };
|