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