Initial commit
Dieser Commit ist enthalten in:
332
backend/full-backend-3005.js
Normale Datei
332
backend/full-backend-3005.js
Normale Datei
@ -0,0 +1,332 @@
|
||||
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);
|
||||
});
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren