changed email creation

This commit is contained in:
2025-10-20 01:50:12 +02:00
parent 9393de5239
commit e80bb81b5d
9 changed files with 308 additions and 98 deletions

View File

@@ -32,13 +32,19 @@ export interface JWTPayload {
}
export interface RegisterRequest {
email: string;
// REMOVED: email - will be auto-generated
password: string;
firstname: String;
lastname: String;
firstname: string;
lastname: string;
role?: string;
}
function generateEmail(firstname: string, lastname: string): string {
const cleanFirstname = firstname.toLowerCase().replace(/[^a-z0-9]/g, '');
const cleanLastname = lastname.toLowerCase().replace(/[^a-z0-9]/g, '');
return `${cleanFirstname}.${cleanLastname}@sp.de`;
}
export const login = async (req: Request, res: Response) => {
try {
const { email, password } = req.body as LoginRequest;
@@ -162,16 +168,19 @@ export const validateToken = async (req: Request, res: Response) => {
export const register = async (req: Request, res: Response) => {
try {
const { email, password, firstname, lastname, role = 'user' } = req.body as RegisterRequest;
const { password, firstname, lastname, role = 'user' } = req.body as RegisterRequest;
// Validate required fields
if (!email || !password || !firstname || !lastname) {
// Validate required fields - REMOVED email
if (!password || !firstname || !lastname) {
return res.status(400).json({
error: 'E-Mail, Passwort und Name sind erforderlich'
error: 'Password, firstname und lastname sind erforderlich'
});
}
// Check if email already exists
// Generate email automatically
const email = generateEmail(firstname, lastname);
// Check if generated email already exists
const existingUser = await db.get<Employee>(
'SELECT id FROM employees WHERE email = ?',
[email]
@@ -179,19 +188,19 @@ export const register = async (req: Request, res: Response) => {
if (existingUser) {
return res.status(400).json({
error: 'Ein Benutzer mit dieser E-Mail existiert bereits'
error: `Ein Benutzer mit der E-Mail ${email} existiert bereits`
});
}
// Hash password
// Hash password and create user
const hashedPassword = await bcrypt.hash(password, 10);
// Insert user
const result = await db.run(
`INSERT INTO employees (id, email, password, firstname, lastname, role, employee_type, contract_type, can_work_alone, is_active)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[uuidv4(), email, hashedPassword, firstname, lastname, role, 'experienced', 'small', false, 1]
);
// Insert user with generated email
const result = await db.run(
`INSERT INTO employees (id, email, password, firstname, lastname, role, employee_type, contract_type, can_work_alone, is_active)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[uuidv4(), email, hashedPassword, firstname, lastname, role, 'experienced', 'small', false, 1]
);
if (!result.lastID) {
throw new Error('Benutzer konnte nicht erstellt werden');
@@ -199,7 +208,7 @@ export const register = async (req: Request, res: Response) => {
// Get created user
const newUser = await db.get<Employee>(
'SELECT id, email, name, role FROM employees WHERE id = ?',
'SELECT id, email, firstname, lastname, role FROM employees WHERE id = ?',
[result.lastID]
);

View File

@@ -6,6 +6,13 @@ import { db } from '../services/databaseService.js';
import { AuthRequest } from '../middleware/auth.js';
import { CreateEmployeeRequest } from '../models/Employee.js';
function generateEmail(firstname: string, lastname: string): string {
// Remove special characters and convert to lowercase
const cleanFirstname = firstname.toLowerCase().replace(/[^a-z0-9]/g, '');
const cleanLastname = lastname.toLowerCase().replace(/[^a-z0-9]/g, '');
return `${cleanFirstname}.${cleanLastname}@sp.de`;
}
export const getEmployees = async (req: AuthRequest, res: Response): Promise<void> => {
try {
console.log('🔍 Fetching employees - User:', req.user);
@@ -76,41 +83,40 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
});
const {
email,
password,
firstname,
lastname,
firstname,
lastname,
role,
employeeType,
contractType,
canWorkAlone
} = req.body as CreateEmployeeRequest;
// Validierung
if (!email || !password || !firstname || !lastname || !role || !employeeType || !contractType) {
// Validation - REMOVED email check
if (!password || !firstname || !lastname || !role || !employeeType || !contractType) {
console.log('❌ Validation failed: Missing required fields');
res.status(400).json({
error: 'Email, password, name, role, employeeType und contractType sind erforderlich'
error: 'Password, firstname, lastname, role, employeeType und contractType sind erforderlich'
});
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;
}
// Generate email automatically
const email = generateEmail(firstname, lastname);
console.log('📧 Generated email:', email);
// Check if email already exists
const existingActiveUser = await db.get<any>('SELECT id FROM employees WHERE email = ? AND is_active = 1', [email]);
// Check if generated email already exists
const existingUser = await db.get<any>('SELECT id FROM employees WHERE email = ? AND is_active = 1', [email]);
if (existingActiveUser) {
console.log('❌ Email exists for active user:', existingActiveUser);
res.status(409).json({ error: 'Email already exists' });
if (existingUser) {
console.log('❌ Generated email already exists:', email);
res.status(409).json({
error: `Employee with email ${email} already exists. Please use different firstname/lastname.`
});
return;
}
// Hash password
// Hash password and create employee
const hashedPassword = await bcrypt.hash(password, 10);
const employeeId = uuidv4();
@@ -123,8 +129,8 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
employeeId,
email,
hashedPassword,
firstname, // Changed from name
lastname, // Added
firstname,
lastname,
role,
employeeType,
contractType,
@@ -133,10 +139,10 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
]
);
// Return created employee
// Return created employee with generated email
const newEmployee = await db.get<any>(`
SELECT
id, email, name, role, is_active as isActive,
id, email, firstname, lastname, role, is_active as isActive,
employee_type as employeeType,
contract_type as contractType,
can_work_alone as canWorkAlone,
@@ -157,35 +163,58 @@ export const updateEmployee = async (req: AuthRequest, res: Response): Promise<v
try {
const { id } = req.params;
const { firstname, lastname, role, isActive, employeeType, contractType, canWorkAlone } = req.body;
console.log('📝 Update Employee Request:', { id, name, role, isActive, employeeType, contractType, canWorkAlone });
// Check if employee exists
const existingEmployee = await db.get('SELECT * FROM employees WHERE id = ?', [id]);
console.log('📝 Update Employee Request:', { id, firstname, lastname, role, isActive, employeeType, contractType, canWorkAlone });
// Check if employee exists and get current data
const existingEmployee = await db.get<any>('SELECT * FROM employees WHERE id = ?', [id]);
if (!existingEmployee) {
res.status(404).json({ error: 'Employee not found' });
return;
}
// Update employee
// Generate new email if firstname or lastname changed
let email = existingEmployee.email;
if (firstname || lastname) {
const newFirstname = firstname || existingEmployee.firstname;
const newLastname = lastname || existingEmployee.lastname;
email = generateEmail(newFirstname, newLastname);
// Check if new email already exists (for another employee)
const emailExists = await db.get<any>(
'SELECT id FROM employees WHERE email = ? AND id != ? AND is_active = 1',
[email, id]
);
if (emailExists) {
res.status(409).json({
error: `Cannot update name - email ${email} already exists for another employee`
});
return;
}
}
// Update employee with potentially new email
await db.run(
`UPDATE employees
SET firstname = COALESCE(?, firstname),
lastname = COALESCE(?, lastname),
role = COALESCE(?, role),
is_active = COALESCE(?, is_active),
employee_type = COALESCE(?, employee_type),
contract_type = COALESCE(?, contract_type),
can_work_alone = COALESCE(?, can_work_alone)
WHERE id = ?`,
[firstname, lastname, role, isActive, employeeType, contractType, canWorkAlone, id]
SET firstname = COALESCE(?, firstname),
lastname = COALESCE(?, lastname),
email = ?,
role = COALESCE(?, role),
is_active = COALESCE(?, is_active),
employee_type = COALESCE(?, employee_type),
contract_type = COALESCE(?, contract_type),
can_work_alone = COALESCE(?, can_work_alone)
WHERE id = ?`,
[firstname, lastname, email, role, isActive, employeeType, contractType, canWorkAlone, id]
);
console.log('✅ Employee updated successfully');
console.log('✅ Employee updated successfully with email:', email);
// Return updated employee
const updatedEmployee = await db.get<any>(`
SELECT
id, email, name, role, is_active as isActive,
id, email, firstname, lastname, role, is_active as isActive,
employee_type as employeeType,
contract_type as contractType,
can_work_alone as canWorkAlone,

View File

@@ -5,6 +5,25 @@ import { v4 as uuidv4 } from 'uuid';
import { randomUUID } from 'crypto';
import { db } from '../services/databaseService.js';
// Add the same email generation function
function generateEmail(firstname: string, lastname: string): string {
// Convert German umlauts to their expanded forms
const convertUmlauts = (str: string): string => {
return str
.toLowerCase()
.replace(/ü/g, 'ue')
.replace(/ö/g, 'oe')
.replace(/ä/g, 'ae')
.replace(/ß/g, 'ss');
};
// Remove any remaining special characters and convert to lowercase
const cleanFirstname = convertUmlauts(firstname).replace(/[^a-z0-9]/g, '');
const cleanLastname = convertUmlauts(lastname).replace(/[^a-z0-9]/g, '');
return `${cleanFirstname}.${cleanLastname}@sp.de`;
}
export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => {
try {
const adminExists = await db.get<{ 'COUNT(*)': number }>(
@@ -43,12 +62,11 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
return;
}
const { password, firstname, lastname } = req.body;
const email = 'admin@instandhaltung.de';
const { password, firstname, lastname } = req.body; // Changed from name to firstname/lastname
console.log('👤 Creating admin with data:', { name, email });
console.log('👤 Creating admin with data:', { firstname, lastname });
// Validation
// Validation - updated for firstname/lastname
if (!password || !firstname || !lastname) {
res.status(400).json({ error: 'Passwort, Vorname und Nachname sind erforderlich' });
return;
@@ -60,6 +78,10 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
return;
}
// Generate email automatically using the same pattern
const email = generateEmail(firstname, lastname);
console.log('📧 Generated admin email:', email);
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
const adminId = randomUUID();
@@ -70,18 +92,14 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
await db.run('BEGIN TRANSACTION');
try {
// Create admin user
// Create admin user with generated email
await db.run(
`INSERT INTO employees (id, email, password, firstname, lastname, role, employee_type, contract_type, can_work_alone, is_active)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[adminId, email, hashedPassword, firstname, lastname, 'admin', 'manager', 'large', true, 1]
);
console.log('✅ Admin user created successfully');
// Initialize default templates WITHOUT starting a new transaction
//console.log('🔄 Initialisiere Standard-Vorlagen...');
//await initializeDefaultTemplates(adminId, false);
console.log('✅ Admin user created successfully with email:', email);
// Commit the entire setup transaction
await db.run('COMMIT');
@@ -91,7 +109,9 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
res.status(201).json({
success: true,
message: 'Admin erfolgreich erstellt',
email: email
email: email,
firstname: firstname,
lastname: lastname
});
} catch (dbError) {

View File

@@ -14,7 +14,6 @@ export interface Employee {
}
export interface CreateEmployeeRequest {
email: string;
password: string;
firstname: string;
lastname: string;

View File

@@ -3,15 +3,15 @@ export interface ShiftPlan {
id: string;
name: string;
description?: string;
startDate?: string; // Optional for templates
endDate?: string; // Optional for templates
startDate?: string;
endDate?: string;
isTemplate: boolean;
status: 'draft' | 'published' | 'archived' | 'template';
createdBy: string;
createdAt: string;
timeSlots: TimeSlot[];
shifts: Shift[];
scheduledShifts?: ScheduledShift[]; // Only for non-template plans with dates
scheduledShifts?: ScheduledShift[];
}
export interface TimeSlot {
@@ -85,7 +85,6 @@ export interface AssignEmployeeRequest {
scheduledShiftId: string;
}
export interface UpdateRequiredEmployeesRequest {
requiredEmployees: number;
}

View File

@@ -2,15 +2,20 @@
import { Employee } from './Employee.js';
import { ShiftPlan } from './ShiftPlan.js';
// Add the missing type definitions
// Updated Availability interface to match new schema
export interface Availability {
id: string;
employeeId: string;
planId: string;
dayOfWeek: number; // 1=Monday, 7=Sunday
timeSlotId: string;
shiftId: string; // Now references shift_id instead of time_slot_id + day_of_week
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
notes?: string;
// Optional convenience fields (can be joined from shifts and time_slots tables)
dayOfWeek?: number;
timeSlotId?: string;
timeSlotName?: string;
startTime?: string;
endTime?: string;
}
export interface Constraint {
@@ -120,4 +125,77 @@ export interface ShiftRequirement {
maxEmployees: number;
assignedEmployees: string[];
isPriority: boolean;
}
// New types for the updated schema
export interface EmployeeWithRoles extends Employee {
roles: string[];
}
export interface ShiftWithDetails {
id: string;
planId: string;
timeSlotId: string;
dayOfWeek: number;
requiredEmployees: number;
color?: string;
timeSlot: {
id: string;
name: string;
startTime: string;
endTime: string;
description?: string;
};
}
export interface AvailabilityWithDetails extends Availability {
employee?: {
id: string;
firstname: string;
lastname: string;
employeeType: 'manager' | 'trainee' | 'experienced';
canWorkAlone: boolean;
};
shift?: {
dayOfWeek: number;
timeSlotId: string;
timeSlot?: {
name: string;
startTime: string;
endTime: string;
};
};
}
// Types for scheduling algorithm input
export interface SchedulingInput {
planId: string;
startDate: string;
endDate: string;
constraints: Constraint[];
options?: SolverOptions;
}
// Types for scheduling results with enhanced information
export interface EnhancedAssignment extends Assignment {
employeeName: string;
shiftDetails: {
date: string;
dayOfWeek: number;
timeSlotName: string;
startTime: string;
endTime: string;
};
preferenceLevel: number;
}
export interface SchedulingStatistics {
totalShifts: number;
totalAssignments: number;
coverageRate: number;
preferenceSatisfaction: number;
constraintViolations: number;
hardConstraintViolations: number;
softConstraintViolations: number;
processingTime: number;
}

View File

@@ -2,7 +2,8 @@
export interface Employee {
id: string;
email: string;
name: string;
firstname: string;
lastname: string;
role: 'admin' | 'maintenance' | 'user';
employeeType: 'manager' | 'trainee' | 'experienced';
contractType: 'small' | 'large';
@@ -13,9 +14,9 @@ export interface Employee {
}
export interface CreateEmployeeRequest {
email: string;
password: string;
name: string;
firstname: string;
lastname: string;
role: 'admin' | 'maintenance' | 'user';
employeeType: 'manager' | 'trainee' | 'experienced';
contractType: 'small' | 'large';
@@ -23,7 +24,8 @@ export interface CreateEmployeeRequest {
}
export interface UpdateEmployeeRequest {
name?: string;
firstname?: string;
lastname?: string;
role?: 'admin' | 'maintenance' | 'user';
employeeType?: 'manager' | 'trainee' | 'experienced';
contractType?: 'small' | 'large';
@@ -39,8 +41,7 @@ export interface EmployeeAvailability {
id: string;
employeeId: string;
planId: string;
dayOfWeek: number; // 1=Monday, 7=Sunday
timeSlotId: string;
shiftId: string; // Now references shift_id instead of time_slot_id + day_of_week
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
notes?: string;
}
@@ -72,4 +73,14 @@ export interface ManagerSelfAssignmentRequest {
export interface EmployeeWithAvailabilities extends Employee {
availabilities: EmployeeAvailability[];
}
// Additional types for the new roles system
export interface Role {
role: 'admin' | 'user' | 'maintenance';
}
export interface EmployeeRole {
employeeId: string;
role: 'admin' | 'user' | 'maintenance';
}

View File

@@ -3,14 +3,15 @@ export interface ShiftPlan {
id: string;
name: string;
description?: string;
startDate?: string; // Optional for templates
endDate?: string; // Optional for templates
startDate?: string;
endDate?: string;
isTemplate: boolean;
status: 'draft' | 'published' | 'archived' | 'template';
createdBy: string;
createdAt: string;
timeSlots: TimeSlot[];
shifts: Shift[];
scheduledShifts?: ScheduledShift[];
}
export interface TimeSlot {
@@ -49,16 +50,6 @@ export interface ShiftAssignment {
assignedBy: string;
}
export interface EmployeeAvailability {
id: string;
employeeId: string;
planId: string;
dayOfWeek: number;
timeSlotId: string;
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
notes?: string;
}
// Request/Response DTOs
export interface CreateShiftPlanRequest {
name: string;
@@ -68,7 +59,6 @@ export interface CreateShiftPlanRequest {
isTemplate: boolean;
timeSlots: Omit<TimeSlot, 'id' | 'planId'>[];
shifts: Omit<Shift, 'id' | 'planId'>[];
templateId?: string;
}
export interface UpdateShiftPlanRequest {
@@ -95,10 +85,6 @@ export interface AssignEmployeeRequest {
scheduledShiftId: string;
}
export interface UpdateAvailabilityRequest {
planId: string;
availabilities: Omit<EmployeeAvailability, 'id' | 'employeeId'>[];
}
export interface UpdateRequiredEmployeesRequest {
requiredEmployees: number;

View File

@@ -2,15 +2,20 @@
import { Employee } from './Employee.js';
import { ShiftPlan } from './ShiftPlan.js';
// Add the missing type definitions
// Updated Availability interface to match new schema
export interface Availability {
id: string;
employeeId: string;
planId: string;
dayOfWeek: number; // 1=Monday, 7=Sunday
timeSlotId: string;
shiftId: string; // Now references shift_id instead of time_slot_id + day_of_week
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
notes?: string;
// Optional convenience fields (can be joined from shifts and time_slots tables)
dayOfWeek?: number;
timeSlotId?: string;
timeSlotName?: string;
startTime?: string;
endTime?: string;
}
export interface Constraint {
@@ -75,6 +80,7 @@ export interface Solution {
variablesCreated: number;
optimal: boolean;
};
variables?: { [key: string]: number };
}
// Additional helper types for the scheduling system
@@ -119,4 +125,77 @@ export interface ShiftRequirement {
maxEmployees: number;
assignedEmployees: string[];
isPriority: boolean;
}
// New types for the updated schema
export interface EmployeeWithRoles extends Employee {
roles: string[];
}
export interface ShiftWithDetails {
id: string;
planId: string;
timeSlotId: string;
dayOfWeek: number;
requiredEmployees: number;
color?: string;
timeSlot: {
id: string;
name: string;
startTime: string;
endTime: string;
description?: string;
};
}
export interface AvailabilityWithDetails extends Availability {
employee?: {
id: string;
firstname: string;
lastname: string;
employeeType: 'manager' | 'trainee' | 'experienced';
canWorkAlone: boolean;
};
shift?: {
dayOfWeek: number;
timeSlotId: string;
timeSlot?: {
name: string;
startTime: string;
endTime: string;
};
};
}
// Types for scheduling algorithm input
export interface SchedulingInput {
planId: string;
startDate: string;
endDate: string;
constraints: Constraint[];
options?: SolverOptions;
}
// Types for scheduling results with enhanced information
export interface EnhancedAssignment extends Assignment {
employeeName: string;
shiftDetails: {
date: string;
dayOfWeek: number;
timeSlotName: string;
startTime: string;
endTime: string;
};
preferenceLevel: number;
}
export interface SchedulingStatistics {
totalShifts: number;
totalAssignments: number;
coverageRate: number;
preferenceSatisfaction: number;
constraintViolations: number;
hardConstraintViolations: number;
softConstraintViolations: number;
processingTime: number;
}