pg
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
// email-notifications.js
|
||||
const nodemailer = require('nodemailer');
|
||||
const { getDb } = require('./database');
|
||||
|
||||
@@ -7,7 +6,14 @@ class EmailNotifications {
|
||||
this.transporter = null;
|
||||
this.initialized = false;
|
||||
this.notificationCooldown = 12 * 60 * 60 * 1000; // 12 часов в миллисекундах
|
||||
this.spamBlockCooldown = 60 * 60 * 1000; // 60 минут при блокировке спама
|
||||
this.spamBlockedUntil = null;
|
||||
this.isSpamBlocked = false;
|
||||
this.maxRetries = 3;
|
||||
this.init();
|
||||
|
||||
// Запускаем обработку очереди каждые 5 минут
|
||||
setInterval(() => this.processRetryQueue(), 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
async init() {
|
||||
@@ -37,8 +43,22 @@ class EmailNotifications {
|
||||
// Тестируем подключение
|
||||
await this.transporter.verify();
|
||||
this.initialized = true;
|
||||
|
||||
// Восстанавливаем состояние блокировки из БД
|
||||
await this.restoreSpamBlockState();
|
||||
|
||||
console.log('✅ Email уведомления инициализированы');
|
||||
console.log(`📧 Отправитель: ${process.env.YANDEX_EMAIL}`);
|
||||
|
||||
if (this.isSpamBlocked && this.spamBlockedUntil) {
|
||||
const now = new Date();
|
||||
if (now < this.spamBlockedUntil) {
|
||||
const minutesLeft = Math.ceil((this.spamBlockedUntil - now) / (60 * 1000));
|
||||
console.log(`⏸️ Email отправка заблокирована из-за спама. До разблокировки: ${minutesLeft} минут`);
|
||||
} else {
|
||||
this.clearSpamBlock();
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка инициализации Email уведомлений:', error.message);
|
||||
@@ -46,6 +66,358 @@ class EmailNotifications {
|
||||
}
|
||||
}
|
||||
|
||||
async restoreSpamBlockState() {
|
||||
if (!getDb) return;
|
||||
|
||||
try {
|
||||
const db = getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
`SELECT spam_blocked_until FROM email_settings WHERE setting_key = 'spam_block'`,
|
||||
(err, row) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка получения состояния блокировки:', err);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (row && row.spam_blocked_until) {
|
||||
const blockedUntil = new Date(row.spam_blocked_until);
|
||||
const now = new Date();
|
||||
|
||||
if (now < blockedUntil) {
|
||||
this.isSpamBlocked = true;
|
||||
this.spamBlockedUntil = blockedUntil;
|
||||
console.log(`🔄 Восстановлена блокировка из-за спама до: ${blockedUntil.toLocaleString('ru-RU')}`);
|
||||
} else {
|
||||
this.clearSpamBlockFromDB();
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка восстановления состояния блокировки:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async setSpamBlock() {
|
||||
this.isSpamBlocked = true;
|
||||
this.spamBlockedUntil = new Date(Date.now() + this.spamBlockCooldown);
|
||||
|
||||
console.log(`🚫 Email отправка заблокирована из-за спама до: ${this.spamBlockedUntil.toLocaleString('ru-RU')}`);
|
||||
|
||||
// Сохраняем в БД
|
||||
await this.saveSpamBlockToDB();
|
||||
}
|
||||
|
||||
async saveSpamBlockToDB() {
|
||||
if (!getDb) return;
|
||||
|
||||
try {
|
||||
const db = getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`INSERT OR REPLACE INTO email_settings (setting_key, setting_value, spam_blocked_until, updated_at)
|
||||
VALUES ('spam_block', 'blocked', ?, CURRENT_TIMESTAMP)`,
|
||||
[this.spamBlockedUntil.toISOString()],
|
||||
(err) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка сохранения блокировки в БД:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log('✅ Состояние блокировки сохранено в БД');
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка сохранения блокировки:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async clearSpamBlock() {
|
||||
this.isSpamBlocked = false;
|
||||
this.spamBlockedUntil = null;
|
||||
|
||||
console.log('✅ Блокировка из-за спама снята');
|
||||
|
||||
// Очищаем из БД
|
||||
await this.clearSpamBlockFromDB();
|
||||
|
||||
// Запускаем обработку очереди
|
||||
this.processRetryQueue();
|
||||
}
|
||||
|
||||
async clearSpamBlockFromDB() {
|
||||
if (!getDb) return;
|
||||
|
||||
try {
|
||||
const db = getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`DELETE FROM email_settings WHERE setting_key = 'spam_block'`,
|
||||
(err) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка очистки блокировки из БД:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log('✅ Состояние блокировки очищено из БД');
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка очистки блокировки:', error);
|
||||
}
|
||||
}
|
||||
|
||||
isSpamBlockActive() {
|
||||
if (!this.isSpamBlocked || !this.spamBlockedUntil) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
if (now >= this.spamBlockedUntil) {
|
||||
this.clearSpamBlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async saveToQueue(to, subject, htmlContent, userId, taskId, notificationType, retryCount = 0) {
|
||||
if (!getDb) {
|
||||
console.warn('⚠️ БД не доступна для сохранения в очередь');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const db = getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`INSERT INTO email_queue
|
||||
(to_email, subject, html_content, user_id, task_id, notification_type, retry_count, status, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP)`,
|
||||
[to, subject, htmlContent, userId, taskId, notificationType, retryCount],
|
||||
function(err) {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка сохранения в очередь:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`📝 Email сохранен в очередь (ID: ${this.lastID}): ${to}, ${subject}`);
|
||||
resolve(this.lastID);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка сохранения в очередь:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async updateQueueStatus(queueId, status, errorMessage = null, retryCount = null) {
|
||||
if (!getDb) return false;
|
||||
|
||||
try {
|
||||
const db = getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
let query = `UPDATE email_queue SET status = ?, updated_at = CURRENT_TIMESTAMP`;
|
||||
const params = [status];
|
||||
|
||||
if (errorMessage) {
|
||||
query += `, error_message = ?`;
|
||||
params.push(errorMessage);
|
||||
}
|
||||
|
||||
if (retryCount !== null) {
|
||||
query += `, retry_count = ?`;
|
||||
params.push(retryCount);
|
||||
}
|
||||
|
||||
query += ` WHERE id = ?`;
|
||||
params.push(queueId);
|
||||
|
||||
db.run(query, params, function(err) {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка обновления статуса в очереди:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`📝 Статус email в очереди обновлен (ID: ${queueId}): ${status}`);
|
||||
resolve(this.changes > 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка обновления статуса очереди:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async removeFromQueue(queueId) {
|
||||
if (!getDb) return false;
|
||||
|
||||
try {
|
||||
const db = getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`DELETE FROM email_queue WHERE id = ?`,
|
||||
[queueId],
|
||||
function(err) {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка удаления из очереди:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`🗑️ Email удален из очереди (ID: ${queueId})`);
|
||||
resolve(this.changes > 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка удаления из очереди:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getPendingEmails(limit = 10) {
|
||||
if (!getDb) return [];
|
||||
|
||||
try {
|
||||
const db = getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT * FROM email_queue
|
||||
WHERE status = 'pending'
|
||||
ORDER BY created_at ASC
|
||||
LIMIT ?`,
|
||||
[limit],
|
||||
(err, rows) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка получения очереди:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows || []);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка получения очереди:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async processRetryQueue() {
|
||||
if (this.isSpamBlockActive()) {
|
||||
const minutesLeft = Math.ceil((this.spamBlockedUntil - new Date()) / (60 * 1000));
|
||||
console.log(`⏸️ Пропуск обработки очереди: блокировка из-за спама (осталось ${minutesLeft} минут)`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.initialized || !this.transporter) {
|
||||
console.warn('⚠️ Пропуск обработки очереди: Email не инициализирован');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🔍 Проверка очереди email для повторной отправки...');
|
||||
|
||||
try {
|
||||
const pendingEmails = await this.getPendingEmails(20);
|
||||
|
||||
if (pendingEmails.length === 0) {
|
||||
console.log('📭 Очередь email пуста');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📧 Найдено ${pendingEmails.length} email в очереди`);
|
||||
|
||||
for (const email of pendingEmails) {
|
||||
try {
|
||||
// Проверяем, не превышено ли максимальное количество попыток
|
||||
if (email.retry_count >= this.maxRetries) {
|
||||
console.log(`⏭️ Пропуск email ${email.id}: превышено максимальное количество попыток (${email.retry_count})`);
|
||||
await this.updateQueueStatus(email.id, 'failed', 'Превышено максимальное количество попыток отправки');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Обновляем статус на "в процессе отправки"
|
||||
await this.updateQueueStatus(email.id, 'sending');
|
||||
|
||||
// Отправляем email
|
||||
const info = await this.transporter.sendMail({
|
||||
from: `"School CRM" <${process.env.YANDEX_EMAIL}>`,
|
||||
to: email.to_email,
|
||||
subject: email.subject,
|
||||
html: email.html_content,
|
||||
text: email.html_content.replace(/<[^>]*>/g, '')
|
||||
});
|
||||
|
||||
console.log(`✅ Email отправлен из очереди (ID: ${email.id}): ${email.to_email}, Message ID: ${info.messageId}`);
|
||||
|
||||
// Удаляем из очереди при успешной отправке
|
||||
await this.removeFromQueue(email.id);
|
||||
|
||||
// Если есть связанная задача, обновляем историю уведомлений
|
||||
if (email.user_id && email.task_id && email.notification_type) {
|
||||
try {
|
||||
await this.recordNotificationSent(email.user_id, email.task_id, email.notification_type);
|
||||
console.log(`📝 История уведомлений обновлена для email из очереди (ID: ${email.id})`);
|
||||
} catch (historyError) {
|
||||
console.error('❌ Ошибка обновления истории для email из очереди:', historyError);
|
||||
}
|
||||
}
|
||||
|
||||
// Делаем небольшую паузу между отправками
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
} catch (emailError) {
|
||||
console.error(`❌ Ошибка отправки email из очереди (ID: ${email.id}):`, emailError.message);
|
||||
|
||||
// Проверяем, является ли ошибка блокировкой спама
|
||||
if (emailError.message.includes('554 5.7.1 Message rejected under suspicion of SPAM') ||
|
||||
emailError.message.includes('suspicion of SPAM') ||
|
||||
emailError.message.includes('SPAM')) {
|
||||
|
||||
console.log('🚫 Обнаружена блокировка из-за спама при обработке очереди');
|
||||
await this.setSpamBlock();
|
||||
|
||||
// Увеличиваем счетчик попыток и возвращаем в очередь
|
||||
const newRetryCount = (email.retry_count || 0) + 1;
|
||||
await this.updateQueueStatus(
|
||||
email.id,
|
||||
'pending',
|
||||
`SPAM блокировка: ${emailError.message}`,
|
||||
newRetryCount
|
||||
);
|
||||
break; // Прерываем цикл при блокировке спама
|
||||
}
|
||||
|
||||
// Для других ошибок просто увеличиваем счетчик попыток
|
||||
const newRetryCount = (email.retry_count || 0) + 1;
|
||||
await this.updateQueueStatus(
|
||||
email.id,
|
||||
'pending',
|
||||
`Ошибка отправки: ${emailError.message}`,
|
||||
newRetryCount
|
||||
);
|
||||
|
||||
// Делаем паузу перед следующей попыткой
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Обработка очереди email завершена`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка обработки очереди email:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async canSendNotification(userId, taskId, notificationType) {
|
||||
if (!getDb) return true; // Если БД не готова, разрешаем отправку
|
||||
|
||||
@@ -221,10 +593,23 @@ class EmailNotifications {
|
||||
});
|
||||
}
|
||||
|
||||
async sendEmailNotification(to, subject, htmlContent) {
|
||||
async sendEmailNotification(to, subject, htmlContent, userId = null, taskId = null, notificationType = null) {
|
||||
// Проверяем блокировку из-за спама
|
||||
if (this.isSpamBlockActive()) {
|
||||
const minutesLeft = Math.ceil((this.spamBlockedUntil - new Date()) / (60 * 1000));
|
||||
console.log(`⏸️ Email отправка заблокирована из-за спама. Сохраняем в очередь. Осталось: ${minutesLeft} минут`);
|
||||
|
||||
// Сохраняем в очередь для последующей отправки
|
||||
const queueId = await this.saveToQueue(to, subject, htmlContent, userId, taskId, notificationType);
|
||||
return queueId ? { queued: true, queueId } : false;
|
||||
}
|
||||
|
||||
if (!this.initialized || !this.transporter) {
|
||||
console.warn('⚠️ Email уведомления отключены');
|
||||
return false;
|
||||
|
||||
// Также сохраняем в очередь на случай, если сервис восстановится
|
||||
const queueId = await this.saveToQueue(to, subject, htmlContent, userId, taskId, notificationType);
|
||||
return queueId ? { queued: true, queueId } : false;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -233,15 +618,31 @@ class EmailNotifications {
|
||||
to: to,
|
||||
subject: subject,
|
||||
html: htmlContent,
|
||||
text: htmlContent.replace(/<[^>]*>/g, '') // Конвертируем HTML в текст
|
||||
text: htmlContent.replace(/<[^>]*>/g, '')
|
||||
});
|
||||
|
||||
console.log(`📧 Email отправлен: ${to}, Message ID: ${info.messageId}`);
|
||||
return true;
|
||||
return { sent: true, messageId: info.messageId };
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка отправки email:', error.message);
|
||||
return false;
|
||||
|
||||
// Проверяем, является ли ошибка блокировкой спама
|
||||
if (error.message.includes('554 5.7.1 Message rejected under suspicion of SPAM') ||
|
||||
error.message.includes('suspicion of SPAM') ||
|
||||
error.message.includes('SPAM')) {
|
||||
|
||||
console.log('🚫 Обнаружена блокировка из-за спама. Активируем блокировку на 60 минут.');
|
||||
await this.setSpamBlock();
|
||||
|
||||
// Сохраняем email в очередь для повторной отправки после блокировки
|
||||
const queueId = await this.saveToQueue(to, subject, htmlContent, userId, taskId, notificationType, 1);
|
||||
return queueId ? { queued: true, queueId, spamBlocked: true } : false;
|
||||
}
|
||||
|
||||
// Для других ошибок также сохраняем в очередь
|
||||
const queueId = await this.saveToQueue(to, subject, htmlContent, userId, taskId, notificationType, 1);
|
||||
return queueId ? { queued: true, queueId } : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,14 +787,20 @@ class EmailNotifications {
|
||||
htmlContent = this.getDefaultHtml(taskData);
|
||||
}
|
||||
|
||||
const result = await this.sendEmailNotification(emailTo, subject, htmlContent);
|
||||
const result = await this.sendEmailNotification(emailTo, subject, htmlContent, userId, taskId, notificationType);
|
||||
|
||||
// Если уведомление успешно отправлено, записываем в историю
|
||||
if (result) {
|
||||
// Если уведомление успешно отправлено (не в очередь), записываем в историю
|
||||
if (result && result.sent) {
|
||||
await this.recordNotificationSent(userId, taskId, notificationType);
|
||||
console.log(`✅ Уведомление отправлено: пользователь ${userId}, задача ${taskId}, тип ${notificationType}`);
|
||||
} else if (result && result.queued) {
|
||||
console.log(`📝 Уведомление сохранено в очередь (ID: ${result.queueId}): пользователь ${userId}, задача ${taskId}, тип ${notificationType}`);
|
||||
|
||||
if (result.spamBlocked) {
|
||||
console.log(`🚫 Отправка заблокирована из-за спама. Email будет отправлен после разблокировки.`);
|
||||
}
|
||||
} else {
|
||||
console.log(`❌ Не удалось отправить уведомление: пользователь ${userId}, задача ${taskId}, тип ${notificationType}`);
|
||||
console.log(`❌ Не удалось отправить/сохранить уведомление: пользователь ${userId}, задача ${taskId}, тип ${notificationType}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -845,11 +1252,175 @@ class EmailNotifications {
|
||||
});
|
||||
}
|
||||
|
||||
async getEmailQueueStats() {
|
||||
if (!getDb) return { pending: 0, failed: 0, total: 0 };
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const db = getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
|
||||
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed,
|
||||
COUNT(*) as total
|
||||
FROM email_queue
|
||||
`;
|
||||
|
||||
db.get(query, [], (err, stats) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка получения статистики очереди:', err);
|
||||
resolve({ pending: 0, failed: 0, total: 0 });
|
||||
} else {
|
||||
resolve(stats || { pending: 0, failed: 0, total: 0 });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getEmailQueueItems(status = null, limit = 50) {
|
||||
if (!getDb) return [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const db = getDb();
|
||||
let query = `SELECT * FROM email_queue`;
|
||||
const params = [];
|
||||
|
||||
if (status) {
|
||||
query += ` WHERE status = ?`;
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
query += ` ORDER BY created_at DESC LIMIT ?`;
|
||||
params.push(limit);
|
||||
|
||||
db.all(query, params, (err, items) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка получения элементов очереди:', err);
|
||||
resolve([]);
|
||||
} else {
|
||||
resolve(items || []);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async retryFailedEmails() {
|
||||
if (!getDb) return { retried: 0, total: 0 };
|
||||
|
||||
try {
|
||||
const db = getDb();
|
||||
|
||||
// Получаем все проваленные email
|
||||
const failedEmails = await new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT * FROM email_queue WHERE status = 'failed' AND retry_count < ?`,
|
||||
[this.maxRetries],
|
||||
(err, emails) => {
|
||||
if (err) reject(err);
|
||||
else resolve(emails || []);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (failedEmails.length === 0) {
|
||||
console.log('📭 Нет проваленных email для повторной отправки');
|
||||
return { retried: 0, total: 0 };
|
||||
}
|
||||
|
||||
console.log(`🔍 Найдено ${failedEmails.length} проваленных email для повторной отправки`);
|
||||
|
||||
let retriedCount = 0;
|
||||
|
||||
for (const email of failedEmails) {
|
||||
try {
|
||||
// Сбрасываем статус на pending для повторной попытки
|
||||
await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`UPDATE email_queue SET status = 'pending', retry_count = retry_count + 1 WHERE id = ?`,
|
||||
[email.id],
|
||||
function(err) {
|
||||
if (err) reject(err);
|
||||
else {
|
||||
console.log(`🔄 Email ${email.id} подготовлен для повторной отправки`);
|
||||
retriedCount++;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Ошибка подготовки email ${email.id} для повторной отправки:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Подготовлено ${retriedCount} email для повторной отправки`);
|
||||
|
||||
// Запускаем обработку очереди
|
||||
this.processRetryQueue();
|
||||
|
||||
return { retried: retriedCount, total: failedEmails.length };
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка при попытке повторной отправки email:', error);
|
||||
return { retried: 0, total: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
async clearOldQueueItems(days = 30) {
|
||||
if (!getDb) return { deleted: 0 };
|
||||
|
||||
try {
|
||||
const db = getDb();
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`DELETE FROM email_queue
|
||||
WHERE created_at < datetime('now', '-${days} days')
|
||||
AND (status = 'sent' OR status = 'failed')`,
|
||||
function(err) {
|
||||
if (err) reject(err);
|
||||
else resolve(this.changes || 0);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
console.log(`🗑️ Удалено ${result} старых записей из очереди email (старше ${days} дней)`);
|
||||
return { deleted: result };
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка очистки старых записей очереди:', error);
|
||||
return { deleted: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return this.initialized;
|
||||
}
|
||||
|
||||
getSpamBlockStatus() {
|
||||
if (!this.isSpamBlocked || !this.spamBlockedUntil) {
|
||||
return { blocked: false, blockedUntil: null, minutesLeft: 0 };
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
if (now >= this.spamBlockedUntil) {
|
||||
this.clearSpamBlock();
|
||||
return { blocked: false, blockedUntil: null, minutesLeft: 0 };
|
||||
}
|
||||
|
||||
const minutesLeft = Math.ceil((this.spamBlockedUntil - now) / (60 * 1000));
|
||||
return {
|
||||
blocked: true,
|
||||
blockedUntil: this.spamBlockedUntil,
|
||||
minutesLeft: minutesLeft
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton
|
||||
const emailNotifications = new EmailNotifications();
|
||||
|
||||
// Экспортируем функцию для очистки старых записей (можно запускать по cron)
|
||||
emailNotifications.clearOldQueueItems = emailNotifications.clearOldQueueItems.bind(emailNotifications);
|
||||
|
||||
module.exports = emailNotifications;
|
||||
Reference in New Issue
Block a user