const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); let db; const DEFAULT_SETTINGS = { review_code: 'GUEST2026', review_min_length: '20', review_ip_cooldown_minutes: '5' }; function init(database) { db = database; initDefaultSettings(); } function initDefaultSettings() { Object.entries(DEFAULT_SETTINGS).forEach(([key, value]) => { db.run(`INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)`, [key, value], (err) => { if (err) console.error('Settings init error:', err); }); }); } function get(key, callback) { db.get(`SELECT value FROM settings WHERE key = ?`, [key], (err, row) => { if (err) return callback(err, null); callback(null, row ? row.value : null); }); } function set(key, value, callback) { db.run( `INSERT INTO settings (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = CURRENT_TIMESTAMP`, [key, value, value], function(err) { if (err) return callback(err); callback(null); } ); } function getAll(callback) { db.all(`SELECT * FROM settings`, [], (err, rows) => { if (err) return callback(err, null); const settings = {}; rows.forEach(row => { settings[row.key] = row.value; }); callback(null, settings); }); } function getReviewCode(callback) { get('review_code', callback); } function setReviewCode(value, callback) { set('review_code', value, callback); } function checkIpCooldown(ip, callback) { const cooldownMinutes = 5; const cutoffTime = new Date(Date.now() - cooldownMinutes * 60 * 1000).toISOString(); db.get( `SELECT id FROM reviews WHERE ip_address = ? AND created_at > ? LIMIT 1`, [ip, cutoffTime], (err, row) => { if (err) return callback(err, false); callback(null, !!row); } ); } function maskValue(val) { if (!val || val.length <= 4) return '****'; return '****' + val.slice(-4); } function setupRoutes(app, authenticateToken, requireAdmin) { app.get('/api/admin/settings', authenticateToken, requireAdmin, (req, res) => { getAll((err, settings) => { if (err) return res.status(500).json({ error: 'Database error' }); if (settings.review_code) { settings.review_code_masked = maskValue(settings.review_code); } res.json(settings); }); }); app.get('/api/admin/settings/review-code', authenticateToken, requireAdmin, (req, res) => { getReviewCode((err, code) => { if (err) return res.status(500).json({ error: 'Database error' }); res.json({ code: code }); }); }); app.put('/api/admin/settings/review-code', authenticateToken, requireAdmin, (req, res) => { const { code } = req.body; if (!code || code.length < 3) { return res.status(400).json({ error: 'Code must be at least 3 characters' }); } setReviewCode(code, (err) => { if (err) return res.status(500).json({ error: 'Database error' }); res.json({ message: 'Review code updated', code: code }); }); }); app.get('/api/admin/security', authenticateToken, requireAdmin, (req, res) => { const jwtSecret = process.env.JWT_SECRET || ''; const hotelKey = process.env.HOTEL777KEY || ''; res.json({ jwt_secret_masked: maskValue(jwtSecret), hotel777key_masked: maskValue(hotelKey) }); }); app.post('/api/admin/security/regenerate', authenticateToken, requireAdmin, (req, res) => { const { type } = req.body; if (!['jwt', 'hotel777key'].includes(type)) { return res.status(400).json({ error: 'Type must be "jwt" or "hotel777key"' }); } const secret = crypto.randomBytes(32).toString('hex'); const envVarName = type === 'jwt' ? 'JWT_SECRET' : 'HOTEL777KEY'; const envPath = path.join(__dirname, '..', '..', '.env'); let envContent = ''; if (fs.existsSync(envPath)) { envContent = fs.readFileSync(envPath, 'utf8'); } if (envContent.includes(`${envVarName}=`)) { envContent = envContent.replace(new RegExp(`${envVarName}=.*`, 'g'), `${envVarName}=${secret}`); } else { envContent += `\n${envVarName}=${secret}\n`; } fs.writeFileSync(envPath, envContent); process.env[envVarName] = secret; console.log(`🔐 ${envVarName} regenerated by admin`); const jwtWarning = type === 'jwt' ? ' Все ранее выданные токены доступа стали недействительными. Пользователям потребуется войти заново.' : ''; res.json({ message: `${envVarName} перегенерирован. НЕОБХОДИМО перезапустить сервер для применения нового ключа.${jwtWarning}`, key: secret, warning: 'Сохраните этот ключ. Он будет показан только один раз. После перезапуска сервера старый ключ перестанет работать.' }); }); app.get('/api/admin/settings/email', authenticateToken, requireAdmin, (req, res) => { getAll((err, settings) => { if (err) return res.status(500).json({ error: 'Database error' }); res.json({ smtp_host: settings.smtp_host || '', smtp_port: settings.smtp_port || '587', smtp_secure: settings.smtp_secure || 'false', smtp_user: settings.smtp_user || '', smtp_pass_masked: settings.smtp_pass ? maskValue(settings.smtp_pass) : '', smtp_from: settings.smtp_from || '', email_notifications_enabled: settings.email_notifications_enabled || 'false', admin_email: settings.admin_email || '' }); }); }); app.put('/api/admin/settings/email', authenticateToken, requireAdmin, (req, res) => { const { smtp_host, smtp_port, smtp_secure, smtp_user, smtp_pass, smtp_from, email_notifications_enabled, admin_email } = req.body; const updates = []; if (smtp_host !== undefined) updates.push(['smtp_host', smtp_host]); if (smtp_port !== undefined) updates.push(['smtp_port', smtp_port]); if (smtp_secure !== undefined) updates.push(['smtp_secure', smtp_secure]); if (smtp_user !== undefined) updates.push(['smtp_user', smtp_user]); if (smtp_pass !== undefined && smtp_pass !== '' && !smtp_pass.startsWith('****')) updates.push(['smtp_pass', smtp_pass]); if (smtp_from !== undefined) updates.push(['smtp_from', smtp_from]); if (email_notifications_enabled !== undefined) updates.push(['email_notifications_enabled', email_notifications_enabled]); if (admin_email !== undefined) updates.push(['admin_email', admin_email]); let processed = 0; if (updates.length === 0) return res.json({ message: 'No changes' }); updates.forEach(([key, value]) => { set(key, value, (err) => { if (err) console.error('Email settings update error:', err); processed++; if (processed === updates.length) { res.json({ message: 'Email settings updated' }); } }); }); }); app.post('/api/admin/settings/email/test', authenticateToken, requireAdmin, (req, res) => { const emailModule = require('../email'); getAll((err, settings) => { if (err) return res.status(500).json({ error: 'Database error' }); const to = settings.admin_email || settings.smtp_user || ''; if (!to) return res.status(400).json({ error: 'Email получателя не настроен. Укажите admin_email в настройках.' }); if (!settings.smtp_host) return res.status(400).json({ error: 'SMTP хост не настроен.' }); emailModule.sendTestEmail(settings, to, (sendErr, info) => { if (sendErr) return res.status(500).json({ error: 'Ошибка отправки: ' + sendErr.message }); res.json({ message: 'Тестовое письмо отправлено на ' + to }); }); }); }); } module.exports = { init, get, set, getAll, getReviewCode, setReviewCode, checkIpCooldown, setupRoutes };