Initial commit: TimeClock v3 - Node.js/Vue.js Zeiterfassung

Features:
- Backend: Node.js/Express mit MySQL/MariaDB
- Frontend: Vue.js 3 mit Composition API
- UTC-Zeithandling für korrekte Zeiterfassung
- Timewish-basierte Überstundenberechnung
- Wochenübersicht mit Urlaubs-/Krankheits-/Feiertagshandling
- Bereinigtes Arbeitsende (Generell/Woche)
- Überstunden-Offset für historische Daten
- Fixed Layout mit scrollbarem Content
- Kompakte UI mit grünem Theme
This commit is contained in:
Torsten Schulz (local)
2025-10-17 14:11:28 +02:00
commit e95bb4cb76
86 changed files with 19530 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
const authService = require('../services/AuthService');
/**
* Authentication Middleware
* Validiert JWT-Token und fügt Benutzer-Info zu req hinzu
*/
const authenticateToken = async (req, res, next) => {
try {
// Token aus Authorization-Header extrahieren
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
success: false,
error: 'Kein Token vorhanden',
code: 'NO_TOKEN'
});
}
// Token validieren
const decoded = await authService.validateToken(token);
// Benutzer-Info zu Request hinzufügen
req.user = decoded;
next();
} catch (error) {
console.error('Auth-Middleware-Fehler:', error.message);
return res.status(401).json({
success: false,
error: 'Ungültiger oder abgelaufener Token',
code: 'INVALID_TOKEN'
});
}
};
/**
* Optional Authentication Middleware
* Validiert Token falls vorhanden, erlaubt aber auch Requests ohne Token
*/
const optionalAuth = async (req, res, next) => {
try {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token) {
const decoded = await authService.validateToken(token);
req.user = decoded;
}
next();
} catch (error) {
// Bei optionalem Auth ignorieren wir Fehler
next();
}
};
module.exports = {
authenticateToken,
optionalAuth
};

View File

@@ -0,0 +1,104 @@
const hashId = require('../utils/hashId');
/**
* Middleware zum automatischen Hashen von IDs in Response-Daten
*/
const hashResponseIds = (req, res, next) => {
// Originale json-Methode speichern
const originalJson = res.json.bind(res);
// json-Methode überschreiben
res.json = function(data) {
// Wenn Daten vorhanden sind, IDs hashen
if (data) {
data = hashResponseData(data);
}
// Originale Methode aufrufen
return originalJson(data);
};
next();
};
/**
* Rekursiv IDs in Datenstruktur hashen
* @param {*} data - Zu verarbeitende Daten
* @returns {*} Daten mit gehashten IDs
*/
function hashResponseData(data) {
// Primitive Typen direkt zurückgeben
if (data === null || data === undefined) {
return data;
}
// Array: Jedes Element verarbeiten
if (Array.isArray(data)) {
return data.map(item => hashResponseData(item));
}
// Object: ID-Felder hashen
if (typeof data === 'object') {
const result = {};
for (const [key, value] of Object.entries(data)) {
// ID-Felder identifizieren und hashen
if (isIdField(key) && typeof value === 'number') {
result[key] = hashId.encode(value);
}
// Verschachtelte Objekte/Arrays rekursiv verarbeiten
else if (typeof value === 'object') {
result[key] = hashResponseData(value);
}
// Andere Werte unverändert übernehmen
else {
result[key] = value;
}
}
return result;
}
// Andere Typen unverändert
return data;
}
/**
* Prüft ob ein Feldname eine ID repräsentiert
* @param {string} fieldName - Name des Feldes
* @returns {boolean} True wenn ID-Feld
*/
function isIdField(fieldName) {
// Felder die als ID erkannt werden sollen
const idPatterns = [
'id',
'_id',
'user_id',
'userId',
'auth_info_id',
'authInfoId',
'auth_token_id',
'authTokenId',
'worklog_id',
'worklogId',
'vacation_id',
'vacationId',
'sick_id',
'sickId',
'holiday_id',
'holidayId',
'state_id',
'stateId',
'sick_type_id',
'sickTypeId',
'weekly_worktime_id',
'weeklyWorktimeId'
];
return idPatterns.includes(fieldName);
}
module.exports = hashResponseIds;

View File

@@ -0,0 +1,116 @@
const hashId = require('../utils/hashId');
/**
* Middleware zum automatischen Enthashen von IDs in Request-Daten
*/
const unhashRequestIds = (req, res, next) => {
// Body-Parameter verarbeiten
if (req.body && typeof req.body === 'object') {
req.body = unhashRequestData(req.body);
}
// Query-Parameter verarbeiten
if (req.query && typeof req.query === 'object') {
req.query = unhashRequestData(req.query);
}
// Route-Parameter verarbeiten
if (req.params && typeof req.params === 'object') {
req.params = unhashRequestData(req.params);
}
next();
};
/**
* Rekursiv Hash-IDs in Datenstruktur entschlüsseln
* @param {*} data - Zu verarbeitende Daten
* @returns {*} Daten mit entschlüsselten IDs
*/
function unhashRequestData(data) {
// Primitive Typen direkt zurückgeben
if (data === null || data === undefined) {
return data;
}
// String: Könnte ein Hash sein
if (typeof data === 'string') {
// Versuche als Hash zu dekodieren
const decoded = hashId.decode(data);
if (decoded !== null) {
return decoded;
}
return data;
}
// Array: Jedes Element verarbeiten
if (Array.isArray(data)) {
return data.map(item => unhashRequestData(item));
}
// Object: Rekursiv verarbeiten
if (typeof data === 'object') {
const result = {};
for (const [key, value] of Object.entries(data)) {
// ID-Felder identifizieren und enthashen
if (isIdField(key) && typeof value === 'string') {
const decoded = hashId.decode(value);
result[key] = decoded !== null ? decoded : value;
}
// Verschachtelte Objekte/Arrays rekursiv verarbeiten
else if (typeof value === 'object') {
result[key] = unhashRequestData(value);
}
// Andere Werte unverändert übernehmen
else {
result[key] = value;
}
}
return result;
}
// Andere Typen unverändert
return data;
}
/**
* Prüft ob ein Feldname eine ID repräsentiert
* @param {string} fieldName - Name des Feldes
* @returns {boolean} True wenn ID-Feld
*/
function isIdField(fieldName) {
// Felder die als ID erkannt werden sollen
const idPatterns = [
'id',
'_id',
'user_id',
'userId',
'auth_info_id',
'authInfoId',
'auth_token_id',
'authTokenId',
'worklog_id',
'worklogId',
'vacation_id',
'vacationId',
'sick_id',
'sickId',
'holiday_id',
'holidayId',
'state_id',
'stateId',
'sick_type_id',
'sickTypeId',
'weekly_worktime_id',
'weeklyWorktimeId'
];
return idPatterns.includes(fieldName);
}
module.exports = unhashRequestIds;