# Sequelize ORM Integration

## Übersicht

TimeClock v3 verwendet **Sequelize** als ORM (Object-Relational Mapping) für die MySQL-Datenbank. Dies ermöglicht eine typsichere, objektorientierte Arbeitsweise mit der Datenbank.

## Vorteile von Sequelize

✅ **Type-Safety** - Models definieren Datentypen  
✅ **Validierung** - Automatische Datenvalidierung  
✅ **Assoziationen** - Einfache Beziehungen zwischen Models  
✅ **Migrations** - Versionskontrolle für Datenbankschema  
✅ **Abstraktion** - Datenbank-unabhängiger Code  
✅ **Query Builder** - Typsichere Queries  

## Konfiguration

### .env Datei

Alle Datenbankeinstellungen werden über `.env` konfiguriert:

```env
# MySQL Datenbank-Verbindung
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=ihr_passwort
DB_NAME=stechuhr2

# Datenbank-Optionen
DB_LOGGING=false          # SQL-Queries in Console loggen (true/false)
DB_TIMEZONE=+01:00        # Timezone für Timestamps
DB_POOL_MAX=10            # Max. Connections im Pool
DB_POOL_MIN=0             # Min. Connections im Pool
```

### Initialisierung

```javascript
// src/config/database.js
const sequelize = new Sequelize(
  process.env.DB_NAME,
  process.env.DB_USER,
  process.env.DB_PASSWORD,
  {
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    dialect: 'mysql',
    logging: process.env.DB_LOGGING === 'true',
    pool: {
      max: parseInt(process.env.DB_POOL_MAX),
      min: parseInt(process.env.DB_POOL_MIN)
    }
  }
);
```

## Models

### Model-Struktur

Jedes Model repräsentiert eine Datenbanktabelle:

```javascript
// src/models/User.js
const { Model, DataTypes } = require('sequelize');

class User extends Model {
  static initialize(sequelize) {
    User.init(
      {
        id: {
          type: DataTypes.BIGINT,
          primaryKey: true,
          autoIncrement: true
        },
        full_name: {
          type: DataTypes.TEXT,
          allowNull: false
        }
        // ... weitere Felder
      },
      {
        sequelize,
        tableName: 'user',
        timestamps: false
      }
    );
    return User;
  }
}
```

### Verfügbare Models

| Model | Tabelle | Beschreibung |
|-------|---------|--------------|
| **User** | `user` | Benutzer |
| **Worklog** | `worklog` | Zeiteinträge (Clock In/Out) |
| **AuthInfo** | `auth_info` | Authentifizierung |
| **State** | `state` | Bundesländer/Regionen |
| **WeeklyWorktime** | `weekly_worktime` | Wochenarbeitszeit |
| **Holiday** | `holiday` | Feiertage |
| **Vacation** | `vacation` | Urlaub |
| **Sick** | `sick` | Krankmeldungen |
| **SickType** | `sick_type` | Krankmeldungstypen |

### Model-Features

#### 1. Getter/Setter

```javascript
// Worklog Model
class Worklog extends Model {
  // State als JSON speichern/laden
  state: {
    type: DataTypes.TEXT,
    get() {
      const rawValue = this.getDataValue('state');
      try {
        return JSON.parse(rawValue);
      } catch {
        return { action: rawValue };
      }
    },
    set(value) {
      if (typeof value === 'object') {
        this.setDataValue('state', JSON.stringify(value));
      }
    }
  }
}

// Verwendung:
worklog.state = { action: 'Clock In', project: 'Website' };
console.log(worklog.state.project); // 'Website'
```

#### 2. Instance Methods

```javascript
class Worklog extends Model {
  isClockIn() {
    return this.relatedTo_id === null;
  }
  
  getProject() {
    return this.state.project || 'Allgemein';
  }
}

// Verwendung:
if (worklog.isClockIn()) {
  console.log(`Projekt: ${worklog.getProject()}`);
}
```

#### 3. Class Methods

```javascript
class User extends Model {
  getFullName() {
    return this.full_name;
  }
}
```

## Assoziationen

