172 lines
6.3 KiB
JavaScript
172 lines
6.3 KiB
JavaScript
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}`);
|
||
}); |