332 Zeilen
8.7 KiB
JavaScript
332 Zeilen
8.7 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const Database = require('better-sqlite3');
|
|
const bcryptjs = require('bcryptjs');
|
|
const CryptoJS = require('crypto-js');
|
|
const path = require('path');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3005;
|
|
|
|
// Environment variables
|
|
const FIELD_ENCRYPTION_KEY = process.env.FIELD_ENCRYPTION_KEY || 'dev_field_key_change_in_production_32chars_min!';
|
|
|
|
// Database setup
|
|
const dbPath = path.join(__dirname, 'skillmate.dev.db');
|
|
const db = new Database(dbPath);
|
|
|
|
console.log('Database path:', dbPath);
|
|
|
|
// Middleware
|
|
app.use(cors({
|
|
origin: ['http://localhost:5173', 'http://127.0.0.1:5173', 'http://localhost:3006', 'http://127.0.0.1:3006'],
|
|
credentials: true
|
|
}));
|
|
app.use(express.json());
|
|
|
|
// Encryption/Decryption functions
|
|
function encrypt(text) {
|
|
if (!text) return null;
|
|
return CryptoJS.AES.encrypt(text, FIELD_ENCRYPTION_KEY).toString();
|
|
}
|
|
|
|
function decrypt(encryptedText) {
|
|
if (!encryptedText) return null;
|
|
try {
|
|
const bytes = CryptoJS.AES.decrypt(encryptedText, FIELD_ENCRYPTION_KEY);
|
|
return bytes.toString(CryptoJS.enc.Utf8);
|
|
} catch (error) {
|
|
console.error('Decryption error:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Login endpoint
|
|
app.post('/api/auth/login', async (req, res) => {
|
|
console.log('Login attempt:', req.body);
|
|
|
|
const { username, password } = req.body;
|
|
|
|
if (!username || !password) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: { message: 'Email and password are required' }
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Find user by email (encrypted)
|
|
const users = db.prepare('SELECT * FROM users WHERE is_active = 1').all();
|
|
let user = null;
|
|
|
|
// Check encrypted emails
|
|
for (const u of users) {
|
|
const decryptedEmail = decrypt(u.email);
|
|
if (decryptedEmail === username || u.email === username) {
|
|
user = u;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!user) {
|
|
console.log('User not found:', username);
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: { message: 'Invalid credentials' }
|
|
});
|
|
}
|
|
|
|
console.log('User found:', { id: user.id, username: user.username, role: user.role });
|
|
|
|
// Verify password
|
|
const isValidPassword = bcryptjs.compareSync(password, user.password);
|
|
console.log('Password valid:', isValidPassword);
|
|
|
|
if (!isValidPassword) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: { message: 'Invalid credentials' }
|
|
});
|
|
}
|
|
|
|
// Decrypt sensitive fields
|
|
const decryptedUser = {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: decrypt(user.email) || user.email,
|
|
role: user.role,
|
|
employee_id: user.employee_id,
|
|
is_active: user.is_active,
|
|
last_login: user.last_login,
|
|
created_at: user.created_at,
|
|
updated_at: user.updated_at
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Login successful!',
|
|
user: decryptedUser
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { message: 'Internal server error' }
|
|
});
|
|
}
|
|
});
|
|
|
|
// Get all users endpoint (for admin panel)
|
|
app.get('/api/users', (req, res) => {
|
|
console.log('Get users request');
|
|
|
|
try {
|
|
const users = db.prepare('SELECT id, username, email, role, employee_id, is_active, last_login, created_at, updated_at FROM users').all();
|
|
|
|
// Decrypt sensitive fields for each user
|
|
const decryptedUsers = users.map(user => ({
|
|
id: user.id,
|
|
username: user.username,
|
|
email: decrypt(user.email) || user.email,
|
|
role: user.role,
|
|
employee_id: user.employee_id,
|
|
is_active: user.is_active,
|
|
last_login: user.last_login,
|
|
created_at: user.created_at,
|
|
updated_at: user.updated_at
|
|
}));
|
|
|
|
console.log(`Returning ${decryptedUsers.length} users`);
|
|
|
|
res.json({
|
|
success: true,
|
|
users: decryptedUsers
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Get users error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { message: 'Internal server error' }
|
|
});
|
|
}
|
|
});
|
|
|
|
// Get all employees endpoint
|
|
app.get('/api/employees', (req, res) => {
|
|
console.log('Get employees request');
|
|
|
|
try {
|
|
const employees = db.prepare('SELECT * FROM employees ORDER BY last_name, first_name').all();
|
|
|
|
// Decrypt sensitive fields for each employee
|
|
const decryptedEmployees = employees.map(emp => ({
|
|
...emp,
|
|
first_name: decrypt(emp.first_name) || emp.first_name,
|
|
last_name: decrypt(emp.last_name) || emp.last_name,
|
|
email: decrypt(emp.email) || emp.email,
|
|
phone: decrypt(emp.phone) || emp.phone,
|
|
mobile: decrypt(emp.mobile) || emp.mobile
|
|
}));
|
|
|
|
console.log(`Returning ${decryptedEmployees.length} employees`);
|
|
|
|
res.json({
|
|
success: true,
|
|
employees: decryptedEmployees
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Get employees error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { message: 'Internal server error' }
|
|
});
|
|
}
|
|
});
|
|
|
|
// Create employee endpoint
|
|
app.post('/api/employees', (req, res) => {
|
|
console.log('Create employee request:', req.body);
|
|
|
|
const {
|
|
first_name,
|
|
last_name,
|
|
employee_number,
|
|
position,
|
|
department,
|
|
email,
|
|
phone,
|
|
mobile,
|
|
office,
|
|
availability = 'available',
|
|
clearance_level,
|
|
clearance_valid_until,
|
|
clearance_issued_date
|
|
} = req.body;
|
|
|
|
if (!first_name || !last_name || !employee_number || !position || !department || !email || !phone) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: { message: 'Required fields missing' }
|
|
});
|
|
}
|
|
|
|
try {
|
|
const crypto = require('crypto');
|
|
const employeeId = crypto.randomUUID();
|
|
const now = new Date().toISOString();
|
|
|
|
// Encrypt sensitive fields
|
|
const encryptedEmployee = {
|
|
id: employeeId,
|
|
first_name: encrypt(first_name),
|
|
last_name: encrypt(last_name),
|
|
employee_number,
|
|
photo: null,
|
|
position,
|
|
department,
|
|
email: encrypt(email),
|
|
phone: encrypt(phone),
|
|
mobile: mobile ? encrypt(mobile) : null,
|
|
office,
|
|
availability,
|
|
clearance_level,
|
|
clearance_valid_until,
|
|
clearance_issued_date,
|
|
created_at: now,
|
|
updated_at: now,
|
|
created_by: 'system', // TODO: use actual user ID
|
|
updated_by: null
|
|
};
|
|
|
|
const insertStmt = db.prepare(`
|
|
INSERT INTO employees (
|
|
id, first_name, last_name, employee_number, photo, position, department,
|
|
email, phone, mobile, office, availability, clearance_level,
|
|
clearance_valid_until, clearance_issued_date, created_at, updated_at,
|
|
created_by, updated_by
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
insertStmt.run(
|
|
encryptedEmployee.id,
|
|
encryptedEmployee.first_name,
|
|
encryptedEmployee.last_name,
|
|
encryptedEmployee.employee_number,
|
|
encryptedEmployee.photo,
|
|
encryptedEmployee.position,
|
|
encryptedEmployee.department,
|
|
encryptedEmployee.email,
|
|
encryptedEmployee.phone,
|
|
encryptedEmployee.mobile,
|
|
encryptedEmployee.office,
|
|
encryptedEmployee.availability,
|
|
encryptedEmployee.clearance_level,
|
|
encryptedEmployee.clearance_valid_until,
|
|
encryptedEmployee.clearance_issued_date,
|
|
encryptedEmployee.created_at,
|
|
encryptedEmployee.updated_at,
|
|
encryptedEmployee.created_by,
|
|
encryptedEmployee.updated_by
|
|
);
|
|
|
|
console.log('Employee created successfully:', employeeId);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Employee created successfully',
|
|
employee: {
|
|
id: employeeId,
|
|
first_name,
|
|
last_name,
|
|
employee_number,
|
|
position,
|
|
department,
|
|
email,
|
|
phone,
|
|
mobile,
|
|
office,
|
|
availability,
|
|
clearance_level,
|
|
clearance_valid_until,
|
|
clearance_issued_date
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Create employee error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { message: 'Internal server error' }
|
|
});
|
|
}
|
|
});
|
|
|
|
// Health check endpoint
|
|
app.get('/api/health', (req, res) => {
|
|
res.json({ status: 'OK', message: 'Backend server is running' });
|
|
});
|
|
|
|
// Error handling middleware
|
|
app.use((error, req, res, next) => {
|
|
console.error('Unhandled error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: { message: 'Internal server error' }
|
|
});
|
|
});
|
|
|
|
// Start server
|
|
app.listen(PORT, () => {
|
|
console.log(`Backend server running on http://localhost:${PORT}`);
|
|
console.log('Database ready');
|
|
});
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGTERM', () => {
|
|
console.log('Received SIGTERM, closing database...');
|
|
db.close();
|
|
process.exit(0);
|
|
}); |