Files
ihaleist/server/notify-store.mjs
T
hikmettan f86339417a 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)
2026-08-29 03:19:47 +03:00

129 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Otomatik e-posta bildirimi abonelikleri (SQLite).
* Kullanıcı kendi filtre kriterlerini kaydeder; zamanlayıcı yeni eşleşen
* ihaleleri bulup özet e-posta gönderir.
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
let DatabaseSync = null;
try {
({ DatabaseSync } = await import('node:sqlite'));
} catch {
console.warn('[notify-store] node:sqlite yok — otomatik bildirim devre dışı');
}
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DATA_DIR = path.join(__dirname, 'data');
const DB_PATH = path.join(DATA_DIR, 'notify.sqlite');
const SCHEMA = `
CREATE TABLE IF NOT EXISTS abonelikler (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL,
ad TEXT,
sehir TEXT,
sektor TEXT,
alim_turu TEXT,
anahtar TEXT,
kurumlar TEXT, -- JSON: favori kurum listesi
aktif INTEGER NOT NULL DEFAULT 1,
sonGonderim TEXT,
sonGorulenler TEXT, -- JSON: son gönderimde bildirilen ihale id'leri
olusturma TEXT,
UNIQUE(email, sehir, sektor, alim_turu, anahtar)
);
CREATE INDEX IF NOT EXISTS idx_abone_email ON abonelikler(email);
`;
export function available() {
return Boolean(DatabaseSync);
}
function open({ create = true } = {}) {
if (!DatabaseSync) return null;
if (!fs.existsSync(DB_PATH)) {
if (!create) return null;
fs.mkdirSync(DATA_DIR, { recursive: true });
}
const db = new DatabaseSync(DB_PATH);
db.exec(SCHEMA);
return db;
}
const bosaNull = (v) => (v == null || v === '' ? '' : String(v).trim());
export function subscribe(email, kriter = {}, ad = null) {
const db = open();
if (!db) throw new Error('Bildirim deposu kullanılamıyor');
try {
db.prepare(`
INSERT INTO abonelikler (email, ad, sehir, sektor, alim_turu, anahtar, kurumlar, aktif, olusturma)
VALUES (?,?,?,?,?,?,?,1,?)
ON CONFLICT(email, sehir, sektor, alim_turu, anahtar) DO UPDATE SET
aktif=1, kurumlar=excluded.kurumlar, ad=excluded.ad
`).run(
email,
ad,
bosaNull(kriter.sehir),
bosaNull(kriter.sektor),
bosaNull(kriter.alimTuru),
bosaNull(kriter.anahtar),
JSON.stringify(kriter.kurumlar || []),
new Date().toISOString()
);
return list(email);
} finally {
db.close();
}
}
export function list(email) {
const db = open({ create: false });
if (!db) return [];
try {
const rows = email
? db.prepare('SELECT * FROM abonelikler WHERE email=? ORDER BY id DESC').all(email)
: db.prepare('SELECT * FROM abonelikler WHERE aktif=1 ORDER BY id').all();
return rows.map((r) => ({
id: r.id,
email: r.email,
ad: r.ad,
sehir: r.sehir || null,
sektor: r.sektor || null,
alimTuru: r.alim_turu || null,
anahtar: r.anahtar || null,
kurumlar: (() => { try { return JSON.parse(r.kurumlar || '[]'); } catch { return []; } })(),
aktif: Boolean(r.aktif),
sonGonderim: r.sonGonderim,
sonGorulenler: (() => { try { return JSON.parse(r.sonGorulenler || '[]'); } catch { return []; } })(),
}));
} finally {
db.close();
}
}
export function remove(id, email) {
const db = open({ create: false });
if (!db) return false;
try {
const r = db.prepare('DELETE FROM abonelikler WHERE id=? AND email=?').run(id, email);
return r.changes > 0;
} finally {
db.close();
}
}
export function markSent(id, ihaleIdleri) {
const db = open({ create: false });
if (!db) return;
try {
db.prepare('UPDATE abonelikler SET sonGonderim=?, sonGorulenler=? WHERE id=?')
.run(new Date().toISOString(), JSON.stringify(ihaleIdleri.slice(0, 400)), id);
} finally {
db.close();
}
}