mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fbd0f03eb2 | |||
| 86166048e8 | |||
| 0363505126 | |||
| 1231c8362f | |||
| 663eb61352 |
@@ -7,12 +7,13 @@ export const validateLogin = [
|
|||||||
.isEmail()
|
.isEmail()
|
||||||
.withMessage('Must be a valid email')
|
.withMessage('Must be a valid email')
|
||||||
.normalizeEmail(),
|
.normalizeEmail(),
|
||||||
|
|
||||||
body('password')
|
body('password')
|
||||||
.isLength({ min: 6 })
|
.optional()
|
||||||
.withMessage('Password must be at least 6 characters')
|
.isLength({ min: 8 })
|
||||||
.trim()
|
.withMessage('Password must be at least 8 characters')
|
||||||
.escape()
|
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/)
|
||||||
|
.withMessage('Password must contain uppercase, lowercase, number and special character'),
|
||||||
];
|
];
|
||||||
|
|
||||||
export const validateRegister = [
|
export const validateRegister = [
|
||||||
@@ -21,18 +22,19 @@ export const validateRegister = [
|
|||||||
.withMessage('First name must be between 1-100 characters')
|
.withMessage('First name must be between 1-100 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('lastname')
|
body('lastname')
|
||||||
.isLength({ min: 1, max: 100 })
|
.isLength({ min: 1, max: 100 })
|
||||||
.withMessage('Last name must be between 1-100 characters')
|
.withMessage('Last name must be between 1-100 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('password')
|
body('password')
|
||||||
|
.optional()
|
||||||
.isLength({ min: 8 })
|
.isLength({ min: 8 })
|
||||||
.withMessage('Password must be at least 8 characters')
|
.withMessage('Password must be at least 8 characters')
|
||||||
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
|
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/)
|
||||||
.withMessage('Password must contain uppercase, lowercase and number')
|
.withMessage('Password must contain uppercase, lowercase, number and special character'),
|
||||||
];
|
];
|
||||||
|
|
||||||
// ===== EMPLOYEE VALIDATION =====
|
// ===== EMPLOYEE VALIDATION =====
|
||||||
@@ -42,49 +44,49 @@ export const validateEmployee = [
|
|||||||
.withMessage('First name must be between 1-100 characters')
|
.withMessage('First name must be between 1-100 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('lastname')
|
body('lastname')
|
||||||
.isLength({ min: 1, max: 100 })
|
.isLength({ min: 1, max: 100 })
|
||||||
.withMessage('Last name must be between 1-100 characters')
|
.withMessage('Last name must be between 1-100 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('password')
|
body('password')
|
||||||
.optional()
|
.optional()
|
||||||
.isLength({ min: 8 })
|
.isLength({ min: 8 })
|
||||||
.withMessage('Password must be at least 8 characters')
|
.withMessage('Password must be at least 8 characters')
|
||||||
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
|
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/)
|
||||||
.withMessage('Password must contain uppercase, lowercase and number'),
|
.withMessage('Password must contain uppercase, lowercase, number and special character'),
|
||||||
|
|
||||||
body('employeeType')
|
body('employeeType')
|
||||||
.isIn(['manager', 'personell', 'apprentice', 'guest'])
|
.isIn(['manager', 'personell', 'apprentice', 'guest'])
|
||||||
.withMessage('Employee type must be manager, personell, apprentice or guest'),
|
.withMessage('Employee type must be manager, personell, apprentice or guest'),
|
||||||
|
|
||||||
body('contractType')
|
body('contractType')
|
||||||
.optional()
|
.optional()
|
||||||
.isIn(['small', 'large', 'flexible'])
|
.isIn(['small', 'large', 'flexible'])
|
||||||
.withMessage('Contract type must be small, large or flexible'),
|
.withMessage('Contract type must be small, large or flexible'),
|
||||||
|
|
||||||
body('roles')
|
body('roles')
|
||||||
.optional()
|
.optional()
|
||||||
.isArray()
|
.isArray()
|
||||||
.withMessage('Roles must be an array'),
|
.withMessage('Roles must be an array'),
|
||||||
|
|
||||||
body('roles.*')
|
body('roles.*')
|
||||||
.optional()
|
.optional()
|
||||||
.isIn(['admin', 'maintenance', 'user'])
|
.isIn(['admin', 'maintenance', 'user'])
|
||||||
.withMessage('Invalid role. Allowed: admin, maintenance, user'),
|
.withMessage('Invalid role. Allowed: admin, maintenance, user'),
|
||||||
|
|
||||||
body('canWorkAlone')
|
body('canWorkAlone')
|
||||||
.optional()
|
.optional()
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
.withMessage('canWorkAlone must be a boolean'),
|
.withMessage('canWorkAlone must be a boolean'),
|
||||||
|
|
||||||
body('isTrainee')
|
body('isTrainee')
|
||||||
.optional()
|
.optional()
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
.withMessage('isTrainee must be a boolean'),
|
.withMessage('isTrainee must be a boolean'),
|
||||||
|
|
||||||
body('isActive')
|
body('isActive')
|
||||||
.optional()
|
.optional()
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
@@ -98,44 +100,44 @@ export const validateEmployeeUpdate = [
|
|||||||
.withMessage('First name must be between 1-100 characters')
|
.withMessage('First name must be between 1-100 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('lastname')
|
body('lastname')
|
||||||
.optional()
|
.optional()
|
||||||
.isLength({ min: 1, max: 100 })
|
.isLength({ min: 1, max: 100 })
|
||||||
.withMessage('Last name must be between 1-100 characters')
|
.withMessage('Last name must be between 1-100 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('employeeType')
|
body('employeeType')
|
||||||
.optional()
|
.optional()
|
||||||
.isIn(['manager', 'personell', 'apprentice', 'guest'])
|
.isIn(['manager', 'personell', 'apprentice', 'guest'])
|
||||||
.withMessage('Employee type must be manager, personell, apprentice or guest'),
|
.withMessage('Employee type must be manager, personell, apprentice or guest'),
|
||||||
|
|
||||||
body('contractType')
|
body('contractType')
|
||||||
.optional()
|
.optional()
|
||||||
.isIn(['small', 'large', 'flexible'])
|
.isIn(['small', 'large', 'flexible'])
|
||||||
.withMessage('Contract type must be small, large or flexible'),
|
.withMessage('Contract type must be small, large or flexible'),
|
||||||
|
|
||||||
body('roles')
|
body('roles')
|
||||||
.optional()
|
.optional()
|
||||||
.isArray()
|
.isArray()
|
||||||
.withMessage('Roles must be an array'),
|
.withMessage('Roles must be an array'),
|
||||||
|
|
||||||
body('roles.*')
|
body('roles.*')
|
||||||
.optional()
|
.optional()
|
||||||
.isIn(['admin', 'maintenance', 'user'])
|
.isIn(['admin', 'maintenance', 'user'])
|
||||||
.withMessage('Invalid role. Allowed: admin, maintenance, user'),
|
.withMessage('Invalid role. Allowed: admin, maintenance, user'),
|
||||||
|
|
||||||
body('canWorkAlone')
|
body('canWorkAlone')
|
||||||
.optional()
|
.optional()
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
.withMessage('canWorkAlone must be a boolean'),
|
.withMessage('canWorkAlone must be a boolean'),
|
||||||
|
|
||||||
body('isTrainee')
|
body('isTrainee')
|
||||||
.optional()
|
.optional()
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
.withMessage('isTrainee must be a boolean'),
|
.withMessage('isTrainee must be a boolean'),
|
||||||
|
|
||||||
body('isActive')
|
body('isActive')
|
||||||
.optional()
|
.optional()
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
@@ -145,14 +147,15 @@ export const validateEmployeeUpdate = [
|
|||||||
export const validateChangePassword = [
|
export const validateChangePassword = [
|
||||||
body('currentPassword')
|
body('currentPassword')
|
||||||
.optional()
|
.optional()
|
||||||
.isLength({ min: 6 })
|
|
||||||
.withMessage('Current password must be at least 6 characters'),
|
|
||||||
|
|
||||||
body('newPassword')
|
|
||||||
.isLength({ min: 8 })
|
.isLength({ min: 8 })
|
||||||
.withMessage('New password must be at least 8 characters')
|
.withMessage('Current password must be at least 8 characters'),
|
||||||
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
|
|
||||||
.withMessage('New password must contain uppercase, lowercase and number')
|
body('password')
|
||||||
|
.optional()
|
||||||
|
.isLength({ min: 8 })
|
||||||
|
.withMessage('Password must be at least 8 characters')
|
||||||
|
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/)
|
||||||
|
.withMessage('Password must contain uppercase, lowercase, number and special character'),
|
||||||
];
|
];
|
||||||
|
|
||||||
// ===== SHIFT PLAN VALIDATION =====
|
// ===== SHIFT PLAN VALIDATION =====
|
||||||
@@ -162,77 +165,77 @@ export const validateShiftPlan = [
|
|||||||
.withMessage('Name must be between 1-200 characters')
|
.withMessage('Name must be between 1-200 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('description')
|
body('description')
|
||||||
.optional()
|
.optional()
|
||||||
.isLength({ max: 1000 })
|
.isLength({ max: 1000 })
|
||||||
.withMessage('Description cannot exceed 1000 characters')
|
.withMessage('Description cannot exceed 1000 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('startDate')
|
body('startDate')
|
||||||
.optional()
|
.optional()
|
||||||
.isISO8601()
|
.isISO8601()
|
||||||
.withMessage('Must be a valid date (ISO format)'),
|
.withMessage('Must be a valid date (ISO format)'),
|
||||||
|
|
||||||
body('endDate')
|
body('endDate')
|
||||||
.optional()
|
.optional()
|
||||||
.isISO8601()
|
.isISO8601()
|
||||||
.withMessage('Must be a valid date (ISO format)'),
|
.withMessage('Must be a valid date (ISO format)'),
|
||||||
|
|
||||||
body('isTemplate')
|
body('isTemplate')
|
||||||
.optional()
|
.optional()
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
.withMessage('isTemplate must be a boolean'),
|
.withMessage('isTemplate must be a boolean'),
|
||||||
|
|
||||||
body('status')
|
body('status')
|
||||||
.optional()
|
.optional()
|
||||||
.isIn(['draft', 'published', 'archived', 'template'])
|
.isIn(['draft', 'published', 'archived', 'template'])
|
||||||
.withMessage('Status must be draft, published, archived or template'),
|
.withMessage('Status must be draft, published, archived or template'),
|
||||||
|
|
||||||
body('timeSlots')
|
body('timeSlots')
|
||||||
.optional()
|
.optional()
|
||||||
.isArray()
|
.isArray()
|
||||||
.withMessage('Time slots must be an array'),
|
.withMessage('Time slots must be an array'),
|
||||||
|
|
||||||
body('timeSlots.*.name')
|
body('timeSlots.*.name')
|
||||||
.isLength({ min: 1, max: 100 })
|
.isLength({ min: 1, max: 100 })
|
||||||
.withMessage('Time slot name must be between 1-100 characters')
|
.withMessage('Time slot name must be between 1-100 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('timeSlots.*.startTime')
|
body('timeSlots.*.startTime')
|
||||||
.matches(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/)
|
.matches(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/)
|
||||||
.withMessage('Start time must be in HH:MM format'),
|
.withMessage('Start time must be in HH:MM format'),
|
||||||
|
|
||||||
body('timeSlots.*.endTime')
|
body('timeSlots.*.endTime')
|
||||||
.matches(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/)
|
.matches(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/)
|
||||||
.withMessage('End time must be in HH:MM format'),
|
.withMessage('End time must be in HH:MM format'),
|
||||||
|
|
||||||
body('timeSlots.*.description')
|
body('timeSlots.*.description')
|
||||||
.optional()
|
.optional()
|
||||||
.isLength({ max: 500 })
|
.isLength({ max: 500 })
|
||||||
.withMessage('Time slot description cannot exceed 500 characters')
|
.withMessage('Time slot description cannot exceed 500 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('shifts')
|
body('shifts')
|
||||||
.optional()
|
.optional()
|
||||||
.isArray()
|
.isArray()
|
||||||
.withMessage('Shifts must be an array'),
|
.withMessage('Shifts must be an array'),
|
||||||
|
|
||||||
body('shifts.*.dayOfWeek')
|
body('shifts.*.dayOfWeek')
|
||||||
.isInt({ min: 1, max: 7 })
|
.isInt({ min: 1, max: 7 })
|
||||||
.withMessage('Day of week must be between 1-7 (Monday-Sunday)'),
|
.withMessage('Day of week must be between 1-7 (Monday-Sunday)'),
|
||||||
|
|
||||||
body('shifts.*.timeSlotId')
|
body('shifts.*.timeSlotId')
|
||||||
.isUUID()
|
.isUUID()
|
||||||
.withMessage('Time slot ID must be a valid UUID'),
|
.withMessage('Time slot ID must be a valid UUID'),
|
||||||
|
|
||||||
body('shifts.*.requiredEmployees')
|
body('shifts.*.requiredEmployees')
|
||||||
.isInt({ min: 0 })
|
.isInt({ min: 0 })
|
||||||
.withMessage('Required employees must be a positive integer'),
|
.withMessage('Required employees must be a positive integer'),
|
||||||
|
|
||||||
body('shifts.*.color')
|
body('shifts.*.color')
|
||||||
.optional()
|
.optional()
|
||||||
.isHexColor()
|
.isHexColor()
|
||||||
@@ -246,34 +249,34 @@ export const validateShiftPlanUpdate = [
|
|||||||
.withMessage('Name must be between 1-200 characters')
|
.withMessage('Name must be between 1-200 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('description')
|
body('description')
|
||||||
.optional()
|
.optional()
|
||||||
.isLength({ max: 1000 })
|
.isLength({ max: 1000 })
|
||||||
.withMessage('Description cannot exceed 1000 characters')
|
.withMessage('Description cannot exceed 1000 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('startDate')
|
body('startDate')
|
||||||
.optional()
|
.optional()
|
||||||
.isISO8601()
|
.isISO8601()
|
||||||
.withMessage('Must be a valid date (ISO format)'),
|
.withMessage('Must be a valid date (ISO format)'),
|
||||||
|
|
||||||
body('endDate')
|
body('endDate')
|
||||||
.optional()
|
.optional()
|
||||||
.isISO8601()
|
.isISO8601()
|
||||||
.withMessage('Must be a valid date (ISO format)'),
|
.withMessage('Must be a valid date (ISO format)'),
|
||||||
|
|
||||||
body('status')
|
body('status')
|
||||||
.optional()
|
.optional()
|
||||||
.isIn(['draft', 'published', 'archived', 'template'])
|
.isIn(['draft', 'published', 'archived', 'template'])
|
||||||
.withMessage('Status must be draft, published, archived or template'),
|
.withMessage('Status must be draft, published, archived or template'),
|
||||||
|
|
||||||
body('timeSlots')
|
body('timeSlots')
|
||||||
.optional()
|
.optional()
|
||||||
.isArray()
|
.isArray()
|
||||||
.withMessage('Time slots must be an array'),
|
.withMessage('Time slots must be an array'),
|
||||||
|
|
||||||
body('shifts')
|
body('shifts')
|
||||||
.optional()
|
.optional()
|
||||||
.isArray()
|
.isArray()
|
||||||
@@ -284,25 +287,25 @@ export const validateCreateFromPreset = [
|
|||||||
body('presetName')
|
body('presetName')
|
||||||
.isLength({ min: 1 })
|
.isLength({ min: 1 })
|
||||||
.withMessage('Preset name is required')
|
.withMessage('Preset name is required')
|
||||||
.isIn(['standardWeek', 'extendedWeek', 'weekendFocused', 'morningOnly', 'eveningOnly', 'ZEBRA_STANDARD'])
|
.isIn(['GENERAL_STANDARD', 'ZEBRA_STANDARD'])
|
||||||
.withMessage('Invalid preset name'),
|
.withMessage('Invalid preset name'),
|
||||||
|
|
||||||
body('name')
|
body('name')
|
||||||
.isLength({ min: 1, max: 200 })
|
.isLength({ min: 1, max: 200 })
|
||||||
.withMessage('Name must be between 1-200 characters')
|
.withMessage('Name must be between 1-200 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('startDate')
|
body('startDate')
|
||||||
.optional()
|
.optional()
|
||||||
.isISO8601()
|
.isISO8601()
|
||||||
.withMessage('Must be a valid date (ISO format)'),
|
.withMessage('Must be a valid date (ISO format)'),
|
||||||
|
|
||||||
body('endDate')
|
body('endDate')
|
||||||
.optional()
|
.optional()
|
||||||
.isISO8601()
|
.isISO8601()
|
||||||
.withMessage('Must be a valid date (ISO format)'),
|
.withMessage('Must be a valid date (ISO format)'),
|
||||||
|
|
||||||
body('isTemplate')
|
body('isTemplate')
|
||||||
.optional()
|
.optional()
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
@@ -314,11 +317,11 @@ export const validateScheduledShiftUpdate = [
|
|||||||
body('assignedEmployees')
|
body('assignedEmployees')
|
||||||
.isArray()
|
.isArray()
|
||||||
.withMessage('assignedEmployees must be an array'),
|
.withMessage('assignedEmployees must be an array'),
|
||||||
|
|
||||||
body('assignedEmployees.*')
|
body('assignedEmployees.*')
|
||||||
.isUUID()
|
.isUUID()
|
||||||
.withMessage('Each assigned employee must be a valid UUID'),
|
.withMessage('Each assigned employee must be a valid UUID'),
|
||||||
|
|
||||||
body('requiredEmployees')
|
body('requiredEmployees')
|
||||||
.optional()
|
.optional()
|
||||||
.isInt({ min: 0 })
|
.isInt({ min: 0 })
|
||||||
@@ -332,18 +335,19 @@ export const validateSetupAdmin = [
|
|||||||
.withMessage('First name must be between 1-100 characters')
|
.withMessage('First name must be between 1-100 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('lastname')
|
body('lastname')
|
||||||
.isLength({ min: 1, max: 100 })
|
.isLength({ min: 1, max: 100 })
|
||||||
.withMessage('Last name must be between 1-100 characters')
|
.withMessage('Last name must be between 1-100 characters')
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
body('password')
|
body('password')
|
||||||
|
.optional()
|
||||||
.isLength({ min: 8 })
|
.isLength({ min: 8 })
|
||||||
.withMessage('Password must be at least 8 characters')
|
.withMessage('Password must be at least 8 characters')
|
||||||
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
|
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/)
|
||||||
.withMessage('Password must contain uppercase, lowercase and number')
|
.withMessage('Password must contain uppercase, lowercase, number and special character'),
|
||||||
];
|
];
|
||||||
|
|
||||||
// ===== SCHEDULING VALIDATION =====
|
// ===== SCHEDULING VALIDATION =====
|
||||||
@@ -351,23 +355,23 @@ export const validateSchedulingRequest = [
|
|||||||
body('shiftPlan')
|
body('shiftPlan')
|
||||||
.isObject()
|
.isObject()
|
||||||
.withMessage('Shift plan is required'),
|
.withMessage('Shift plan is required'),
|
||||||
|
|
||||||
body('shiftPlan.id')
|
body('shiftPlan.id')
|
||||||
.isUUID()
|
.isUUID()
|
||||||
.withMessage('Shift plan ID must be a valid UUID'),
|
.withMessage('Shift plan ID must be a valid UUID'),
|
||||||
|
|
||||||
body('employees')
|
body('employees')
|
||||||
.isArray({ min: 1 })
|
.isArray({ min: 1 })
|
||||||
.withMessage('At least one employee is required'),
|
.withMessage('At least one employee is required'),
|
||||||
|
|
||||||
body('employees.*.id')
|
body('employees.*.id')
|
||||||
.isUUID()
|
.isUUID()
|
||||||
.withMessage('Each employee must have a valid UUID'),
|
.withMessage('Each employee must have a valid UUID'),
|
||||||
|
|
||||||
body('availabilities')
|
body('availabilities')
|
||||||
.isArray()
|
.isArray()
|
||||||
.withMessage('Availabilities must be an array'),
|
.withMessage('Availabilities must be an array'),
|
||||||
|
|
||||||
body('constraints')
|
body('constraints')
|
||||||
.optional()
|
.optional()
|
||||||
.isArray()
|
.isArray()
|
||||||
@@ -379,19 +383,19 @@ export const validateAvailabilities = [
|
|||||||
body('planId')
|
body('planId')
|
||||||
.isUUID()
|
.isUUID()
|
||||||
.withMessage('Plan ID must be a valid UUID'),
|
.withMessage('Plan ID must be a valid UUID'),
|
||||||
|
|
||||||
body('availabilities')
|
body('availabilities')
|
||||||
.isArray()
|
.isArray()
|
||||||
.withMessage('Availabilities must be an array'),
|
.withMessage('Availabilities must be an array'),
|
||||||
|
|
||||||
body('availabilities.*.shiftId')
|
body('availabilities.*.shiftId')
|
||||||
.isUUID()
|
.isUUID()
|
||||||
.withMessage('Each shift ID must be a valid UUID'),
|
.withMessage('Each shift ID must be a valid UUID'),
|
||||||
|
|
||||||
body('availabilities.*.preferenceLevel')
|
body('availabilities.*.preferenceLevel')
|
||||||
.isInt({ min: 0, max: 2 })
|
.isInt({ min: 0, max: 2 })
|
||||||
.withMessage('Preference level must be 0 (unavailable), 1 (available), or 2 (preferred)'),
|
.withMessage('Preference level must be 0 (unavailable), 1 (available), or 2 (preferred)'),
|
||||||
|
|
||||||
body('availabilities.*.notes')
|
body('availabilities.*.notes')
|
||||||
.optional()
|
.optional()
|
||||||
.isLength({ max: 500 })
|
.isLength({ max: 500 })
|
||||||
@@ -424,12 +428,12 @@ export const validatePagination = [
|
|||||||
.optional()
|
.optional()
|
||||||
.isInt({ min: 1 })
|
.isInt({ min: 1 })
|
||||||
.withMessage('Page must be a positive integer'),
|
.withMessage('Page must be a positive integer'),
|
||||||
|
|
||||||
query('limit')
|
query('limit')
|
||||||
.optional()
|
.optional()
|
||||||
.isInt({ min: 1, max: 100 })
|
.isInt({ min: 1, max: 100 })
|
||||||
.withMessage('Limit must be between 1-100'),
|
.withMessage('Limit must be between 1-100'),
|
||||||
|
|
||||||
query('includeInactive')
|
query('includeInactive')
|
||||||
.optional()
|
.optional()
|
||||||
.isBoolean()
|
.isBoolean()
|
||||||
@@ -439,19 +443,19 @@ export const validatePagination = [
|
|||||||
// ===== MIDDLEWARE TO CHECK VALIDATION RESULTS =====
|
// ===== MIDDLEWARE TO CHECK VALIDATION RESULTS =====
|
||||||
export const handleValidationErrors = (req: Request, res: Response, next: NextFunction) => {
|
export const handleValidationErrors = (req: Request, res: Response, next: NextFunction) => {
|
||||||
const errors = validationResult(req);
|
const errors = validationResult(req);
|
||||||
|
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
const errorMessages = errors.array().map(error => ({
|
const errorMessages = errors.array().map(error => ({
|
||||||
field: error.type === 'field' ? error.path : error.type,
|
field: error.type === 'field' ? error.path : error.type,
|
||||||
message: error.msg,
|
message: error.msg,
|
||||||
value: error.msg
|
value: error.msg
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'Validation failed',
|
error: 'Validation failed',
|
||||||
details: errorMessages
|
details: errorMessages
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
@@ -22,6 +22,8 @@ const app = express();
|
|||||||
const PORT = 3002;
|
const PORT = 3002;
|
||||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
app.set('trust proxy', true);
|
||||||
|
|
||||||
// Security configuration
|
// Security configuration
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
console.info('Checking for JWT_SECRET');
|
console.info('Checking for JWT_SECRET');
|
||||||
@@ -34,14 +36,20 @@ if (process.env.NODE_ENV === 'production') {
|
|||||||
|
|
||||||
// Security headers
|
// Security headers
|
||||||
app.use(helmet({
|
app.use(helmet({
|
||||||
contentSecurityPolicy: isDevelopment ? false : {
|
contentSecurityPolicy: {
|
||||||
directives: {
|
directives: {
|
||||||
defaultSrc: ["'self'"],
|
defaultSrc: ["'self'"],
|
||||||
scriptSrc: ["'self'", "'unsafe-inline'"],
|
scriptSrc: ["'self'", "'unsafe-inline'"],
|
||||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||||
imgSrc: ["'self'", "data:", "https:"],
|
imgSrc: ["'self'", "data:", "https:"],
|
||||||
|
connectSrc: ["'self'"],
|
||||||
|
fontSrc: ["'self'"],
|
||||||
|
objectSrc: ["'none'"],
|
||||||
|
mediaSrc: ["'self'"],
|
||||||
|
frameSrc: ["'none'"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
hsts: false,
|
||||||
crossOriginEmbedderPolicy: false
|
crossOriginEmbedderPolicy: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// frontend/src/pages/Features/Features.tsx
|
// frontend/src/components/Layou/FooterLinks/Features/Features.tsx
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const Features: React.FC = () => {
|
const Features: React.FC = () => {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ interface AuthContextType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || '/api';
|
const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
|
||||||
|
|
||||||
interface AuthProviderProps {
|
interface AuthProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -66,7 +66,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|||||||
try {
|
try {
|
||||||
const token = getStoredToken();
|
const token = getStoredToken();
|
||||||
console.log('🔄 Refreshing user, token exists:', !!token);
|
console.log('🔄 Refreshing user, token exists:', !!token);
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.log('ℹ️ No token found, user not logged in');
|
console.log('ℹ️ No token found, user not logged in');
|
||||||
setUser(null);
|
setUser(null);
|
||||||
@@ -104,7 +104,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|||||||
const login = async (credentials: LoginRequest): Promise<void> => {
|
const login = async (credentials: LoginRequest): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
console.log('🔐 Attempting login for:', credentials.email);
|
console.log('🔐 Attempting login for:', credentials.email);
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -120,7 +120,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('✅ Login successful, storing token');
|
console.log('✅ Login successful, storing token');
|
||||||
|
|
||||||
setStoredToken(data.token);
|
setStoredToken(data.token);
|
||||||
setUser(data.user);
|
setUser(data.user);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -137,13 +137,13 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|||||||
|
|
||||||
const hasRole = (roles: string[]): boolean => {
|
const hasRole = (roles: string[]): boolean => {
|
||||||
if (!user || !user.roles || user.roles.length === 0) return false;
|
if (!user || !user.roles || user.roles.length === 0) return false;
|
||||||
|
|
||||||
// Check if user has at least one of the required roles
|
// Check if user has at least one of the required roles
|
||||||
return roles.some(requiredRole =>
|
return roles.some(requiredRole =>
|
||||||
user.roles!.includes(requiredRole)
|
user.roles!.includes(requiredRole)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeAuth = async () => {
|
const initializeAuth = async () => {
|
||||||
console.log('🚀 Initializing authentication...');
|
console.log('🚀 Initializing authentication...');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// frontend/src/services/authService.ts
|
// frontend/src/services/authService.ts
|
||||||
import { Employee } from '../models/Employee';
|
import { Employee } from '../models/Employee';
|
||||||
const API_BASE = process.env.REACT_APP_API_BASE_URL || '/api';
|
const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
|
||||||
|
|
||||||
export interface LoginRequest {
|
export interface LoginRequest {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -24,7 +24,7 @@ class AuthService {
|
|||||||
private token: string | null = null;
|
private token: string | null = null;
|
||||||
|
|
||||||
async login(credentials: LoginRequest): Promise<AuthResponse> {
|
async login(credentials: LoginRequest): Promise<AuthResponse> {
|
||||||
const response = await fetch(`${API_BASE}/auth/login`, {
|
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(credentials)
|
body: JSON.stringify(credentials)
|
||||||
@@ -39,12 +39,11 @@ class AuthService {
|
|||||||
this.token = data.token;
|
this.token = data.token;
|
||||||
localStorage.setItem('token', data.token);
|
localStorage.setItem('token', data.token);
|
||||||
localStorage.setItem('employee', JSON.stringify(data.employee));
|
localStorage.setItem('employee', JSON.stringify(data.employee));
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async register(userData: RegisterRequest): Promise<AuthResponse> {
|
async register(userData: RegisterRequest): Promise<AuthResponse> {
|
||||||
const response = await fetch(`${API_BASE}/employees`, {
|
const response = await fetch(`${API_BASE_URL}/employees`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(userData)
|
body: JSON.stringify(userData)
|
||||||
@@ -73,7 +72,7 @@ class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/auth/me`, {
|
const response = await fetch(`${API_BASE_URL}/auth/me`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,166 +1,59 @@
|
|||||||
|
// vite.config.ts
|
||||||
import { defineConfig, loadEnv } from 'vite'
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
|
|
||||||
// Security-focused Vite configuration
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const isProduction = mode === 'production'
|
const isProduction = mode === 'production'
|
||||||
const isDevelopment = mode === 'development'
|
const isDevelopment = mode === 'development'
|
||||||
|
|
||||||
// Load environment variables securely
|
|
||||||
const env = loadEnv(mode, process.cwd(), '')
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
|
|
||||||
// Strictly defined client-safe environment variables
|
// 🆕 WICHTIG: Relative Pfade für Production
|
||||||
const clientEnv = {
|
const clientEnv = {
|
||||||
NODE_ENV: mode,
|
NODE_ENV: mode,
|
||||||
ENABLE_PRO: env.ENABLE_PRO || 'false',
|
ENABLE_PRO: env.ENABLE_PRO || 'false',
|
||||||
VITE_APP_TITLE: env.APP_TITLE || 'Shift Planning App',
|
VITE_APP_TITLE: env.APP_TITLE || 'Shift Planning App',
|
||||||
VITE_API_URL: isProduction ? '/api' : 'http://localhost:3002/api',
|
VITE_API_URL: isProduction ? '/api' : '/api',
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [
|
plugins: [react()],
|
||||||
react({
|
|
||||||
// React specific security settings
|
|
||||||
jsxRuntime: 'automatic',
|
|
||||||
babel: {
|
|
||||||
plugins: [
|
|
||||||
// Remove console in production
|
|
||||||
isProduction && ['babel-plugin-transform-remove-console', { exclude: ['error', 'warn'] }]
|
|
||||||
].filter(Boolean)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
],
|
|
||||||
|
|
||||||
server: {
|
server: {
|
||||||
port: 3003,
|
port: 3003,
|
||||||
host: true,
|
host: true,
|
||||||
open: isDevelopment,
|
open: isDevelopment,
|
||||||
// Security headers for dev server
|
|
||||||
headers: {
|
|
||||||
'X-Content-Type-Options': 'nosniff',
|
|
||||||
'X-Frame-Options': 'DENY',
|
|
||||||
'X-XSS-Protection': '1; mode=block',
|
|
||||||
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
||||||
'Permissions-Policy': 'camera=(), microphone=(), location=()'
|
|
||||||
},
|
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:3002',
|
target: 'http://localhost:3002',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// Security: disable HMR in non-dev environments
|
|
||||||
hmr: isDevelopment
|
|
||||||
},
|
},
|
||||||
|
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
// Security: No source maps in production
|
sourcemap: isDevelopment,
|
||||||
sourcemap: isDevelopment ? 'inline' : false,
|
base: isProduction ? '/' : '/',
|
||||||
// Generate deterministic hashes for better caching and security
|
|
||||||
assetsDir: 'assets',
|
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
// Security: Use content hashes for cache busting and integrity
|
|
||||||
chunkFileNames: 'assets/[name]-[hash].js',
|
chunkFileNames: 'assets/[name]-[hash].js',
|
||||||
entryFileNames: 'assets/[name]-[hash].js',
|
entryFileNames: 'assets/[name]-[hash].js',
|
||||||
assetFileNames: 'assets/[name]-[hash].[ext]',
|
assetFileNames: 'assets/[name]-[hash].[ext]',
|
||||||
// Security: Manual chunks to separate vendor code
|
|
||||||
manualChunks: (id) => {
|
|
||||||
if (id.includes('node_modules')) {
|
|
||||||
if (id.includes('react') || id.includes('react-dom')) {
|
|
||||||
return 'vendor-react'
|
|
||||||
}
|
|
||||||
if (id.includes('react-router-dom')) {
|
|
||||||
return 'vendor-router'
|
|
||||||
}
|
|
||||||
return 'vendor'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Minification with security-focused settings
|
|
||||||
minify: isProduction ? 'terser' : false,
|
minify: isProduction ? 'terser' : false,
|
||||||
terserOptions: isProduction ? {
|
terserOptions: isProduction ? {
|
||||||
compress: {
|
compress: {
|
||||||
drop_console: true,
|
drop_console: true,
|
||||||
drop_debugger: true,
|
drop_debugger: true,
|
||||||
// Security: Remove potentially sensitive code
|
pure_funcs: ['console.log', 'console.debug', 'console.info']
|
||||||
pure_funcs: [
|
|
||||||
'console.log',
|
|
||||||
'console.info',
|
|
||||||
'console.debug',
|
|
||||||
'console.warn',
|
|
||||||
'console.trace',
|
|
||||||
'console.table',
|
|
||||||
'debugger'
|
|
||||||
],
|
|
||||||
dead_code: true,
|
|
||||||
if_return: true,
|
|
||||||
comparisons: true,
|
|
||||||
loops: true,
|
|
||||||
hoist_funs: true,
|
|
||||||
hoist_vars: true,
|
|
||||||
reduce_vars: true,
|
|
||||||
booleans: true,
|
|
||||||
conditionals: true,
|
|
||||||
evaluate: true,
|
|
||||||
sequences: true,
|
|
||||||
unused: true
|
|
||||||
},
|
|
||||||
mangle: {
|
|
||||||
// Security: Obfuscate code
|
|
||||||
toplevel: true,
|
|
||||||
keep_classnames: false,
|
|
||||||
keep_fnames: false,
|
|
||||||
reserved: [
|
|
||||||
'React',
|
|
||||||
'ReactDOM',
|
|
||||||
'useState',
|
|
||||||
'useEffect',
|
|
||||||
'useContext',
|
|
||||||
'createElement'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
format: {
|
|
||||||
comments: false,
|
|
||||||
beautify: false,
|
|
||||||
// Security: ASCII only to prevent encoding attacks
|
|
||||||
ascii_only: true
|
|
||||||
}
|
}
|
||||||
} : undefined,
|
} : undefined,
|
||||||
// Security: Report bundle size issues
|
|
||||||
reportCompressedSize: true,
|
|
||||||
chunkSizeWarningLimit: 1000,
|
|
||||||
// Security: Don't expose source paths
|
|
||||||
assetsInlineLimit: 4096
|
|
||||||
},
|
},
|
||||||
|
|
||||||
preview: {
|
|
||||||
port: 3004,
|
|
||||||
headers: {
|
|
||||||
// Security headers for preview server
|
|
||||||
'X-Content-Type-Options': 'nosniff',
|
|
||||||
'X-Frame-Options': 'DENY',
|
|
||||||
'X-XSS-Protection': '1; mode=block',
|
|
||||||
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
||||||
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
||||||
'Content-Security-Policy': `
|
|
||||||
default-src 'self';
|
|
||||||
script-src 'self' 'unsafe-inline';
|
|
||||||
style-src 'self' 'unsafe-inline';
|
|
||||||
img-src 'self' data: https:;
|
|
||||||
font-src 'self';
|
|
||||||
connect-src 'self';
|
|
||||||
base-uri 'self';
|
|
||||||
form-action 'self';
|
|
||||||
frame-ancestors 'none';
|
|
||||||
`.replace(/\s+/g, ' ').trim()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': resolve(__dirname, './src'),
|
'@': resolve(__dirname, './src'),
|
||||||
@@ -173,31 +66,10 @@ export default defineConfig(({ mode }) => {
|
|||||||
'@/design': resolve(__dirname, './src/design')
|
'@/design': resolve(__dirname, './src/design')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// ✅ SICHER: Strict environment variable control
|
|
||||||
define: Object.keys(clientEnv).reduce((acc, key) => {
|
define: Object.keys(clientEnv).reduce((acc, key) => {
|
||||||
acc[`import.meta.env.${key}`] = JSON.stringify(clientEnv[key])
|
acc[`import.meta.env.${key}`] = JSON.stringify(clientEnv[key])
|
||||||
return acc
|
return acc
|
||||||
}, {} as Record<string, string>),
|
}, {} as Record<string, string>)
|
||||||
|
|
||||||
// Security: Clear build directory
|
|
||||||
emptyOutDir: true,
|
|
||||||
|
|
||||||
// Security: Optimize dependencies
|
|
||||||
optimizeDeps: {
|
|
||||||
include: ['react', 'react-dom', 'react-router-dom'],
|
|
||||||
exclude: ['@vitejs/plugin-react']
|
|
||||||
},
|
|
||||||
|
|
||||||
// Security: CSS configuration
|
|
||||||
css: {
|
|
||||||
devSourcemap: isDevelopment,
|
|
||||||
modules: {
|
|
||||||
localsConvention: 'camelCase',
|
|
||||||
generateScopedName: isProduction
|
|
||||||
? '[hash:base64:8]'
|
|
||||||
: '[name]__[local]--[hash:base64:5]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
125
package-lock.json
generated
125
package-lock.json
generated
@@ -280,6 +280,7 @@
|
|||||||
"backend/node_modules/@types/node": {
|
"backend/node_modules/@types/node": {
|
||||||
"version": "24.7.0",
|
"version": "24.7.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.14.0"
|
"undici-types": "~7.14.0"
|
||||||
}
|
}
|
||||||
@@ -349,17 +350,6 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"backend/node_modules/acorn": {
|
|
||||||
"version": "8.15.0",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"acorn": "bin/acorn"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"backend/node_modules/acorn-walk": {
|
"backend/node_modules/acorn-walk": {
|
||||||
"version": "8.3.4",
|
"version": "8.3.4",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2038,6 +2028,7 @@
|
|||||||
"frontend": {
|
"frontend": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"date-fns": "4.1.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^6.28.0"
|
"react-router-dom": "^6.28.0"
|
||||||
@@ -2048,7 +2039,9 @@
|
|||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@vitejs/plugin-react": "^4.3.3",
|
||||||
|
"babel-plugin-transform-remove-console": "6.9.4",
|
||||||
"esbuild": "^0.21.0",
|
"esbuild": "^0.21.0",
|
||||||
|
"terser": "5.44.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"vite": "^6.0.7"
|
"vite": "^6.0.7"
|
||||||
}
|
}
|
||||||
@@ -2078,6 +2071,7 @@
|
|||||||
"version": "7.28.5",
|
"version": "7.28.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
@@ -2338,6 +2332,17 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/source-map": {
|
||||||
|
"version": "0.3.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||||
|
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.25"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2388,10 +2393,6 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@schichtenplaner/premium": {
|
|
||||||
"resolved": "premium",
|
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2443,6 +2444,7 @@
|
|||||||
"version": "20.19.23",
|
"version": "20.19.23",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
@@ -2451,6 +2453,7 @@
|
|||||||
"version": "19.2.2",
|
"version": "19.2.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -2514,12 +2517,32 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/acorn": {
|
||||||
|
"version": "8.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"acorn": "bin/acorn"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/array-flatten": {
|
"node_modules/array-flatten": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/babel-plugin-transform-remove-console": {
|
||||||
|
"version": "6.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz",
|
||||||
|
"integrity": "sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.8.20",
|
"version": "2.8.20",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2585,6 +2608,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.8.19",
|
"baseline-browser-mapping": "^2.8.19",
|
||||||
"caniuse-lite": "^1.0.30001751",
|
"caniuse-lite": "^1.0.30001751",
|
||||||
@@ -2599,6 +2623,13 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-from": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -2656,6 +2687,13 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
@@ -2702,6 +2740,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
@@ -3368,6 +3416,7 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -3457,6 +3506,7 @@
|
|||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "19.2.0",
|
"version": "19.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -3464,6 +3514,7 @@
|
|||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.2.0",
|
"version": "19.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -3730,6 +3781,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -3738,6 +3799,17 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map-support": {
|
||||||
|
"version": "0.5.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
|
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"source-map": "^0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
@@ -3747,6 +3819,26 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/terser": {
|
||||||
|
"version": "5.44.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
|
||||||
|
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
|
"acorn": "^8.15.0",
|
||||||
|
"commander": "^2.20.0",
|
||||||
|
"source-map-support": "~0.5.20"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"terser": "bin/terser"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.15",
|
"version": "0.2.15",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -3788,6 +3880,7 @@
|
|||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -3870,6 +3963,7 @@
|
|||||||
"version": "6.4.1",
|
"version": "6.4.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
@@ -4002,6 +4096,7 @@
|
|||||||
"premium": {
|
"premium": {
|
||||||
"name": "@schichtenplaner/premium",
|
"name": "@schichtenplaner/premium",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"extraneous": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"backendPRO",
|
"backendPRO",
|
||||||
"frontendPRO"
|
"frontendPRO"
|
||||||
|
|||||||
Reference in New Issue
Block a user