mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
fixed user deletion
This commit is contained in:
@@ -13,6 +13,7 @@ export const getEmployees = async (req: AuthRequest, res: Response): Promise<voi
|
||||
phone, department, created_at as createdAt,
|
||||
last_login as lastLogin
|
||||
FROM users
|
||||
WHERE is_active = 1
|
||||
ORDER BY name
|
||||
`);
|
||||
|
||||
@@ -50,22 +51,36 @@ export const getEmployee = async (req: AuthRequest, res: Response): Promise<void
|
||||
|
||||
export const createEmployee = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||
try {
|
||||
console.log('🔍 Starting employee creation process with data:', {
|
||||
...req.body,
|
||||
password: '***hidden***'
|
||||
});
|
||||
|
||||
const { email, password, name, role, phone, department } = req.body;
|
||||
|
||||
// Validierung
|
||||
if (!email || !password || !name || !role) {
|
||||
console.log('❌ Validation failed: Missing required fields');
|
||||
res.status(400).json({ error: 'Email, password, name and role are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
console.log('❌ Validation failed: Password too short');
|
||||
res.status(400).json({ error: 'Password must be at least 6 characters long' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if email already exists
|
||||
const existingUser = await db.get<any>('SELECT id FROM users WHERE email = ?', [email]);
|
||||
if (existingUser) {
|
||||
// First check for ANY user with this email to debug
|
||||
const allUsersWithEmail = await db.all<any>('SELECT id, email, is_active FROM users WHERE email = ?', [email]);
|
||||
console.log('🔍 Found existing users with this email:', allUsersWithEmail);
|
||||
|
||||
// Check if email already exists among active users only
|
||||
const existingActiveUser = await db.get<any>('SELECT id, is_active FROM users WHERE email = ? AND is_active = 1', [email]);
|
||||
console.log('🔍 Checking active users with this email:', existingActiveUser);
|
||||
|
||||
if (existingActiveUser) {
|
||||
console.log('❌ Email exists for active user:', existingActiveUser);
|
||||
res.status(409).json({ error: 'Email already exists' });
|
||||
return;
|
||||
}
|
||||
@@ -141,16 +156,152 @@ export const updateEmployee = async (req: AuthRequest, res: Response): Promise<v
|
||||
export const deleteEmployee = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
console.log('🗑️ Starting deletion process for employee ID:', id);
|
||||
|
||||
// Get full employee details first
|
||||
const existingEmployee = await db.get<any>(`
|
||||
SELECT id, email, name, is_active, role
|
||||
FROM users
|
||||
WHERE id = ?
|
||||
`, [id]);
|
||||
|
||||
// Check if employee exists
|
||||
const existingEmployee = await db.get('SELECT * FROM users WHERE id = ?', [id]);
|
||||
if (!existingEmployee) {
|
||||
console.log('❌ Employee not found for deletion:', id);
|
||||
res.status(404).json({ error: 'Employee not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Soft delete - set is_active to false
|
||||
await db.run('UPDATE users SET is_active = 0 WHERE id = ?', [id]);
|
||||
console.log('📝 Found employee to delete:', existingEmployee);
|
||||
|
||||
try {
|
||||
// Start transaction
|
||||
await db.run('BEGIN TRANSACTION');
|
||||
|
||||
// Remove all references first
|
||||
const queries = [
|
||||
// 1. Remove availabilities
|
||||
'DELETE FROM employee_availabilities WHERE employee_id = ?',
|
||||
|
||||
// 2. Remove from assigned shifts
|
||||
`UPDATE assigned_shifts
|
||||
SET assigned_employees = json_remove(
|
||||
assigned_employees,
|
||||
'$[' || (
|
||||
SELECT key
|
||||
FROM json_each(assigned_employees)
|
||||
WHERE value = ?
|
||||
LIMIT 1
|
||||
) || ']'
|
||||
)
|
||||
WHERE json_extract(assigned_employees, '$') LIKE ?`,
|
||||
|
||||
// 3. Nullify references
|
||||
'UPDATE shift_plans SET created_by = NULL WHERE created_by = ?',
|
||||
'UPDATE shift_templates SET created_by = NULL WHERE created_by = ?',
|
||||
|
||||
// 4. Delete the user
|
||||
'DELETE FROM users WHERE id = ?'
|
||||
];
|
||||
|
||||
// Execute all cleanup queries
|
||||
for (const query of queries) {
|
||||
if (query.includes('json_extract')) {
|
||||
await db.run(query, [id, `%${id}%`]);
|
||||
} else {
|
||||
await db.run(query, [id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the deletion
|
||||
const verifyDeletion = await db.get<{count: number}>(`
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM users WHERE id = ?) +
|
||||
(SELECT COUNT(*) FROM employee_availabilities WHERE employee_id = ?) +
|
||||
(SELECT COUNT(*) FROM assigned_shifts WHERE json_extract(assigned_employees, '$') LIKE ?) as count
|
||||
`, [id, id, `%${id}%`]);
|
||||
|
||||
if ((verifyDeletion?.count ?? 0) > 0) {
|
||||
throw new Error('Failed to remove all references to the employee');
|
||||
}
|
||||
|
||||
// If we got here, everything worked
|
||||
await db.run('COMMIT');
|
||||
console.log('✅ Successfully deleted employee:', existingEmployee.email);
|
||||
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
console.error('Error during deletion, rolling back:', error);
|
||||
await db.run('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log('Attempting to delete employee:', { id, email: existingEmployee.email });
|
||||
|
||||
try {
|
||||
// Start a transaction to ensure all deletes succeed or none do
|
||||
await db.run('BEGIN TRANSACTION');
|
||||
|
||||
console.log('Starting transaction for deletion of employee:', id);
|
||||
|
||||
// First verify the current state
|
||||
const beforeState = await db.all(`
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM employee_availabilities WHERE employee_id = ?) as avail_count,
|
||||
(SELECT COUNT(*) FROM shift_templates WHERE created_by = ?) as template_count,
|
||||
(SELECT COUNT(*) FROM shift_plans WHERE created_by = ?) as plan_count,
|
||||
(SELECT COUNT(*) FROM users WHERE id = ?) as user_count
|
||||
`, [id, id, id, id]);
|
||||
console.log('Before deletion state:', beforeState[0]);
|
||||
|
||||
// Clear all references first
|
||||
await db.run(`DELETE FROM employee_availabilities WHERE employee_id = ?`, [id]);
|
||||
await db.run(`UPDATE shift_plans SET created_by = NULL WHERE created_by = ?`, [id]);
|
||||
await db.run(`UPDATE shift_templates SET created_by = NULL WHERE created_by = ?`, [id]);
|
||||
await db.run(`UPDATE assigned_shifts
|
||||
SET assigned_employees = json_remove(assigned_employees, '$[' || json_each.key || ']')
|
||||
FROM json_each(assigned_shifts.assigned_employees)
|
||||
WHERE json_each.value = ?`, [id]);
|
||||
|
||||
// Now delete the user
|
||||
await db.run('DELETE FROM users WHERE id = ?', [id]);
|
||||
|
||||
// Verify the deletion
|
||||
const verifyAfterDelete = await db.all(`
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM users WHERE id = ?) as user_exists,
|
||||
(SELECT COUNT(*) FROM users WHERE email = ?) as email_exists,
|
||||
(SELECT COUNT(*) FROM users WHERE email = ? AND is_active = 1) as active_email_exists
|
||||
`, [id, existingEmployee.email, existingEmployee.email]);
|
||||
|
||||
console.log('🔍 Verification after delete:', verifyAfterDelete[0]);
|
||||
|
||||
// Verify the deletion worked
|
||||
const verifyDeletion = await db.all<{user_count: number, avail_count: number, shift_refs: number}>(`
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM users WHERE id = ?) as user_count,
|
||||
(SELECT COUNT(*) FROM employee_availabilities WHERE employee_id = ?) as avail_count,
|
||||
(SELECT COUNT(*) FROM assigned_shifts WHERE json_extract(assigned_employees, '$') LIKE ?) as shift_refs
|
||||
`, [id, id, `%${id}%`]);
|
||||
|
||||
const counts = verifyDeletion[0];
|
||||
if (!counts || counts.user_count > 0 || counts.avail_count > 0 || counts.shift_refs > 0) {
|
||||
console.error('Deletion verification failed:', counts);
|
||||
await db.run('ROLLBACK');
|
||||
throw new Error('Failed to delete all user data');
|
||||
}
|
||||
|
||||
// If we got here, the deletion was successful
|
||||
await db.run('COMMIT');
|
||||
console.log('✅ Deletion committed successfully');
|
||||
|
||||
// Final verification after commit
|
||||
const finalCheck = await db.get('SELECT * FROM users WHERE email = ?', [existingEmployee.email]);
|
||||
console.log('🔍 Final verification - any user with this email:', finalCheck);
|
||||
} catch (err) {
|
||||
console.error('Error during deletion transaction:', err);
|
||||
await db.run('ROLLBACK');
|
||||
throw err;
|
||||
}
|
||||
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
|
||||
@@ -239,18 +239,32 @@ app.put('/api/employees/:id', async (req: any, res: any) => {
|
||||
app.delete('/api/employees/:id', async (req: any, res: any) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
console.log('🗑️ Deleting employee:', id);
|
||||
console.log('🗑️ Deleting employee:', id);
|
||||
|
||||
// Mitarbeiter finden
|
||||
const employeeIndex = employees.findIndex(emp => emp.id === id);
|
||||
if (employeeIndex === -1) {
|
||||
return res.status(404).json({ error: 'Employee not found' });
|
||||
}
|
||||
|
||||
// Soft delete
|
||||
employees[employeeIndex].isActive = false;
|
||||
const employeeToDelete = employees[employeeIndex];
|
||||
|
||||
// Admin-Check
|
||||
if (employeeToDelete.role === 'admin') {
|
||||
const adminCount = employees.filter(emp =>
|
||||
emp.role === 'admin' && emp.isActive
|
||||
).length;
|
||||
|
||||
if (adminCount <= 1) {
|
||||
return res.status(400).json({
|
||||
error: 'Mindestens ein Administrator muss im System verbleiben'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Perform hard delete
|
||||
employees.splice(employeeIndex, 1);
|
||||
console.log('✅ Employee permanently deleted:', employeeToDelete.name);
|
||||
|
||||
console.log('✅ Employee deactivated:', employees[employeeIndex].name);
|
||||
res.status(204).send();
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -17,20 +17,59 @@ export class DatabaseService {
|
||||
console.error('Database connection error:', err);
|
||||
} else {
|
||||
console.log('Connected to SQLite database');
|
||||
this.initializeDatabase();
|
||||
this.enableForeignKeysAndInitialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async enableForeignKeysAndInitialize() {
|
||||
try {
|
||||
// First enable foreign keys
|
||||
await this.run('PRAGMA foreign_keys = ON');
|
||||
console.log('Foreign keys enabled');
|
||||
|
||||
// Then check if it's actually enabled
|
||||
const pragma = await this.get('PRAGMA foreign_keys');
|
||||
console.log('Foreign keys status:', pragma);
|
||||
|
||||
// Now initialize the database
|
||||
await this.initializeDatabase();
|
||||
} catch (error) {
|
||||
console.error('Error in database initialization:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private initializeDatabase() {
|
||||
const dropTables = `
|
||||
DROP TABLE IF EXISTS employee_availabilities;
|
||||
DROP TABLE IF EXISTS assigned_shifts;
|
||||
DROP TABLE IF EXISTS shift_plans;
|
||||
DROP TABLE IF EXISTS template_shifts;
|
||||
DROP TABLE IF EXISTS shift_templates;
|
||||
DROP TABLE IF EXISTS users;
|
||||
`;
|
||||
|
||||
this.db.exec(dropTables, (err) => {
|
||||
if (err) {
|
||||
console.error('Error dropping tables:', err);
|
||||
return;
|
||||
}
|
||||
console.log('Existing tables dropped');
|
||||
});
|
||||
|
||||
const schema = `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
role TEXT CHECK(role IN ('admin', 'instandhalter', 'user')) NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
phone TEXT,
|
||||
department TEXT,
|
||||
last_login DATETIME,
|
||||
CONSTRAINT unique_active_email UNIQUE (email, is_active) WHERE is_active = 1
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS shift_templates (
|
||||
@@ -64,8 +103,8 @@ export class DatabaseService {
|
||||
status TEXT CHECK(status IN ('draft', 'published')) DEFAULT 'draft',
|
||||
created_by TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (created_by) REFERENCES users(id),
|
||||
FOREIGN KEY (template_id) REFERENCES shift_templates(id)
|
||||
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (template_id) REFERENCES shift_templates(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assigned_shifts (
|
||||
@@ -98,6 +137,15 @@ export class DatabaseService {
|
||||
});
|
||||
}
|
||||
|
||||
exec(sql: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.exec(sql, (err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get<T>(sql: string, params: any[] = []): Promise<T | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.get(sql, params, (err, row) => {
|
||||
|
||||
Reference in New Issue
Block a user