Models sind miteinander verknüpft:

```javascript
// User → Worklog (1:n)
User.hasMany(Worklog, { foreignKey: 'user_id', as: 'worklogs' });
Worklog.belongsTo(User, { foreignKey: 'user_id', as: 'user' });

// Worklog → Worklog (Clock In/Out)
Worklog.belongsTo(Worklog, { foreignKey: 'relatedTo_id', as: 'relatedTo' });
Worklog.hasOne(Worklog, { foreignKey: 'relatedTo_id', as: 'clockOut' });

// User → AuthInfo (1:1)
User.hasOne(AuthInfo, { foreignKey: 'user_id', as: 'authInfo' });
AuthInfo.belongsTo(User, { foreignKey: 'user_id', as: 'user' });
```

### Include in Queries

```javascript
// User mit Worklogs laden
const user = await User.findByPk(1, {
  include: [{
    model: Worklog,
    as: 'worklogs',
    limit: 10
  }]
});

console.log(user.worklogs); // Array von Worklog-Instanzen
```

## Repository-Pattern

Repositories verwenden Sequelize Models:

```javascript
class WorklogRepository {
  async findById(id) {
    const { Worklog } = database.getModels();
    return await Worklog.findByPk(id);
  }

  async create(data) {
    const { Worklog } = database.getModels();
    return await Worklog.create(data);
  }

  async update(id, updates) {
    const { Worklog } = database.getModels();
    const worklog = await Worklog.findByPk(id);
    await worklog.update(updates);
    return worklog;
  }
}
```

## Query-Beispiele

### Einfache Queries

```javascript
// Alle Benutzer
const users = await User.findAll();

// Benutzer nach ID
const user = await User.findByPk(1);

// Erstellen
const newUser = await User.create({
  full_name: 'Max Mustermann',
  daily_hours: 8
});

// Aktualisieren
await user.update({ full_name: 'Max M.' });

// Löschen
await user.destroy();
```

### WHERE-Bedingungen

```javascript
const { Op } = require('sequelize');

// Einfache WHERE
const worklogs = await Worklog.findAll({
  where: { user_id: 1 }
});

// Operatoren
const recent = await Worklog.findAll({
  where: {
    tstamp: {
      [Op.gte]: new Date('2025-01-01')
    }
  }
});

// Mehrere Bedingungen
const active = await Worklog.findAll({
  where: {
    user_id: 1,
    relatedTo_id: null
  }
});
```

### Order & Limit

```javascript
const latest = await Worklog.findAll({
  where: { user_id: 1 },
  order: [['tstamp', 'DESC']],
  limit: 10,
  offset: 0
});
```

### Aggregationen

```javascript
// COUNT
const count = await Worklog.count({
  where: { user_id: 1 }
});

// SUM (mit raw query)
const [result] = await sequelize.query(`
  SELECT SUM(TIMESTAMPDIFF(SECOND, w1.tstamp, w2.tstamp)) as total
  FROM worklog w1
  JOIN worklog w2 ON w2.relatedTo_id = w1.id
  WHERE w1.user_id = ?
`, {
  replacements: [1],
  type: QueryTypes.SELECT
});
```

## Raw Queries

Für komplexe Queries können Raw SQL verwendet werden:

```javascript
const sequelize = database.getSequelize();

const [results] = await sequelize.query(`
  SELECT 
    w1.id as start_id,
    w2.id as end_id,
    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 = :userId
`, {
  replacements: { userId: 1 },
  type: sequelize.QueryTypes.SELECT
});
```

## Transaktionen

```javascript
const sequelize = database.getSequelize();

const t = await sequelize.transaction();

try {
  // Clock In erstellen
  const clockIn = await Worklog.create({
    user_id: 1,
    state: { action: 'Clock In' },
    tstamp: new Date()
  }, { transaction: t });

  // Weitere Operationen...
  
  await t.commit();
} catch (error) {
  await t.rollback();
  throw error;
}
```

## Validierung

