284 lines
12 KiB
JavaScript
284 lines
12 KiB
JavaScript
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',
|
||
hotel_phone_primary: '89409270080',
|
||
hotel_phone_additional: '',
|
||
hotel_whatsapp_num: '89409270080',
|
||
hotel_whatsapp_active: 'true',
|
||
hotel_telegram_id: '+89409270080',
|
||
hotel_telegram_active: 'true',
|
||
hotel_max_link: 'kalugin66',
|
||
hotel_max_active: 'true',
|
||
hotel_instagram_id: '',
|
||
hotel_instagram_active: 'true',
|
||
telegram_bot_token: '',
|
||
telegram_notify_enabled: 'false',
|
||
telegram_notify_users: ''
|
||
};
|
||
|
||
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}`,
|
||
warning: 'Ключ сохранён в .env. После перезапуска сервера старый ключ перестанет работать.'
|
||
});
|
||
});
|
||
|
||
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 });
|
||
});
|
||
});
|
||
});
|
||
|
||
app.get('/api/admin/settings/phones', authenticateToken, requireAdmin, (req, res) => {
|
||
getAll((err, settings) => {
|
||
if (err) return res.status(500).json({ error: 'Database error' });
|
||
res.json({
|
||
primary: settings.hotel_phone_primary || '',
|
||
additional: settings.hotel_phone_additional || '',
|
||
whatsappNum: settings.hotel_whatsapp_num || '',
|
||
whatsappActive: settings.hotel_whatsapp_active || 'true',
|
||
telegramId: settings.hotel_telegram_id || '',
|
||
telegramActive: settings.hotel_telegram_active || 'true',
|
||
maxLink: settings.hotel_max_link || '',
|
||
maxActive: settings.hotel_max_active || 'true',
|
||
instagramId: settings.hotel_instagram_id || '',
|
||
instagramActive: settings.hotel_instagram_active || 'true'
|
||
});
|
||
});
|
||
});
|
||
|
||
app.put('/api/admin/settings/phones', authenticateToken, requireAdmin, (req, res) => {
|
||
const { primary, additional, whatsappNum, whatsappActive, telegramId, telegramActive, maxLink, maxActive, instagramId, instagramActive } = req.body;
|
||
if (!primary || primary.length < 5) {
|
||
return res.status(400).json({ error: 'Основной номер должен содержать минимум 5 цифр' });
|
||
}
|
||
const updates = [];
|
||
updates.push(['hotel_phone_primary', primary]);
|
||
updates.push(['hotel_phone_additional', additional || '']);
|
||
updates.push(['hotel_whatsapp_num', whatsappNum || '']);
|
||
updates.push(['hotel_whatsapp_active', whatsappActive || 'true']);
|
||
updates.push(['hotel_telegram_id', telegramId || '']);
|
||
updates.push(['hotel_telegram_active', telegramActive || 'true']);
|
||
updates.push(['hotel_max_link', maxLink || '']);
|
||
updates.push(['hotel_max_active', maxActive || 'true']);
|
||
updates.push(['hotel_instagram_id', instagramId || '']);
|
||
updates.push(['hotel_instagram_active', instagramActive || 'true']);
|
||
let processed = 0;
|
||
updates.forEach(([key, value]) => {
|
||
set(key, value, (err) => {
|
||
if (err) console.error('Phone settings update error:', err);
|
||
processed++;
|
||
if (processed === updates.length) {
|
||
res.json({ message: 'Настройки телефонов сохранены' });
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
app.get('/api/config', (req, res) => {
|
||
getAll((err, settings) => {
|
||
if (err) return res.status(500).json({ error: 'Database error' });
|
||
res.json({
|
||
phone_primary: settings.hotel_phone_primary || '89409270080',
|
||
phone_additional: settings.hotel_phone_additional || '',
|
||
whatsapp_num: settings.hotel_whatsapp_num || '89409270080',
|
||
whatsapp_active: settings.hotel_whatsapp_active || 'true',
|
||
telegram_id: settings.hotel_telegram_id || '+89409270080',
|
||
telegram_active: settings.hotel_telegram_active || 'true',
|
||
max_link: settings.hotel_max_link || 'kalugin66',
|
||
max_active: settings.hotel_max_active || 'true',
|
||
instagram_id: settings.hotel_instagram_id || '',
|
||
instagram_active: settings.hotel_instagram_active || 'true'
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
module.exports = { init, get, set, getAll, getReviewCode, setReviewCode, checkIpCooldown, setupRoutes };
|