const express = require('express'); const cors = require('cors'); const path = require('path'); const { initDB } = require('./db'); const { authenticateToken } = require('./middleware/auth'); const app = express(); const PORT = process.env.PORT || 3000; // Middleware app.use(cors()); app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ limit: '50mb', extended: true })); // Static files (for serving built frontend) app.use(express.static(path.join(__dirname, 'public'))); async function startServer() { // Initialize database const db = await initDB(); // Make db available to routes app.locals.db = db; // Auth routes (login is public) const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const { JWT_SECRET } = require('./middleware/auth'); app.post('/api/auth/login', (req, res) => { const { username, password } = req.body; if (!username || !password) return res.status(400).json({ code: 400, message: '用户名和密码不能为空' }); const user = db.get('SELECT * FROM users WHERE username = ?', [username]); if (!user) return res.status(401).json({ code: 401, message: '用户名或密码错误' }); if (user.status !== 1) return res.status(403).json({ code: 403, message: '账号已被禁用' }); if (user.role === 'sales') return res.status(403).json({ code: 403, message: '销售人员无需登录系统' }); if (!bcrypt.compareSync(password, user.password)) return res.status(401).json({ code: 401, message: '用户名或密码错误' }); const token = jwt.sign({ id: user.id, username: user.username, role: user.role, realname: user.realname }, JWT_SECRET, { expiresIn: '24h' }); res.json({ code: 200, message: '登录成功', data: { token, user: { id: user.id, username: user.username, realname: user.realname, role: user.role } } }); }); app.get('/api/auth/me', authenticateToken, (req, res) => { const user = db.get('SELECT id, username, realname, role, status FROM users WHERE id = ?', [req.user.id]); if (!user) return res.status(404).json({ code: 404, message: '用户不存在' }); res.json({ code: 200, data: user }); }); // Module routes const adminRoutes = require('./routes/admin'); const rdRoutes = require('./routes/rd'); app.use('/api/admin', authenticateToken, adminRoutes); app.use('/api/rd', authenticateToken, rdRoutes); // Health check app.get('/api/health', (req, res) => { res.json({ code: 200, message: 'OK', time: new Date().toISOString() }); }); // Fallback: serve index.html for SPA app.get('*', (req, res) => { const indexPath = path.join(__dirname, 'public', 'index.html'); const fs = require('fs'); if (fs.existsSync(indexPath)) { res.sendFile(indexPath); } else { res.status(404).json({ code: 404, message: 'Not found' }); } }); app.listen(PORT, () => { console.log(`🚀 WorkTime Server running at http://localhost:${PORT}`); }); // ===== Auto-backup cron: daily at 03:00 ===== const cron = require('node-cron'); const fsBackup = require('fs'); const { buildBackupJson, BACKUP_DIR, ensureBackupDir } = adminRoutes; function runAutoBackup() { try { ensureBackupDir(); const json = buildBackupJson(db); const date = new Date().toISOString().slice(0, 10); const filePath = `${BACKUP_DIR}/backup-${date}.json`; fsBackup.writeFileSync(filePath, json, 'utf8'); console.log(`[AutoBackup] Saved: ${filePath}`); // Purge backups older than 30 days const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000; fsBackup.readdirSync(BACKUP_DIR).forEach(f => { if (!f.endsWith('.json')) return; const fp = `${BACKUP_DIR}/${f}`; if (fsBackup.statSync(fp).mtimeMs < cutoff) { fsBackup.unlinkSync(fp); console.log(`[AutoBackup] Purged old backup: ${f}`); } }); } catch (e) { console.error('[AutoBackup] Error:', e.message); } } cron.schedule('0 3 * * *', runAutoBackup); console.log('[AutoBackup] Scheduled daily at 03:00'); } startServer().catch(err => { console.error('Failed to start server:', err); process.exit(1); }); module.exports = app;