```javascript
class User extends Model {
  static initialize(sequelize) {
    User.init({
      email: {
        type: DataTypes.STRING,
        validate: {
          isEmail: true,
          notEmpty: true
        }
      },
      full_name: {
        type: DataTypes.TEXT,
        validate: {
          len: [2, 255]
        }
      },
      daily_hours: {
        type: DataTypes.INTEGER,
        validate: {
          min: 1,
          max: 24
        }
      }
    }, {
      sequelize,
      tableName: 'user'
    });
  }
}
```

## Hooks (Lifecycle Events)

```javascript
class User extends Model {
  static initialize(sequelize) {
    User.init({ /* ... */ }, {
      sequelize,
      tableName: 'user',
      hooks: {
        beforeCreate: (user, options) => {
          user.last_change = new Date();
        },
        beforeUpdate: (user, options) => {
          user.version += 1;
          user.last_change = new Date();
        }
      }
    });
  }
}
```

## Migrations (Optional)

Für Datenbankschema-Änderungen:

```bash
# Sequelize CLI installieren
npm install --save-dev sequelize-cli

# Initialisieren
npx sequelize-cli init

# Migration erstellen
npx sequelize-cli migration:generate --name add-user-field

# Migrations ausführen
npx sequelize-cli db:migrate

# Migrations rückgängig machen
npx sequelize-cli db:migrate:undo
```

## Best Practices

### 1. Model-Zugriff über Database

```javascript
// ✅ Gut
const { Worklog } = database.getModels();
const worklogs = await Worklog.findAll();

// ❌ Nicht
const Worklog = require('../models/Worklog');
```

### 2. Eager Loading für Beziehungen

```javascript
// ✅ Gut - Eine Query
const user = await User.findByPk(1, {
  include: [{ model: Worklog, as: 'worklogs' }]
});

// ❌ Nicht - N+1 Problem
const user = await User.findByPk(1);
const worklogs = await Worklog.findAll({ where: { user_id: user.id } });
```

### 3. Transactions für kritische Operationen

```javascript
// ✅ Gut
const t = await sequelize.transaction();
try {
  await operation1({ transaction: t });
  await operation2({ transaction: t });
  await t.commit();
} catch (error) {
  await t.rollback();
}
```

### 4. Raw Queries für Performance

```javascript
// Für komplexe Aggregationen sind Raw Queries oft schneller
const stats = await sequelize.query(complexSQL, {
  replacements: { userId },
  type: QueryTypes.SELECT
});
```

## Debugging

### SQL-Queries anzeigen

```env
# In .env
DB_LOGGING=true
```

Dann werden alle SQL-Queries in der Console geloggt:

```
Executing (default): SELECT * FROM `user` WHERE `id` = 1;
```

### Model-Daten anzeigen

```javascript
console.log(user.toJSON());
```

## Performance-Tipps

1. **Indizes nutzen** - Models definieren Indizes
2. **Eager Loading** - Include statt separate Queries
3. **Pagination** - Limit & Offset verwenden
4. **Raw Queries** - Für komplexe Aggregationen
5. **Connection Pooling** - Bereits konfiguriert

## Troubleshooting

### "Sequelize not initialized"

```javascript
// Sicherstellen, dass database.initialize() aufgerufen wurde
await database.initialize();
```

### "Model not found"

```javascript
// Models werden in database.js initialisiert
// Prüfen Sie, ob Model in initializeModels() geladen wird
```

### Timezone-Probleme

```env
# In .env die richtige Timezone setzen
DB_TIMEZONE=+01:00
```

## Migration von Raw SQL

### Vorher (Raw SQL):

```javascript
const [rows] = await db.execute(
  'SELECT * FROM user WHERE id = ?',
  [userId]
);
```

### Nachher (Sequelize):

```javascript
const user = await User.findByPk(userId);
```

Viel einfacher und typsicher! 🎉

## Zusammenfassung

Sequelize bietet:
- ✅ Typsichere Models
- ✅ Automatische Validierung
- ✅ Einfache Assoziationen
- ✅ Query Builder
- ✅ .env-basierte Konfiguration
- ✅ Migrations-Support

Perfekt für professionelle Node.js-Anwendungen!



