Files
stechuhr3/backend/DATABASE.md
Torsten Schulz (local) e95bb4cb76 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
2025-10-17 14:11:28 +02:00

7.6 KiB

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

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

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

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)

# 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:

// 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:

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:

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:

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:

{
  "action": "Clock In|Clock Out",
  "project": "Projektname",
  "description": "Beschreibung der Tätigkeit"
}

Parsing im Service:

let stateData;
try {
  stateData = JSON.parse(worklog.state);
} catch {
  stateData = { project: 'Allgemein', description: '' };
}

Statistiken

Basis-Statistiken

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:

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:

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

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

mysqldump -u root -p stechuhr2 > backup_$(date +%Y%m%d).sql

Restore

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):

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