# 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