email and fix
This commit is contained in:
441
notifications.js
441
notifications.js
@@ -1,97 +1,7 @@
|
||||
const fetch = require('node-fetch');
|
||||
// notifications.js
|
||||
const postgresLogger = require('./postgres');
|
||||
const { getDb } = require('./database');
|
||||
|
||||
async function sendDeadlineNotification(assignment, hoursLeft) {
|
||||
try {
|
||||
if (!process.env.NOTIFICATION_SERVICE_URL ||
|
||||
!process.env.NOTIFICATION_SERVICE_LOGIN ||
|
||||
!process.env.NOTIFICATION_SERVICE_PASSWORD) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notificationKey = `deadline_${hoursLeft}h_task_${assignment.task_id}_user_${assignment.user_id}`;
|
||||
const lastSent = await getLastNotificationSent(notificationKey);
|
||||
const now = new Date();
|
||||
|
||||
if (lastSent) {
|
||||
const timeSinceLast = now.getTime() - new Date(lastSent).getTime();
|
||||
if (timeSinceLast < 12 * 60 * 60 * 1000) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const subject = `⚠️ До окончания срока задачи осталось менее ${hoursLeft} часов`;
|
||||
const content = `Задача: ${assignment.title}\n\n` +
|
||||
`Описание: ${assignment.description || 'Без описания'}\n` +
|
||||
`Срок выполнения: ${new Date(assignment.due_date).toLocaleString('ru-RU')}\n` +
|
||||
`Осталось времени: ${hoursLeft} часов\n\n` +
|
||||
`Пожалуйста, завершите задачу в срок.`;
|
||||
|
||||
const recipients = [
|
||||
{ id: assignment.user_id, name: assignment.user_name, email: assignment.user_email },
|
||||
{ id: assignment.created_by, name: assignment.creator_name, email: assignment.creator_email }
|
||||
].filter((value, index, self) =>
|
||||
self.findIndex(r => r.id === value.id) === index
|
||||
);
|
||||
|
||||
const recipientIds = recipients.map(r => r.id);
|
||||
|
||||
const authHeader = encodeBasicAuth(
|
||||
process.env.NOTIFICATION_SERVICE_LOGIN,
|
||||
process.env.NOTIFICATION_SERVICE_PASSWORD
|
||||
);
|
||||
|
||||
const FormData = require('form-data');
|
||||
const formData = new FormData();
|
||||
formData.append('subject', subject);
|
||||
formData.append('content', content);
|
||||
formData.append('recipients', JSON.stringify(recipientIds));
|
||||
formData.append('deliveryMethods', JSON.stringify(['email', 'telegram', 'vk']));
|
||||
|
||||
const response = await fetch(process.env.NOTIFICATION_SERVICE_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Basic ${authHeader}`
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await saveNotificationSent(notificationKey);
|
||||
console.log(`✅ Уведомление о сроке (${hoursLeft}ч) отправлено для задачи ${assignment.task_id}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка отправки уведомления о сроке:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function getLastNotificationSent(key) {
|
||||
return new Promise((resolve) => {
|
||||
const db = getDb();
|
||||
if (!db) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.get("SELECT created_at FROM notification_logs WHERE notification_key = ? ORDER BY created_at DESC LIMIT 1",
|
||||
[key], (err, row) => {
|
||||
resolve(row ? row.created_at : null);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function saveNotificationSent(key) {
|
||||
const db = getDb();
|
||||
if (!db) return;
|
||||
|
||||
db.run("INSERT INTO notification_logs (notification_key) VALUES (?)", [key]);
|
||||
}
|
||||
|
||||
function encodeBasicAuth(login, password) {
|
||||
return Buffer.from(`${login}:${password}`).toString('base64');
|
||||
}
|
||||
const emailNotifications = require('./email-notifications');
|
||||
|
||||
async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, authorId, comment = '', status = '', userName = '') {
|
||||
try {
|
||||
@@ -101,31 +11,10 @@ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, a
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.env.NOTIFICATION_SERVICE_URL ||
|
||||
!process.env.NOTIFICATION_SERVICE_LOGIN ||
|
||||
!process.env.NOTIFICATION_SERVICE_PASSWORD) {
|
||||
console.log('⚠️ Настройки сервиса уведомлений не заданы');
|
||||
|
||||
// Логируем в PostgreSQL даже если уведомления не отправляются
|
||||
await logNotificationToPostgres({
|
||||
type,
|
||||
taskId,
|
||||
taskTitle,
|
||||
taskDescription,
|
||||
authorId,
|
||||
comment,
|
||||
status,
|
||||
userName,
|
||||
error: 'Сервис уведомлений не настроен'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📢 Начинаем отправку уведомлений для задачи ${taskId}:`);
|
||||
console.log(` Тип: ${type}, Автор действия: ${authorId}, Название: ${taskTitle}`);
|
||||
|
||||
// Получаем заказчика (создателя задачи) ОТДЕЛЬНО
|
||||
// Получаем заказчика
|
||||
const creator = await new Promise((resolve, reject) => {
|
||||
db.get(`
|
||||
SELECT t.created_by as user_id, u.name as user_name, u.login as user_login, u.email
|
||||
@@ -138,7 +27,7 @@ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, a
|
||||
});
|
||||
});
|
||||
|
||||
// Получаем исполнителей ОТДЕЛЬНО
|
||||
// Получаем исполнителей
|
||||
const assignees = await new Promise((resolve, reject) => {
|
||||
db.all(`
|
||||
SELECT ta.user_id, u.name as user_name, u.login as user_login, u.email
|
||||
@@ -151,29 +40,9 @@ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, a
|
||||
});
|
||||
});
|
||||
|
||||
// Собираем всех участников
|
||||
const participants = [];
|
||||
if (creator) {
|
||||
participants.push({
|
||||
...creator,
|
||||
role: 'creator',
|
||||
is_creator: true
|
||||
});
|
||||
}
|
||||
|
||||
if (assignees && assignees.length > 0) {
|
||||
assignees.forEach(assignee => {
|
||||
participants.push({
|
||||
...assignee,
|
||||
role: 'assignee',
|
||||
is_creator: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Получаем информацию об авторе действия
|
||||
const author = await new Promise((resolve, reject) => {
|
||||
db.get("SELECT name, login FROM users WHERE id = ?", [authorId], (err, row) => {
|
||||
db.get("SELECT name, login, email FROM users WHERE id = ?", [authorId], (err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
});
|
||||
@@ -182,148 +51,134 @@ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, a
|
||||
const authorName = author ? author.name : 'Система';
|
||||
const authorLogin = author ? author.login : 'system';
|
||||
|
||||
// Логируем в PostgreSQL
|
||||
const postgresLogIds = await logNotificationToPostgres({
|
||||
type,
|
||||
taskId,
|
||||
taskTitle,
|
||||
taskDescription,
|
||||
authorId,
|
||||
authorName,
|
||||
authorLogin,
|
||||
participants,
|
||||
comment,
|
||||
status,
|
||||
userName
|
||||
});
|
||||
|
||||
let subject, content;
|
||||
|
||||
switch (type) {
|
||||
case 'created':
|
||||
subject = `Новая задача: ${taskTitle}`;
|
||||
content = `Создана новая задача:\n\n` +
|
||||
`📋 ${taskTitle}\n` +
|
||||
`📝 ${taskDescription || 'Без описания'}\n` +
|
||||
`👤 Автор: ${authorName}\n\n` +
|
||||
`Для просмотра перейдите в систему управления задачами.`;
|
||||
break;
|
||||
|
||||
case 'updated':
|
||||
subject = `Обновлена задача: ${taskTitle}`;
|
||||
content = `Задача была обновлена:\n\n` +
|
||||
`📋 ${taskTitle}\n` +
|
||||
`📝 ${taskDescription || 'Без описания'}\n` +
|
||||
`👤 Изменено: ${authorName}\n\n` +
|
||||
`Для просмотра изменений перейдите в систему управления задачами.`;
|
||||
break;
|
||||
|
||||
case 'rework':
|
||||
subject = `Задача возвращена на доработку: ${taskTitle}`;
|
||||
content = `Задача возвращена на доработку:\n\n` +
|
||||
`📋 ${taskTitle}\n` +
|
||||
`📝 Комментарий: ${comment}\n` +
|
||||
`👤 Автор замечания: ${authorName}\n\n` +
|
||||
`Пожалуйста, исправьте замечания и обновите статус задачи.`;
|
||||
break;
|
||||
|
||||
case 'closed':
|
||||
subject = `Задача закрыта: ${taskTitle}`;
|
||||
content = `Задача была закрыта:\n\n` +
|
||||
`📋 ${taskTitle}\n` +
|
||||
`👤 Закрыта: ${authorName}\n\n` +
|
||||
`Задача завершена и перемещена в архив.`;
|
||||
break;
|
||||
|
||||
case 'status_changed':
|
||||
const statusText = getStatusText(status);
|
||||
subject = `Изменен статус задачи: ${taskTitle}`;
|
||||
content = `Статус задачи изменен:\n\n` +
|
||||
`📋 ${taskTitle}\n` +
|
||||
`🔄 Новый статус: ${statusText}\n` +
|
||||
`👤 Изменил: ${userName || authorName}\n\n` +
|
||||
`Для просмотра перейдите в систему управления задачами.`;
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`⚠️ Неизвестный тип уведомления: ${type}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Фильтруем получателей: исключаем автора действия
|
||||
const recipientIds = participants
|
||||
.filter(p => {
|
||||
const shouldExclude = p.user_id === authorId;
|
||||
if (shouldExclude) {
|
||||
console.log(` ✋ Исключаем автора действия: ${p.user_name} (ID: ${p.user_id})`);
|
||||
}
|
||||
return !shouldExclude;
|
||||
})
|
||||
.map(p => p.user_id);
|
||||
|
||||
if (recipientIds.length === 0) {
|
||||
console.log('❌ Нет получателей для уведомления (все участники - автор изменения)');
|
||||
|
||||
// Обновляем статус в PostgreSQL
|
||||
await updatePostgresLogStatus(postgresLogIds, 'no_recipients', 'Нет получателей после фильтрации');
|
||||
return;
|
||||
}
|
||||
|
||||
const authHeader = encodeBasicAuth(
|
||||
process.env.NOTIFICATION_SERVICE_LOGIN,
|
||||
process.env.NOTIFICATION_SERVICE_PASSWORD
|
||||
);
|
||||
|
||||
const FormData = require('form-data');
|
||||
const formData = new FormData();
|
||||
formData.append('subject', subject);
|
||||
formData.append('content', content);
|
||||
formData.append('recipients', JSON.stringify(recipientIds));
|
||||
formData.append('deliveryMethods', JSON.stringify(['email', 'telegram', 'vk']));
|
||||
|
||||
console.log(`🚀 Отправляем запрос на сервис уведомлений...`);
|
||||
|
||||
try {
|
||||
const response = await fetch(process.env.NOTIFICATION_SERVICE_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Basic ${authHeader}`
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log(`✅ Уведомления успешно отправлены для задачи ${taskId}`);
|
||||
|
||||
// Обновляем статус в PostgreSQL
|
||||
await updatePostgresLogStatus(postgresLogIds, 'sent', null, new Date().toISOString());
|
||||
|
||||
console.log(` Результат от сервиса:`, result);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка отправки уведомлений:', error);
|
||||
|
||||
// Обновляем статус в PostgreSQL
|
||||
await updatePostgresLogStatus(postgresLogIds, 'failed', error.message);
|
||||
|
||||
console.error(' Детали ошибки:', {
|
||||
taskId,
|
||||
// Логируем в PostgreSQL (если настроено)
|
||||
let postgresLogIds = [];
|
||||
if (postgresLogger.initialized) {
|
||||
postgresLogIds = await logNotificationToPostgres({
|
||||
type,
|
||||
taskId,
|
||||
taskTitle,
|
||||
taskDescription,
|
||||
authorId,
|
||||
errorMessage: error.message,
|
||||
stack: error.stack
|
||||
authorName,
|
||||
authorLogin,
|
||||
participants: [...(creator ? [{...creator, role: 'creator'}] : []), ...assignees.map(a => ({...a, role: 'assignee'}))],
|
||||
comment,
|
||||
status,
|
||||
userName
|
||||
});
|
||||
}
|
||||
|
||||
// Отправляем email уведомления
|
||||
const participants = [...(creator ? [creator] : []), ...assignees].filter(p => p.user_id !== authorId);
|
||||
|
||||
for (const participant of participants) {
|
||||
const taskData = {
|
||||
taskId,
|
||||
title: taskTitle,
|
||||
description: taskDescription,
|
||||
due_date: null, // Можно добавить получение срока из БД
|
||||
author_name: authorName,
|
||||
comment: comment,
|
||||
status: status,
|
||||
user_name: userName || participant.user_name,
|
||||
hours_left: type === 'deadline' ? 24 : null // Для уведомлений о дедлайне
|
||||
};
|
||||
|
||||
await emailNotifications.sendTaskNotification(
|
||||
participant.user_id,
|
||||
taskData,
|
||||
type
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`✅ Уведомления отправлены для задачи ${taskId}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Общая ошибка при обработке уведомлений:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновим функцию для уведомлений о дедлайнах
|
||||
async function sendDeadlineNotification(assignment, hoursLeft) {
|
||||
try {
|
||||
const taskData = {
|
||||
taskId: assignment.task_id,
|
||||
title: assignment.title,
|
||||
description: assignment.description || '',
|
||||
due_date: assignment.due_date,
|
||||
author_name: assignment.creator_name,
|
||||
hours_left: hoursLeft
|
||||
};
|
||||
|
||||
// Отправляем уведомление исполнителю
|
||||
await emailNotifications.sendTaskNotification(
|
||||
assignment.user_id,
|
||||
taskData,
|
||||
'deadline'
|
||||
);
|
||||
|
||||
// Отправляем уведомление заказчику
|
||||
await emailNotifications.sendTaskNotification(
|
||||
assignment.created_by,
|
||||
taskData,
|
||||
'deadline'
|
||||
);
|
||||
|
||||
console.log(`✅ Email уведомление о сроке (${hoursLeft}ч) отправлено для задачи ${assignment.task_id}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка отправки email уведомления о сроке:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkUpcomingDeadlines() {
|
||||
const now = new Date();
|
||||
const in48Hours = new Date(now.getTime() + 48 * 60 * 60 * 1000).toISOString();
|
||||
const in24Hours = new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString();
|
||||
const nowISO = now.toISOString();
|
||||
|
||||
const query = `
|
||||
SELECT DISTINCT ta.*, t.title, t.description, t.created_by, u.name as user_name, u.email as user_email,
|
||||
creator.name as creator_name, creator.email as creator_email
|
||||
FROM task_assignments ta
|
||||
JOIN tasks t ON ta.task_id = t.id
|
||||
JOIN users u ON ta.user_id = u.id
|
||||
JOIN users creator ON t.created_by = creator.id
|
||||
WHERE ta.due_date IS NOT NULL
|
||||
AND ta.due_date > ?
|
||||
AND ta.due_date <= ?
|
||||
AND ta.status NOT IN ('completed', 'overdue')
|
||||
AND t.status = 'active'
|
||||
AND t.closed_at IS NULL
|
||||
`;
|
||||
|
||||
const db = getDb();
|
||||
if (!db) {
|
||||
console.error('❌ База данных не доступна для проверки сроков');
|
||||
return;
|
||||
}
|
||||
|
||||
db.all(query, [nowISO, in48Hours], async (err, assignments) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка при проверке сроков задач:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const assignment of assignments) {
|
||||
const dueDate = new Date(assignment.due_date);
|
||||
const timeLeft = dueDate.getTime() - now.getTime();
|
||||
const hoursLeft = Math.floor(timeLeft / (60 * 60 * 1000));
|
||||
|
||||
if (hoursLeft <= 48 && hoursLeft > 24) {
|
||||
await sendDeadlineNotification(assignment, 48);
|
||||
} else if (hoursLeft <= 24) {
|
||||
await sendDeadlineNotification(assignment, 24);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Вспомогательные функции для работы с PostgreSQL
|
||||
async function logNotificationToPostgres(data) {
|
||||
try {
|
||||
@@ -433,57 +288,11 @@ function getStatusText(status) {
|
||||
return statusMap[status] || status;
|
||||
}
|
||||
|
||||
function checkUpcomingDeadlines() {
|
||||
const now = new Date();
|
||||
const in48Hours = new Date(now.getTime() + 48 * 60 * 60 * 1000).toISOString();
|
||||
const in24Hours = new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString();
|
||||
const nowISO = now.toISOString();
|
||||
|
||||
const query = `
|
||||
SELECT DISTINCT ta.*, t.title, t.description, t.created_by, u.name as user_name, u.email as user_email,
|
||||
creator.name as creator_name, creator.email as creator_email
|
||||
FROM task_assignments ta
|
||||
JOIN tasks t ON ta.task_id = t.id
|
||||
JOIN users u ON ta.user_id = u.id
|
||||
JOIN users creator ON t.created_by = creator.id
|
||||
WHERE ta.due_date IS NOT NULL
|
||||
AND ta.due_date > ?
|
||||
AND ta.due_date <= ?
|
||||
AND ta.status NOT IN ('completed', 'overdue')
|
||||
AND t.status = 'active'
|
||||
AND t.closed_at IS NULL
|
||||
`;
|
||||
|
||||
const db = getDb();
|
||||
if (!db) {
|
||||
console.error('❌ База данных не доступна для проверки сроков');
|
||||
return;
|
||||
}
|
||||
|
||||
db.all(query, [nowISO, in48Hours], async (err, assignments) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка при проверке сроков задач:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const assignment of assignments) {
|
||||
const dueDate = new Date(assignment.due_date);
|
||||
const timeLeft = dueDate.getTime() - now.getTime();
|
||||
const hoursLeft = Math.floor(timeLeft / (60 * 60 * 1000));
|
||||
|
||||
if (hoursLeft <= 48 && hoursLeft > 24) {
|
||||
await sendDeadlineNotification(assignment, 48);
|
||||
} else if (hoursLeft <= 24) {
|
||||
await sendDeadlineNotification(assignment, 24);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Экспортируем функции
|
||||
module.exports = {
|
||||
sendTaskNotifications,
|
||||
checkUpcomingDeadlines,
|
||||
sendDeadlineNotification,
|
||||
getStatusText
|
||||
getStatusText,
|
||||
emailNotifications // Экспортируем для доступа к методам
|
||||
};
|
||||
Reference in New Issue
Block a user