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}`); });