feat: otomatik e-posta bildirimi

Kullanıcı kriter kaydediyor; sunucu periyodik olarak eşleşen YENİ ihaleleri
bulup özet e-posta gönderiyor.

- server/notify-store.mjs: abonelik deposu (SQLite, kalıcı volume'de)
  kriterler: il · sektör · alım türü · anahtar kelime · favori kurumlar
- server/notifier.mjs: eşleştirme + gönderim motoru
  · aynı ihale iki kez bildirilmez (sonGorulenler takibi)
  · ilk abonelikte geçmiş bildirilmez, yalnızca temel alınır (spam önleme)
  · sonuçlanmış/arşiv kayıtları bildirime girmez
- /api/notify/subscriptions (GET/POST/DELETE) + /api/notify/run (admin, anlık test)
- scheduler: NOTIFY_INTERVAL_SAAT (varsayılan 6) ile periyodik tur
- NotifySubscriptions paneli: kriter ekle/sil, 'ekrandaki filtreleri kullan'
- tendersPayload ortak yardımcısına çıkarıldı (API ve bildirim aynı veriyi kullanır)
This commit is contained in:
2026-08-29 03:19:47 +03:00
parent 667b335ea7
commit f86339417a
6 changed files with 525 additions and 33 deletions
+29 -1
View File
@@ -14,6 +14,7 @@ import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { historyStats } from './history-store.mjs';
import { bildirimleriIsle } from './notifier.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -137,7 +138,24 @@ export function schedulerStatus() {
};
}
export function startScheduler() {
// --- Otomatik e-posta bildirimi ---
let bildirimCalisiyor = false;
async function bildirimTuru(tenderGetir) {
if (bildirimCalisiyor) return;
bildirimCalisiyor = true;
try {
const s = await bildirimleriIsle(tenderGetir);
if (s.atlandi) console.log(`[scheduler] bildirim atlandı: ${s.atlandi}`);
else console.log(`[scheduler] bildirim turu: ${s.abonelik} abonelik, ${s.gonderilen || 0} e-posta, ${s.hata || 0} hata`);
} catch (e) {
console.warn('[scheduler] bildirim hatası:', e.message);
} finally {
bildirimCalisiyor = false;
}
}
export function startScheduler({ tenderGetir } = {}) {
if (process.env.SCAN_ENABLED === '0') {
console.log('[scheduler] devre dışı (SCAN_ENABLED=0)');
return;
@@ -158,4 +176,14 @@ export function startScheduler() {
setTimeout(dtTara, 4 * 60 * 1000).unref?.();
setInterval(dtTara, Math.max(1, dtSaat) * 3600 * 1000).unref?.();
}
// Otomatik e-posta bildirimi
if (process.env.NOTIFY_ENABLED === '0' || !tenderGetir) {
console.log('[scheduler] otomatik e-posta bildirimi devre dışı');
} else {
const nSaat = Number(process.env.NOTIFY_INTERVAL_SAAT || 6);
console.log(`[scheduler] otomatik e-posta bildirimi her ${nSaat} saatte bir kontrol edilecek`);
setTimeout(() => bildirimTuru(tenderGetir), 6 * 60 * 1000).unref?.();
setInterval(() => bildirimTuru(tenderGetir), Math.max(1, nSaat) * 3600 * 1000).unref?.();
}
}