Files
Schichtenplaner/backend/src/scripts/applyMigration.ts

211 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { db } from '../services/databaseService.js';
import { readFile, readdir } from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Define interfaces for type safety
interface TableColumnInfo {
cid: number;
name: string;
type: string;
notnull: number;
dflt_value: string | null;
pk: number;
}
interface CountResult {
count: number;
}
interface EmployeeRecord {
id: string;
// Note: roles column doesn't exist in new schema
}
// Helper function to ensure migrations are tracked
async function ensureMigrationTable() {
await db.exec(`
CREATE TABLE IF NOT EXISTS applied_migrations (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
}
// Helper function to check if a migration has been applied
async function isMigrationApplied(migrationName: string): Promise<boolean> {
const result = await db.get<CountResult>(
'SELECT COUNT(*) as count FROM applied_migrations WHERE name = ?',
[migrationName]
);
return (result?.count ?? 0) > 0;
}
// Helper function to mark a migration as applied
async function markMigrationAsApplied(migrationName: string) {
await db.run(
'INSERT INTO applied_migrations (id, name) VALUES (?, ?)',
[crypto.randomUUID(), migrationName]
);
}
// Function to handle schema changes for the new employee type system
async function applySchemaUpdates() {
console.log('🔄 Applying schema updates for new employee type system...');
await db.run('BEGIN TRANSACTION');
try {
// 1. Create employee_types table if it doesn't exist
await db.exec(`
CREATE TABLE IF NOT EXISTS employee_types (
type TEXT PRIMARY KEY,
category TEXT CHECK(category IN ('internal', 'external')) NOT NULL,
has_contract_type BOOLEAN NOT NULL DEFAULT FALSE
)
`);
// 2. Insert default employee types
await db.run(`INSERT OR IGNORE INTO employee_types (type, category, has_contract_type) VALUES ('manager', 'internal', 1)`);
await db.run(`INSERT OR IGNORE INTO employee_types (type, category, has_contract_type) VALUES ('personell', 'internal', 1)`);
await db.run(`INSERT OR IGNORE INTO employee_types (type, category, has_contract_type) VALUES ('apprentice', 'internal', 1)`);
await db.run(`INSERT OR IGNORE INTO employee_types (type, category, has_contract_type) VALUES ('guest', 'external', 0)`);
// 3. Check if employees table needs to be altered
const employeesTableInfo = await db.all<TableColumnInfo>(`
PRAGMA table_info(employees)
`);
// Check for employee_type column (not roles column)
const hasEmployeeType = employeesTableInfo.some((col: TableColumnInfo) => col.name === 'employee_type');
const hasIsTrainee = employeesTableInfo.some((col: TableColumnInfo) => col.name === 'is_trainee');
if (!hasEmployeeType) {
console.log('🔄 Adding employee_type column to employees table...');
await db.run('ALTER TABLE employees ADD COLUMN employee_type TEXT NOT NULL DEFAULT "personell"');
}
if (!hasIsTrainee) {
console.log('🔄 Adding is_trainee column to employees table...');
await db.run('ALTER TABLE employees ADD COLUMN is_trainee BOOLEAN DEFAULT FALSE NOT NULL');
}
// 4. Create roles table if it doesn't exist
await db.exec(`
CREATE TABLE IF NOT EXISTS roles (
role TEXT PRIMARY KEY CHECK(role IN ('admin', 'user', 'maintenance')),
authority_level INTEGER NOT NULL UNIQUE CHECK(authority_level BETWEEN 1 AND 100),
description TEXT
)
`);
// 5. Insert default roles
await db.run(`INSERT OR IGNORE INTO roles (role, authority_level, description) VALUES ('admin', 100, 'Vollzugriff')`);
await db.run(`INSERT OR IGNORE INTO roles (role, authority_level, description) VALUES ('maintenance', 50, 'Wartungszugriff')`);
await db.run(`INSERT OR IGNORE INTO roles (role, authority_level, description) VALUES ('user', 10, 'Standardbenutzer')`);
// 6. Create employee_roles junction table if it doesn't exist
await db.exec(`
CREATE TABLE IF NOT EXISTS employee_roles (
employee_id TEXT NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
role TEXT NOT NULL REFERENCES roles(role),
PRIMARY KEY (employee_id, role)
)
`);
// 7. REMOVED: Role migration logic since roles column doesn't exist in new schema
// In a fresh installation, we don't need to migrate existing roles
console.log(' Skipping role migration - fresh installation with new schema');
await db.run('COMMIT');
console.log('✅ Schema updates applied successfully');
} catch (error) {
await db.run('ROLLBACK');
console.error('❌ Schema updates failed:', error);
throw error;
}
}
export async function applyMigration() {
try {
console.log('📦 Starting database migration...');
// Ensure migration tracking table exists
await ensureMigrationTable();
// Apply schema updates for new employee type system
await applySchemaUpdates();
// Get all migration files
const migrationsDir = join(__dirname, '../database/migrations');
try {
const files = await readdir(migrationsDir);
// Sort files to ensure consistent order
const migrationFiles = files
.filter(f => f.endsWith('.sql'))
.sort();
// Process each migration file
for (const migrationFile of migrationFiles) {
if (await isMigrationApplied(migrationFile)) {
console.log(` Migration ${migrationFile} already applied, skipping...`);
continue;
}
console.log(`📄 Applying migration: ${migrationFile}`);
const migrationPath = join(migrationsDir, migrationFile);
const migrationSQL = await readFile(migrationPath, 'utf-8');
// Split into individual statements
const statements = migrationSQL
.split(';')
.map(s => s.trim())
.filter(s => s.length > 0);
// Start transaction for this migration
await db.run('BEGIN TRANSACTION');
try {
// Execute each statement
for (const statement of statements) {
try {
await db.exec(statement);
console.log('✅ Executed:', statement.slice(0, 50) + '...');
} catch (error) {
const err = error as { code: string; message: string };
if (err.code === 'SQLITE_ERROR' && err.message.includes('duplicate column name')) {
console.log(' Column already exists, skipping...');
continue;
}
throw error;
}
}
// Mark migration as applied
await markMigrationAsApplied(migrationFile);
await db.run('COMMIT');
console.log(`✅ Migration ${migrationFile} applied successfully`);
} catch (error) {
await db.run('ROLLBACK');
throw error;
}
}
} catch (error) {
console.log(' No migration directory found or no migration files, skipping file-based migrations...');
}
console.log('✅ All migrations completed successfully');
} catch (error) {
console.error('❌ Migration failed:', error);
throw error;
}
}