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

370
backend/DATABASE.md Normal file
View File

@@ -0,0 +1,370 @@
# Datenbank-Integration
## Übersicht
TimeClock v3 verwendet MySQL mit der bestehenden `stechuhr2` Datenbankstruktur. Die Integration erfolgt über das Repository-Pattern für saubere Abstraktion.
## Architektur
```
Controller
Service (Business-Logik)
Repository (Datenbankzugriff)
MySQL Datenbank
```
## Verwendete Tabellen
### Haupttabellen
#### `worklog` - Zeiteinträge
```sql
CREATE TABLE `worklog` (
`id` bigint NOT NULL AUTO_INCREMENT,
`version` int NOT NULL,
`user_id` bigint NOT NULL,
`state` text NOT NULL, -- JSON mit {action, project, description}
`tstamp` datetime DEFAULT NULL,
`relatedTo_id` bigint DEFAULT NULL, -- Verknüpfung Clock In/Out
PRIMARY KEY (`id`)
)
```
**Verwendung:**
- Clock In: Neuer Eintrag mit `relatedTo_id = NULL`
- Clock Out: Neuer Eintrag mit `relatedTo_id` verweist auf Clock In
- State: JSON-Format `{"action": "Clock In", "project": "Projektname", "description": "Beschreibung"}`
#### `user` - Benutzer
```sql
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`full_name` text NOT NULL,
`daily_hours` int NOT NULL,
`week_hours` int NOT NULL,
...
)
```
#### `weekly_worktime` - Wochenarbeitszeit
```sql
CREATE TABLE `weekly_worktime` (
`id` int NOT NULL AUTO_INCREMENT,
`weekly_work_time` double NOT NULL DEFAULT '40',
`starting_from` date DEFAULT NULL,
`ends_at` date DEFAULT NULL,
`user_id` int DEFAULT NULL,
...
)
```
## Konfiguration
### Umgebungsvariablen (`.env`)
```env
# Datenbank-Verbindung
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=stechuhr2
```
### Connection Pool
Die Datenbankverbindung wird über einen Connection Pool verwaltet:
```javascript
// src/config/database.js
const pool = mysql.createPool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
connectionLimit: 10
});
```
## Repository-Pattern
### WorklogRepository
**Verantwortlichkeiten:**
- CRUD-Operationen auf `worklog` Tabelle
- Komplexe Queries (Paare, Statistiken)
- Datenbankzugriff abstrahieren
**Hauptmethoden:**
```javascript
await worklogRepository.findAllByUser(userId)
await worklogRepository.findById(id)
await worklogRepository.create(worklogData)
await worklogRepository.update(id, updateData)
await worklogRepository.delete(id)
await worklogRepository.findPairsByUser(userId)
await worklogRepository.getStatistics(userId)
```
### UserRepository
**Verantwortlichkeiten:**
- Benutzerverwaltung
- Wochenarbeitszeit abrufen
- Benutzereinstellungen
**Hauptmethoden:**
```javascript
await userRepository.findById(userId)
await userRepository.findByEmail(email)
await userRepository.getWeeklyWorktime(userId, date)
await userRepository.getUserSetting(userId, settingId)
```
## Datenfluss
### Timer starten
```
1. Frontend → POST /api/time-entries
{ project: "Website", description: "Feature XY" }
2. Controller → Service.createEntry()
3. Service → WorklogRepository.create()
{
user_id: 1,
state: '{"action":"Clock In","project":"Website","description":"Feature XY"}',
tstamp: '2025-10-15 10:00:00',
relatedTo_id: null
}
4. MySQL → INSERT INTO worklog
→ Gibt neue ID zurück (z.B. 123)
5. Service → Konvertiert zu TimeEntry-Format
{
id: 123,
startTime: '2025-10-15T10:00:00',
endTime: null,
project: 'Website',
description: 'Feature XY',
duration: null,
isRunning: true
}
6. Controller → Frontend
Status 201, JSON Response
```
### Timer stoppen
```
1. Frontend → PUT /api/time-entries/123
{ endTime: '2025-10-15T12:00:00' }
2. Service → WorklogRepository.create()
{
user_id: 1,
state: '{"action":"Clock Out","project":"Website","description":"Feature XY"}',
tstamp: '2025-10-15 12:00:00',
relatedTo_id: 123 // Verweist auf Clock In
}
3. Service → Berechnet Dauer und gibt Pair zurück
```
## Worklog-Paare
Clock In und Clock Out werden als separate Einträge gespeichert, aber logisch als Paar behandelt:
```sql
SELECT
w1.id as start_id,
w1.tstamp as start_time,
w2.id as end_id,
w2.tstamp as end_time,
TIMESTAMPDIFF(SECOND, w1.tstamp, w2.tstamp) as duration
FROM worklog w1
LEFT JOIN worklog w2 ON w2.relatedTo_id = w1.id
WHERE w1.user_id = ? AND w1.relatedTo_id IS NULL
```
**Vorteile:**
- Historische Genauigkeit
- Korrekturen möglich
- Audit-Trail
## State-Format
Der `state` Feld speichert JSON-Daten:
```json
{
"action": "Clock In|Clock Out",
"project": "Projektname",
"description": "Beschreibung der Tätigkeit"
}
```
**Parsing im Service:**
```javascript
let stateData;
try {
stateData = JSON.parse(worklog.state);
} catch {
stateData = { project: 'Allgemein', description: '' };
}
```
## Statistiken
### Basis-Statistiken
```sql
SELECT
COUNT(DISTINCT w1.id) as total_entries,
COUNT(DISTINCT w2.id) as completed_entries,
SUM(TIMESTAMPDIFF(SECOND, w1.tstamp, w2.tstamp)) as total_seconds
FROM worklog w1
LEFT JOIN worklog w2 ON w2.relatedTo_id = w1.id
WHERE w1.user_id = ? AND w1.relatedTo_id IS NULL
```
### Projekt-Statistiken
Werden im Service berechnet durch Gruppierung der Paare nach Projekt.
## Transaktionen
Für zukünftige Erweiterungen können Transaktionen verwendet werden:
```javascript
const connection = await database.getPool().getConnection();
try {
await connection.beginTransaction();
// Multiple Operationen
await connection.execute(sql1, params1);
await connection.execute(sql2, params2);
await connection.commit();
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
```
## Migration von In-Memory
Der Code unterstützt beide Modi:
- **Entwicklung:** In-Memory (schnell, keine DB erforderlich)
- **Produktion:** MySQL (persistent, skalierbar)
Umschalten durch Änderung des Service-Imports.
## Performance-Optimierungen
### Indizes
Wichtige Indizes sind bereits vorhanden:
```sql
KEY `worklog_tstamp_IDX` (`tstamp`)
KEY `worklog_user_id_IDX` (`user_id`, `tstamp`)
```
### Connection Pooling
- Wiederverwendung von Verbindungen
- Limit: 10 gleichzeitige Verbindungen
- Automatisches Reconnect
### Query-Optimierung
- Prepared Statements (SQL Injection Schutz)
- WHERE-Klauseln mit Indizes
- LEFT JOIN statt mehrere Queries
## Fehlerbehandlung
```javascript
try {
const result = await worklogRepository.create(data);
return result;
} catch (error) {
if (error.code === 'ER_DUP_ENTRY') {
throw new Error('Eintrag existiert bereits');
}
if (error.code === 'ER_NO_REFERENCED_ROW_2') {
throw new Error('Benutzer nicht gefunden');
}
throw error;
}
```
## Backup & Restore
### Backup erstellen
```bash
mysqldump -u root -p stechuhr2 > backup_$(date +%Y%m%d).sql
```
### Restore
```bash
mysql -u root -p stechuhr2 < backup_20251015.sql
```
## Sicherheit
**Implemented:**
- Prepared Statements (SQL Injection Schutz)
- Connection Pooling
- Error Handling
🔄 **TODO:**
- Input Validation
- Rate Limiting
- User Authentication
- Row-Level Security
## Monitoring
Logging von Queries (Development):
```javascript
if (process.env.NODE_ENV === 'development') {
console.log('SQL:', sql);
console.log('Params:', params);
}
```
## Zukünftige Erweiterungen
1. **Authentifizierung**
- Verwendung der `auth_info` Tabelle
- JWT-Token-basiert
2. **Multi-User Support**
- Benutzer-ID aus Session
- Row-Level Security
3. **Erweiterte Features**
- Urlaub (`vacation` Tabelle)
- Krankmeldungen (`sick` Tabelle)
- Feiertage (`holiday` Tabelle)
4. **Caching**
- Redis für häufige Abfragen
- Cache-Invalidierung
5. **Read Replicas**
- Master-Slave Setup
- Read-Write Splitting