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
371 lines
7.6 KiB
Markdown
371 lines
7.6 KiB
Markdown
# 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
|
|
|
|
|
|
|