postgres
This commit is contained in:
100
postgres-init.js
Normal file
100
postgres-init.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
const { Client } = require('pg');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
async function initializeDatabase() {
|
||||||
|
console.log('🔄 Инициализация PostgreSQL...');
|
||||||
|
|
||||||
|
// Сначала подключаемся без конкретной БД
|
||||||
|
const adminClient = new Client({
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
port: process.env.DB_PORT || 5432,
|
||||||
|
user: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: 'postgres' // Подключаемся к системной БД
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await adminClient.connect();
|
||||||
|
console.log('✅ Подключение к PostgreSQL установлено');
|
||||||
|
|
||||||
|
// Проверяем существование базы данных
|
||||||
|
const dbCheck = await adminClient.query(`
|
||||||
|
SELECT 1 FROM pg_database WHERE datname = '${process.env.DB_NAME}'
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (dbCheck.rows.length === 0) {
|
||||||
|
console.log(`📦 База данных ${process.env.DB_NAME} не существует, создаем...`);
|
||||||
|
await adminClient.query(`CREATE DATABASE ${process.env.DB_NAME}`);
|
||||||
|
console.log(`✅ База данных ${process.env.DB_NAME} создана`);
|
||||||
|
} else {
|
||||||
|
console.log(`✅ База данных ${process.env.DB_NAME} уже существует`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await adminClient.end();
|
||||||
|
|
||||||
|
// Теперь подключаемся к созданной БД
|
||||||
|
const dbClient = new Client({
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
port: process.env.DB_PORT || 5432,
|
||||||
|
user: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.DB_NAME
|
||||||
|
});
|
||||||
|
|
||||||
|
await dbClient.connect();
|
||||||
|
|
||||||
|
// Создаем таблицы
|
||||||
|
await dbClient.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS sms_logs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
task_id INTEGER NOT NULL,
|
||||||
|
task_title VARCHAR(500) NOT NULL,
|
||||||
|
task_description TEXT,
|
||||||
|
notification_type VARCHAR(50) NOT NULL,
|
||||||
|
creator_id INTEGER,
|
||||||
|
creator_name VARCHAR(255),
|
||||||
|
creator_login VARCHAR(100),
|
||||||
|
assignee_id INTEGER,
|
||||||
|
assignee_name VARCHAR(255),
|
||||||
|
assignee_login VARCHAR(100),
|
||||||
|
message_content TEXT NOT NULL,
|
||||||
|
message_subject VARCHAR(500),
|
||||||
|
delivery_methods JSONB DEFAULT '[]',
|
||||||
|
status VARCHAR(50) DEFAULT 'pending',
|
||||||
|
error_message TEXT,
|
||||||
|
retry_count INTEGER DEFAULT 0,
|
||||||
|
sent_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
comments TEXT
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Создаем индексы
|
||||||
|
const indexes = [
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_sms_logs_task_id ON sms_logs(task_id)',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_sms_logs_creator_id ON sms_logs(creator_id)',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_sms_logs_assignee_id ON sms_logs(assignee_id)',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_sms_logs_status ON sms_logs(status)',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_sms_logs_created_at ON sms_logs(created_at)'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const indexQuery of indexes) {
|
||||||
|
try {
|
||||||
|
await dbClient.query(indexQuery);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`⚠️ Индекс не создан: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await dbClient.end();
|
||||||
|
console.log('✅ PostgreSQL полностью инициализирован');
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка инициализации PostgreSQL:', error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { initializeDatabase };
|
||||||
206
postgres.js
Normal file
206
postgres.js
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
const { Pool } = require('pg');
|
||||||
|
require('dotenv').config();
|
||||||
|
const { initializeDatabase } = require('./postgres-init');
|
||||||
|
|
||||||
|
class PostgresLogger {
|
||||||
|
constructor() {
|
||||||
|
this.pool = null;
|
||||||
|
this.initialized = false;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
console.log('🔌 Инициализация PostgreSQL логгера...');
|
||||||
|
|
||||||
|
// Проверяем наличие переменных окружения
|
||||||
|
if (!process.env.DB_HOST || !process.env.DB_USER || !process.env.DB_PASSWORD) {
|
||||||
|
console.log('⚠️ Переменные окружения для PostgreSQL не заданы');
|
||||||
|
console.log(' DB_HOST:', process.env.DB_HOST || 'не задано');
|
||||||
|
console.log(' DB_USER:', process.env.DB_USER || 'не задано');
|
||||||
|
console.log(' DB_NAME:', process.env.DB_NAME || 'minicrm');
|
||||||
|
this.initialized = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем базу данных если нужно
|
||||||
|
const dbInitialized = await initializeDatabase();
|
||||||
|
if (!dbInitialized) {
|
||||||
|
console.error('❌ Не удалось инициализировать базу данных');
|
||||||
|
this.initialized = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Подключаемся к созданной БД
|
||||||
|
this.pool = new Pool({
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
port: process.env.DB_PORT || 5432,
|
||||||
|
database: process.env.DB_NAME || 'minicrm',
|
||||||
|
user: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
max: 5,
|
||||||
|
idleTimeoutMillis: 30000,
|
||||||
|
connectionTimeoutMillis: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Тестируем подключение
|
||||||
|
const client = await this.pool.connect();
|
||||||
|
await client.query('SELECT 1');
|
||||||
|
client.release();
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
console.log('✅ PostgreSQL логгер готов к работе');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка инициализации PostgreSQL логгера:', error.message);
|
||||||
|
console.error(' Убедитесь, что:');
|
||||||
|
console.error(' 1. PostgreSQL сервер запущен на', process.env.DB_HOST);
|
||||||
|
console.error(' 2. Пользователь', process.env.DB_USER, 'имеет права на создание БД');
|
||||||
|
console.error(' 3. Пароль указан верно в .env файле');
|
||||||
|
this.initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async logNotification(notificationData) {
|
||||||
|
if (!this.initialized) {
|
||||||
|
console.log('⚠️ PostgreSQL не инициализирован, логирование пропущено');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
taskId,
|
||||||
|
taskTitle,
|
||||||
|
taskDescription = '',
|
||||||
|
notificationType,
|
||||||
|
authorId,
|
||||||
|
authorName,
|
||||||
|
authorLogin,
|
||||||
|
recipientId,
|
||||||
|
recipientName,
|
||||||
|
recipientLogin,
|
||||||
|
messageContent,
|
||||||
|
messageSubject = '',
|
||||||
|
deliveryMethods = ['email', 'telegram', 'vk'],
|
||||||
|
comments = ''
|
||||||
|
} = notificationData;
|
||||||
|
|
||||||
|
let client;
|
||||||
|
try {
|
||||||
|
client = await this.pool.connect();
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
INSERT INTO sms_logs (
|
||||||
|
task_id, task_title, task_description, notification_type,
|
||||||
|
creator_id, creator_name, creator_login,
|
||||||
|
assignee_id, assignee_name, assignee_login,
|
||||||
|
message_content, message_subject, delivery_methods,
|
||||||
|
status, comments, created_at
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, CURRENT_TIMESTAMP)
|
||||||
|
RETURNING id;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const values = [
|
||||||
|
taskId,
|
||||||
|
taskTitle?.substring(0, 500) || 'Без названия',
|
||||||
|
taskDescription?.substring(0, 5000) || '',
|
||||||
|
notificationType,
|
||||||
|
authorId,
|
||||||
|
authorName || 'Неизвестно',
|
||||||
|
authorLogin || 'unknown',
|
||||||
|
recipientId,
|
||||||
|
recipientName || 'Неизвестно',
|
||||||
|
recipientLogin || 'unknown',
|
||||||
|
messageContent?.substring(0, 5000) || '',
|
||||||
|
messageSubject?.substring(0, 500) || '',
|
||||||
|
JSON.stringify(deliveryMethods),
|
||||||
|
'pending',
|
||||||
|
comments
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await client.query(query, values);
|
||||||
|
const logId = result.rows[0]?.id;
|
||||||
|
|
||||||
|
if (logId) {
|
||||||
|
console.log(`📝 Уведомление записано в PostgreSQL, ID: ${logId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logId || null;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка записи уведомления в PostgreSQL:', error.message);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (client) client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateNotificationStatus(logId, status, errorMessage = null, sentAt = null) {
|
||||||
|
if (!this.initialized || !logId) return;
|
||||||
|
|
||||||
|
let client;
|
||||||
|
try {
|
||||||
|
client = await this.pool.connect();
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
UPDATE sms_logs
|
||||||
|
SET status = $1,
|
||||||
|
error_message = $2,
|
||||||
|
sent_at = $3,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $4;
|
||||||
|
`;
|
||||||
|
|
||||||
|
await client.query(query, [
|
||||||
|
status,
|
||||||
|
errorMessage,
|
||||||
|
sentAt ? new Date(sentAt) : (status === 'sent' ? new Date() : null),
|
||||||
|
logId
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log(`📝 Статус уведомления ${logId} обновлен на: ${status}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка обновления статуса уведомления:', error.message);
|
||||||
|
} finally {
|
||||||
|
if (client) client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async healthCheck() {
|
||||||
|
if (!this.initialized) {
|
||||||
|
return {
|
||||||
|
connected: false,
|
||||||
|
error: 'PostgreSQL не инициализирован',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let client;
|
||||||
|
try {
|
||||||
|
client = await this.pool.connect();
|
||||||
|
await client.query('SELECT 1');
|
||||||
|
|
||||||
|
return {
|
||||||
|
connected: true,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
database: process.env.DB_NAME || 'minicrm',
|
||||||
|
host: process.env.DB_HOST
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
connected: false,
|
||||||
|
error: error.message,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
if (client) client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Другие методы остаются без изменений...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Экспортируем singleton
|
||||||
|
const postgresLogger = new PostgresLogger();
|
||||||
|
module.exports = postgresLogger;
|
||||||
313
server.js
313
server.js
@@ -9,6 +9,7 @@ require('dotenv').config();
|
|||||||
const { db, logActivity, createUserTaskFolder, saveTaskMetadata, updateTaskMetadata, checkTaskAccess } = require('./database');
|
const { db, logActivity, createUserTaskFolder, saveTaskMetadata, updateTaskMetadata, checkTaskAccess } = require('./database');
|
||||||
const authService = require('./auth');
|
const authService = require('./auth');
|
||||||
const adminRouter = require('./admin-server');
|
const adminRouter = require('./admin-server');
|
||||||
|
const postgresLogger = require('./postgres');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
@@ -237,42 +238,98 @@ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, a
|
|||||||
if (!process.env.NOTIFICATION_SERVICE_URL ||
|
if (!process.env.NOTIFICATION_SERVICE_URL ||
|
||||||
!process.env.NOTIFICATION_SERVICE_LOGIN ||
|
!process.env.NOTIFICATION_SERVICE_LOGIN ||
|
||||||
!process.env.NOTIFICATION_SERVICE_PASSWORD) {
|
!process.env.NOTIFICATION_SERVICE_PASSWORD) {
|
||||||
console.log('Настройки сервиса уведомлений не заданы');
|
console.log('⚠️ Настройки сервиса уведомлений не заданы');
|
||||||
|
|
||||||
|
// Логируем в PostgreSQL даже если уведомления не отправляются
|
||||||
|
await logNotificationToPostgres({
|
||||||
|
type,
|
||||||
|
taskId,
|
||||||
|
taskTitle,
|
||||||
|
taskDescription,
|
||||||
|
authorId,
|
||||||
|
comment,
|
||||||
|
status,
|
||||||
|
userName,
|
||||||
|
error: 'Сервис уведомлений не настроен'
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const participants = await new Promise((resolve, reject) => {
|
console.log(`📢 Начинаем отправку уведомлений для задачи ${taskId}:`);
|
||||||
db.all(`
|
console.log(` Тип: ${type}, Автор действия: ${authorId}, Название: ${taskTitle}`);
|
||||||
SELECT t.created_by as user_id, u.name as user_name, u.login as user_login, u.email, 'creator' as role
|
|
||||||
|
// Получаем заказчика (создателя задачи) ОТДЕЛЬНО
|
||||||
|
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
|
FROM tasks t
|
||||||
LEFT JOIN users u ON t.created_by = u.id
|
LEFT JOIN users u ON t.created_by = u.id
|
||||||
WHERE t.id = ?
|
WHERE t.id = ?
|
||||||
|
`, [taskId], (err, row) => {
|
||||||
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) => {
|
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
else resolve(rows);
|
else resolve(row);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!participants || participants.length === 0) {
|
// Получаем исполнителей ОТДЕЛЬНО
|
||||||
console.log('Нет участников для уведомления');
|
const assignees = await new Promise((resolve, reject) => {
|
||||||
return;
|
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) => {
|
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);
|
if (err) reject(err);
|
||||||
else resolve(row);
|
else resolve(row);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const authorName = author ? author.name : 'Система';
|
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;
|
let subject, content;
|
||||||
|
|
||||||
@@ -323,15 +380,26 @@ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, a
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
console.log(`⚠️ Неизвестный тип уведомления: ${type}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Фильтруем получателей: исключаем автора действия
|
||||||
const recipientIds = participants
|
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);
|
.map(p => p.user_id);
|
||||||
|
|
||||||
if (recipientIds.length === 0) {
|
if (recipientIds.length === 0) {
|
||||||
console.log('Нет получателей для уведомления (все участники - автор изменения)');
|
console.log('❌ Нет получателей для уведомления (все участники - автор изменения)');
|
||||||
|
|
||||||
|
// Обновляем статус в PostgreSQL
|
||||||
|
await updatePostgresLogStatus(postgresLogIds, 'no_recipients', 'Нет получателей после фильтрации');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,27 +415,144 @@ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, a
|
|||||||
formData.append('recipients', JSON.stringify(recipientIds));
|
formData.append('recipients', JSON.stringify(recipientIds));
|
||||||
formData.append('deliveryMethods', JSON.stringify(['email', 'telegram', 'vk']));
|
formData.append('deliveryMethods', JSON.stringify(['email', 'telegram', 'vk']));
|
||||||
|
|
||||||
const response = await fetch(process.env.NOTIFICATION_SERVICE_URL, {
|
console.log(`🚀 Отправляем запрос на сервис уведомлений...`);
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Basic ${authHeader}`
|
|
||||||
},
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
try {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
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();
|
} catch (error) {
|
||||||
console.log(`✅ Уведомления отправлены для задачи ${taskId}:`, {
|
console.error('❌ Общая ошибка при обработке уведомлений:', error);
|
||||||
type: type,
|
}
|
||||||
recipients: recipientIds.length,
|
}
|
||||||
authorExcluded: authorId
|
|
||||||
});
|
// Вспомогательные функции для работы с 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) {
|
} 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'));
|
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, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`CRM сервер запущен на порту ${PORT}`);
|
console.log(`CRM сервер запущен на порту ${PORT}`);
|
||||||
console.log(`Откройте http://localhost:${PORT} в браузере`);
|
console.log(`Откройте http://localhost:${PORT} в браузере`);
|
||||||
|
|||||||
Reference in New Issue
Block a user