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:
370
backend/DATABASE.md
Normal file
370
backend/DATABASE.md
Normal 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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user