Files
hotell777_260507/modules/settings/index.js
kalugin66 d612febefb
All checks were successful
Deploy hotel / deploy-kdo (push) Successful in 35s
телеграм
2026-07-20 19:06:49 +05:00

284 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 };