Files
hotel777-manager/server.js
2026-05-03 16:36:18 +05:00

172 lines
6.3 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.
require('dotenv').config();
const express = require('express');
const path = require('path');
const { db, normalizePhone, logAction } = require('./db');
const { sessionMiddleware, ensureAdmin, requireAdmin } = require('./auth');
const { syncBookings } = require('./sync');
const { notifyBookingUpdate } = require('./mailer');
const cron = require('node-cron');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(express.json());
app.use(sessionMiddleware);
// Статика из public
app.use(express.static(path.join(__dirname, 'public')));
// Инициализация администратора
ensureAdmin();
// === API ===
// Вход администратора
app.post('/api/login', (req, res) => {
const { login, password } = req.body;
const admin = db.prepare('SELECT * FROM admins WHERE login = ? AND password = ?').get(login, password);
if (admin) {
req.session.isAdmin = true;
res.json({ success: true });
} else {
res.status(401).json({ error: 'Неверный логин или пароль' });
}
});
// Выход
app.post('/api/logout', (req, res) => {
req.session.destroy();
res.json({ success: true });
});
// Проверка сессии
app.get('/api/me', (req, res) => {
res.json({ isAdmin: !!req.session.isAdmin });
});
// Список заявок (с фильтрами, только для админа)
app.get('/api/bookings', requireAdmin, (req, res) => {
const { status, search, client_id } = req.query;
let query = 'SELECT * FROM bookings WHERE 1=1';
const params = [];
if (status) {
query += ' AND status = ?';
params.push(status);
}
if (client_id) {
query += ' AND user_id = ?';
params.push(client_id);
}
if (search) {
query += ' AND (name LIKE ? OR phone_raw LIKE ? OR comments LIKE ?)';
params.push(`%${search}%`, `%${search}%`, `%${search}%`);
}
query += ' ORDER BY created_at DESC';
const bookings = db.prepare(query).all(...params);
res.json(bookings);
});
// Детали заявки
app.get('/api/bookings/:id', requireAdmin, (req, res) => {
const booking = db.prepare('SELECT * FROM bookings WHERE id = ?').get(req.params.id);
if (!booking) return res.status(404).json({ error: 'Заявка не найдена' });
res.json(booking);
});
// Обновление заявки (статус, комментарии, перепривязка клиента)
app.put('/api/bookings/:id', requireAdmin, (req, res) => {
const { id } = req.params;
const { status, comments, phone } = req.body; // phone для перепривязки карточки
const booking = db.prepare('SELECT * FROM bookings WHERE id = ?').get(id);
if (!booking) return res.status(404).json({ error: 'Заявка не найдена' });
const changes = {};
// Обновление статуса
if (status && status !== booking.status) {
const allowed = ['Новая', 'В работе', 'Подтверждена', 'Заселение', 'Завершена', 'Отменена'];
if (!allowed.includes(status)) {
return res.status(400).json({ error: 'Недопустимый статус' });
}
db.prepare('UPDATE bookings SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(status, id);
changes.status = { from: booking.status, to: status };
}
// Обновление комментария
if (comments !== undefined && comments !== booking.comments) {
db.prepare('UPDATE bookings SET comments = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(comments, id);
changes.comments = { from: booking.comments, to: comments };
}
// Перепривязка к другой карточке по номеру телефона
if (phone) {
const normPhone = normalizePhone(phone);
const user = db.prepare('SELECT id FROM users WHERE phone = ?').get(normPhone);
if (!user) {
return res.status(400).json({ error: 'Карточка клиента с таким телефоном не найдена' });
}
if (user.id !== booking.user_id) {
db.prepare('UPDATE bookings SET user_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(user.id, id);
changes.user_id = { from: booking.user_id, to: user.id };
}
}
// Если были изменения логируем и шлём уведомление
if (Object.keys(changes).length > 0) {
logAction(`Изменение заявки #${id}`, changes);
const updated = db.prepare('SELECT * FROM bookings WHERE id = ?').get(id);
notifyBookingUpdate(updated, changes);
}
const updatedBooking = db.prepare('SELECT * FROM bookings WHERE id = ?').get(id);
res.json(updatedBooking);
});
// Список карточек клиентов
app.get('/api/clients', requireAdmin, (req, res) => {
const { search } = req.query;
let query = 'SELECT * FROM users WHERE 1=1';
const params = [];
if (search) {
query += ' AND (phone LIKE ? OR name LIKE ?)';
params.push(`%${search}%`, `%${search}%`);
}
query += ' ORDER BY name ASC';
const clients = db.prepare(query).all(...params);
res.json(clients);
});
// Профиль клиента с его заявками (сортировка: сначала по дате убыванию, потом по статусу в алфавитном порядке)
app.get('/api/clients/:id', requireAdmin, (req, res) => {
const client = db.prepare('SELECT * FROM users WHERE id = ?').get(req.params.id);
if (!client) return res.status(404).json({ error: 'Клиент не найден' });
const bookings = db.prepare(`
SELECT * FROM bookings
WHERE user_id = ?
ORDER BY created_at DESC, status ASC
`).all(req.params.id);
res.json({ client, bookings });
});
// Ручной запуск синхронизации
app.post('/api/admin/sync', requireAdmin, async (req, res) => {
await syncBookings();
res.json({ success: true, message: 'Синхронизация запущена' });
});
// Планировщик синхронизации каждые 5 минут (опционально)
cron.schedule('*/5 * * * *', () => {
console.log('Автосинхронизация...');
syncBookings().catch(console.error);
});
// Запуск сервера
app.listen(PORT, () => {
console.log(`Сервис "${process.env.SERVICE_NAME}" запущен на порту ${PORT}`);
console.log(`Ссылка: ${process.env.SERVICE_URL}`);
});