чат
This commit is contained in:
24
cron-jobs.js
24
cron-jobs.js
@@ -1,5 +1,6 @@
|
||||
// cron-jobs.js
|
||||
const { logActivity } = require('./database');
|
||||
const { sendChatSummaryNotifications } = require('./notifications');
|
||||
|
||||
/**
|
||||
* Проверяет задачи с типом document_approval, у которых указан номер документа,
|
||||
@@ -87,6 +88,27 @@ function checkDocumentsForCompletion(db) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Запускает cron-задачу для отправки сводок о новых сообщениях в чате.
|
||||
* Отправка происходит раз в час.
|
||||
*/
|
||||
function startChatNotificationsCron() {
|
||||
console.log('🕐 [CRON] Запущен планировщик уведомлений чата (каждый час)');
|
||||
|
||||
// Первый запуск через 5 секунд после старта сервера, затем каждый час
|
||||
setTimeout(async () => {
|
||||
console.log('📢 [CRON] Первый запуск отправки сводок чата...');
|
||||
await sendChatSummaryNotifications();
|
||||
|
||||
// Запускаем интервал
|
||||
setInterval(async () => {
|
||||
console.log('📢 [CRON] Плановый запуск отправки сводок чата...');
|
||||
await sendChatSummaryNotifications();
|
||||
}, 60 * 60 * 1000); // 1 час
|
||||
}, 5000); // 5 секунд
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkDocumentsForCompletion
|
||||
checkDocumentsForCompletion,
|
||||
startChatNotificationsCron
|
||||
};
|
||||
@@ -774,7 +774,8 @@ function checkAndUpdateTableStructure() {
|
||||
{ name: 'vk_notifications', type: 'BOOLEAN DEFAULT false' },
|
||||
{ name: 'vk_user_id', type: 'TEXT' },
|
||||
{ name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' },
|
||||
{ name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }
|
||||
{ name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' },
|
||||
{ name: 'last_chat_notification_sent_at', type: 'TIMESTAMP' }
|
||||
],
|
||||
notification_history: [
|
||||
{ name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' },
|
||||
|
||||
162
notifications.js
162
notifications.js
@@ -232,11 +232,171 @@ function getStatusText(status) {
|
||||
return statusMap[status] || status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправляет сводку о непрочитанных сообщениях в чатах всем пользователям,
|
||||
* у которых есть такие сообщения (раз в час).
|
||||
*/
|
||||
async function sendChatSummaryNotifications() {
|
||||
const db = getDb();
|
||||
if (!db) {
|
||||
console.error('❌ База данных не доступна для отправки сводки чата');
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем всех пользователей, у которых включены email уведомления
|
||||
const users = await new Promise((resolve, reject) => {
|
||||
db.all(`
|
||||
SELECT u.id, u.name, u.email, u.login,
|
||||
COALESCE(us.email_notifications, 1) as email_notifications,
|
||||
us.notification_email,
|
||||
us.last_chat_notification_sent_at
|
||||
FROM users u
|
||||
LEFT JOIN user_settings us ON u.id = us.user_id
|
||||
WHERE u.email IS NOT NULL AND u.email != ''
|
||||
`, (err, rows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(rows || []);
|
||||
});
|
||||
});
|
||||
|
||||
for (const user of users) {
|
||||
// Проверяем настройки уведомлений
|
||||
if (!user.email_notifications) continue;
|
||||
const emailTo = user.notification_email || user.email;
|
||||
if (!emailTo) continue;
|
||||
|
||||
// Время последней отправки уведомления (или NULL – тогда берём текущее, но при логине уже должно быть установлено)
|
||||
let lastSent = user.last_chat_notification_sent_at;
|
||||
if (!lastSent) {
|
||||
// Если нет метки – устанавливаем сейчас и пропускаем (при первом входе всё равно сбросится)
|
||||
await new Promise((resolve) => {
|
||||
db.run(
|
||||
`UPDATE user_settings SET last_chat_notification_sent_at = CURRENT_TIMESTAMP WHERE user_id = ?`,
|
||||
[user.id],
|
||||
resolve
|
||||
);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Получаем задачи с количеством новых сообщений после lastSent
|
||||
const tasks = await new Promise((resolve, reject) => {
|
||||
db.all(`
|
||||
SELECT
|
||||
t.id,
|
||||
t.title,
|
||||
COUNT(m.id) as new_messages_count
|
||||
FROM tasks t
|
||||
LEFT JOIN task_chat_messages m ON t.id = m.task_id
|
||||
AND m.created_at > ?
|
||||
AND m.is_deleted = 0
|
||||
WHERE (t.created_by = ? OR EXISTS (
|
||||
SELECT 1 FROM task_assignments ta
|
||||
WHERE ta.task_id = t.id AND ta.user_id = ?
|
||||
))
|
||||
GROUP BY t.id
|
||||
HAVING new_messages_count > 0
|
||||
`, [lastSent, user.id, user.id], (err, rows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(rows || []);
|
||||
});
|
||||
});
|
||||
|
||||
if (tasks.length === 0) continue;
|
||||
|
||||
// Формируем HTML‑письмо со списком задач
|
||||
const appUrl = process.env.APP_URL || 'http://localhost:3000';
|
||||
let tasksHtml = '';
|
||||
for (const task of tasks) {
|
||||
const taskUrl = `${appUrl}/task?id=${task.id}`;
|
||||
tasksHtml += `
|
||||
<li style="margin-bottom: 15px;">
|
||||
<strong><a href="${taskUrl}" style="color: #3498db;">${escapeHtml(task.title)}</a></strong><br>
|
||||
Новых сообщений: ${task.new_messages_count}
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
const subject = `📬 Новые сообщения в чатах задач (${tasks.length} задач)`;
|
||||
const htmlContent = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; line-height: 1.6; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
.header { background: #3498db; color: white; padding: 15px; border-radius: 10px 10px 0 0; }
|
||||
.content { padding: 20px; border: 1px solid #ddd; border-top: none; }
|
||||
.footer { margin-top: 20px; font-size: 12px; color: #666; text-align: center; }
|
||||
ul { padding-left: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h2>💬 Новые сообщения в чатах</h2>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>Здравствуйте, ${escapeHtml(user.name)}!</p>
|
||||
<p>У вас есть непрочитанные сообщения в следующих задачах:</p>
|
||||
<ul>${tasksHtml}</ul>
|
||||
<p>Вы получили это письмо, потому что подписаны на уведомления о чатах.<br>
|
||||
Уведомления приходят раз в час. Чтобы отключить их, измените настройки в личном кабинете.</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>© School CRM, ${new Date().getFullYear()}</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// Отправляем email
|
||||
const result = await emailNotifications.sendEmailNotification(
|
||||
emailTo,
|
||||
subject,
|
||||
htmlContent,
|
||||
user.id,
|
||||
null,
|
||||
'chat_summary'
|
||||
);
|
||||
|
||||
if (result.sent) {
|
||||
// Обновляем время последней отправки
|
||||
await new Promise((resolve) => {
|
||||
db.run(
|
||||
`UPDATE user_settings SET last_chat_notification_sent_at = CURRENT_TIMESTAMP WHERE user_id = ?`,
|
||||
[user.id],
|
||||
resolve
|
||||
);
|
||||
});
|
||||
console.log(`✅ Отправлена сводка чата для ${user.login} (${tasks.length} задач)`);
|
||||
} else {
|
||||
console.error(`❌ Не удалось отправить сводку чата для ${user.login}: ${result.error}`);
|
||||
// Не обновляем last_sent – в следующий раз попробуем снова
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Вспомогательная функция для экранирования HTML
|
||||
*/
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/[&<>]/g, function(m) {
|
||||
if (m === '&') return '&';
|
||||
if (m === '<') return '<';
|
||||
if (m === '>') return '>';
|
||||
return m;
|
||||
});
|
||||
}
|
||||
|
||||
// Экспортируем функции
|
||||
module.exports = {
|
||||
sendTaskNotifications,
|
||||
checkUpcomingDeadlines,
|
||||
sendDeadlineNotification,
|
||||
getStatusText,
|
||||
emailNotifications // Экспортируем для доступа к методам
|
||||
emailNotifications,
|
||||
sendChatSummaryNotifications
|
||||
};
|
||||
55
server.js
55
server.js
@@ -234,7 +234,47 @@ app.post('/api/login', async (req, res) => {
|
||||
};
|
||||
|
||||
req.session.user = sessionUser;
|
||||
|
||||
|
||||
// ✅ Обновляем last_chat_notification_sent_at в user_settings
|
||||
const db = getDb();
|
||||
if (db) {
|
||||
// Проверяем, есть ли запись в user_settings
|
||||
db.get("SELECT id FROM user_settings WHERE user_id = ?", [user.id], (err, settings) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка проверки user_settings:', err);
|
||||
} else if (settings) {
|
||||
// Обновляем существующую запись
|
||||
db.run(
|
||||
`UPDATE user_settings
|
||||
SET last_chat_notification_sent_at = CURRENT_TIMESTAMP,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE user_id = ?`,
|
||||
[user.id],
|
||||
(updateErr) => {
|
||||
if (updateErr) console.error('❌ Ошибка обновления last_chat_notification_sent_at:', updateErr);
|
||||
else console.log(`✅ Сброшен счётчик уведомлений чата для пользователя ${user.login}`);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Создаём запись с настройками по умолчанию и проставляем last_chat_notification_sent_at
|
||||
db.run(
|
||||
`INSERT INTO user_settings
|
||||
(user_id, email_notifications, notification_email,
|
||||
telegram_notifications, telegram_chat_id,
|
||||
vk_notifications, vk_user_id, last_chat_notification_sent_at)
|
||||
VALUES (?, 1, ?, 0, '', 0, '', CURRENT_TIMESTAMP)`,
|
||||
[user.id, user.email || ''],
|
||||
(insertErr) => {
|
||||
if (insertErr) console.error('❌ Ошибка создания user_settings:', insertErr);
|
||||
else console.log(`✅ Созданы настройки и сброшен счётчик уведомлений чата для пользователя ${user.login}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn('⚠️ База данных не доступна, пропускаем обновление last_chat_notification_sent_at');
|
||||
}
|
||||
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка сохранения сессии:', err);
|
||||
@@ -245,9 +285,9 @@ app.post('/api/login', async (req, res) => {
|
||||
if (user.groups) {
|
||||
console.log(`Группы пользователя: ${user.groups}`);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: sessionUser
|
||||
});
|
||||
});
|
||||
@@ -257,9 +297,9 @@ app.post('/api/login', async (req, res) => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка аутентификации:', error.message);
|
||||
res.status(500).json({
|
||||
res.status(500).json({
|
||||
error: 'Ошибка сервера при авторизации',
|
||||
details: error.message
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1595,6 +1635,9 @@ initializeServer().then(() => {
|
||||
setInterval(checkOverdueTasks, 60000);
|
||||
setInterval(checkUpcomingDeadlines, 60000);
|
||||
setInterval(() => cronJobs.checkDocumentsForCompletion(db), 60000);
|
||||
|
||||
// ✅ Запускаем cron уведомлений чата (раз в час)
|
||||
cronJobs.startChatNotificationsCron();
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error('❌ Не удалось запустить сервер:', error);
|
||||
|
||||
Reference in New Issue
Block a user