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); });