This commit is contained in:
Калугин Олег Александрович
2025-12-18 09:39:18 +00:00
parent 540fcca315
commit 21b40be946
3 changed files with 584 additions and 35 deletions

313
server.js
View File

@@ -9,6 +9,7 @@ require('dotenv').config();
const { db, logActivity, createUserTaskFolder, saveTaskMetadata, updateTaskMetadata, checkTaskAccess } = require('./database');
const authService = require('./auth');
const adminRouter = require('./admin-server');
const postgresLogger = require('./postgres');
const app = express();
const PORT = process.env.PORT || 3000;
@@ -237,42 +238,98 @@ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, a
if (!process.env.NOTIFICATION_SERVICE_URL ||
!process.env.NOTIFICATION_SERVICE_LOGIN ||
!process.env.NOTIFICATION_SERVICE_PASSWORD) {
console.log('Настройки сервиса уведомлений не заданы');
console.log('⚠️ Настройки сервиса уведомлений не заданы');
// Логируем в PostgreSQL даже если уведомления не отправляются
await logNotificationToPostgres({
type,
taskId,
taskTitle,
taskDescription,
authorId,
comment,
status,
userName,
error: 'Сервис уведомлений не настроен'
});
return;
}
const participants = await new Promise((resolve, reject) => {
db.all(`
SELECT t.created_by as user_id, u.name as user_name, u.login as user_login, u.email, 'creator' as role
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
FROM tasks t
LEFT JOIN users u ON t.created_by = u.id
WHERE t.id = ?
UNION
SELECT ta.user_id, u.name as user_name, u.login as user_login, u.email, 'assignee' as role
FROM task_assignments ta
LEFT JOIN users u ON ta.user_id = u.id
WHERE ta.task_id = ?
`, [taskId, taskId], (err, rows) => {
`, [taskId], (err, row) => {
if (err) reject(err);
else resolve(rows);
else resolve(row);
});
});
if (!participants || participants.length === 0) {
console.log('Нет участников для уведомления');
return;
// Получаем исполнителей ОТДЕЛЬНО
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
FROM task_assignments ta
LEFT JOIN users u ON ta.user_id = u.id
WHERE ta.task_id = ?
`, [taskId], (err, rows) => {
if (err) reject(err);
else resolve(rows || []);
});
});
// Собираем всех участников
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 FROM users WHERE id = ?", [authorId], (err, row) => {
db.get("SELECT name, login FROM users WHERE id = ?", [authorId], (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
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;
@@ -323,15 +380,26 @@ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, a
break;
default:
console.log(`⚠️ Неизвестный тип уведомления: ${type}`);
return;
}
// Фильтруем получателей: исключаем автора действия
const recipientIds = participants
.filter(p => p.user_id !== authorId)
.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('Нет получателей для уведомления (все участники - автор изменения)');
console.log('Нет получателей для уведомления (все участники - автор изменения)');
// Обновляем статус в PostgreSQL
await updatePostgresLogStatus(postgresLogIds, 'no_recipients', 'Нет получателей после фильтрации');
return;
}
@@ -347,27 +415,144 @@ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, a
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
});
console.log(`🚀 Отправляем запрос на сервис уведомлений...`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
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,
type,
authorId,
errorMessage: error.message,
stack: error.stack
});
}
const result = await response.json();
console.log(`✅ Уведомления отправлены для задачи ${taskId}:`, {
type: type,
recipients: recipientIds.length,
authorExcluded: authorId
});
} catch (error) {
console.error('❌ Общая ошибка при обработке уведомлений:', error);
}
}
// Вспомогательные функции для работы с PostgreSQL
async function logNotificationToPostgres(data) {
try {
const {
type,
taskId,
taskTitle,
taskDescription,
authorId,
authorName,
authorLogin,
participants = [],
comment = '',
status = '',
userName = '',
error = ''
} = data;
// Создаем сообщение
let messageContent = '';
switch (type) {
case 'created':
messageContent = `Создана новая задача: ${taskTitle}`;
break;
case 'updated':
messageContent = `Обновлена задача: ${taskTitle}`;
break;
case 'rework':
messageContent = `Задача возвращена на доработку: ${taskTitle}. Комментарий: ${comment}`;
break;
case 'closed':
messageContent = `Задача закрыта: ${taskTitle}`;
break;
case 'status_changed':
messageContent = `Изменен статус задачи: ${taskTitle}. Новый статус: ${getStatusText(status)}`;
break;
}
// Логируем для каждого получателя отдельно
const recipientsToNotify = participants.filter(p => p.user_id !== authorId);
const logIds = [];
for (const recipient of recipientsToNotify) {
const logId = await postgresLogger.logNotification({
taskId,
taskTitle,
taskDescription,
notificationType: type,
authorId,
authorName,
authorLogin,
recipientId: recipient.user_id,
recipientName: recipient.user_name,
recipientLogin: recipient.user_login,
messageContent: `${messageContent}\n\nЗадача: ${taskTitle}\nОписание: ${taskDescription || 'Без описания'}\nАвтор: ${authorName}`,
messageSubject: getNotificationSubject(type, taskTitle),
deliveryMethods: ['email', 'telegram', 'vk'],
comments: error ? `Ошибка: ${error}` : comment
});
if (logId) {
logIds.push(logId);
}
}
return logIds;
} catch (error) {
console.error('❌ Ошибка отправки уведомлений:', error);
console.error('❌ Ошибка логирования в PostgreSQL:', error);
return [];
}
}
async function updatePostgresLogStatus(logIds, status, errorMessage = null, sentAt = null) {
if (!logIds || logIds.length === 0) return;
for (const logId of logIds) {
await postgresLogger.updateNotificationStatus(logId, status, errorMessage, sentAt);
}
}
function getNotificationSubject(type, taskTitle) {
switch (type) {
case 'created':
return `Новая задача: ${taskTitle}`;
case 'updated':
return `Обновлена задача: ${taskTitle}`;
case 'rework':
return `Задача возвращена на доработку: ${taskTitle}`;
case 'closed':
return `Задача закрыта: ${taskTitle}`;
case 'status_changed':
return `Изменен статус задачи: ${taskTitle}`;
default:
return `Уведомление по задаче: ${taskTitle}`;
}
}
@@ -1403,7 +1588,65 @@ app.get('/admin', (req, res) => {
}
res.sendFile(path.join(__dirname, 'public/admin.html'));
});
// API для получения логов уведомлений
app.get('/api/notification-logs', requireAuth, async (req, res) => {
try {
const {
taskId,
status,
startDate,
endDate,
limit = 50,
offset = 0
} = req.query;
const logs = await postgresLogger.getNotifications({
taskId: taskId ? parseInt(taskId) : null,
userId: req.session.user.id,
status,
startDate,
endDate,
limit: parseInt(limit),
offset: parseInt(offset)
});
res.json(logs);
} catch (error) {
console.error('Ошибка получения логов уведомлений:', error);
res.status(500).json({ error: 'Ошибка получения логов' });
}
});
// API для получения статистики
app.get('/api/notification-stats', requireAuth, async (req, res) => {
try {
const { period = 'day' } = req.query;
if (req.session.user.role !== 'admin') {
return res.status(403).json({ error: 'Недостаточно прав' });
}
const stats = await postgresLogger.getStatistics(period);
res.json(stats);
} catch (error) {
console.error('Ошибка получения статистики:', error);
res.status(500).json({ error: 'Ошибка получения статистики' });
}
});
// API для проверки состояния PostgreSQL
app.get('/api/postgres-health', requireAuth, async (req, res) => {
try {
if (req.session.user.role !== 'admin') {
return res.status(403).json({ error: 'Недостаточно прав' });
}
const health = await postgresLogger.healthCheck();
res.json(health);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(PORT, () => {
console.log(`CRM сервер запущен на порту ${PORT}`);
console.log(`Откройте http://localhost:${PORT} в браузере`);