Give topic and institution subscription sets independent identities

This commit is contained in:
2026-09-06 12:56:37 +03:00
parent 0a0c01baf1
commit f50468e768
5 changed files with 105 additions and 14 deletions
+51 -14
View File
@@ -1,3 +1,4 @@
import {createHash} from 'node:crypto';
/**
* Otomatik e-posta bildirimi abonelikleri (SQLite).
* Kullanıcı kendi filtre kriterlerini kaydeder; zamanlayıcı yeni eşleşen
@@ -34,7 +35,8 @@ CREATE TABLE IF NOT EXISTS abonelikler (
sonGonderim TEXT,
sonGorulenler TEXT, -- JSON: son gönderimde bildirilen ihale id'leri
olusturma TEXT,
UNIQUE(email, sehir, sektor, alim_turu, anahtar)
criteria_key TEXT NOT NULL,
UNIQUE(email, criteria_key)
);
CREATE INDEX IF NOT EXISTS idx_abone_email ON abonelikler(email);
`;
@@ -50,14 +52,48 @@ function open({ create = true } = {}) {
fs.mkdirSync(DATA_DIR, { recursive: true, mode:0o700 });
}
const db = new DatabaseSync(DB_PATH);
fs.chmodSync(DB_PATH,0o600);
db.exec('PRAGMA busy_timeout=5000');
db.exec(SCHEMA);
try {
const k = db.prepare('PRAGMA table_info(abonelikler)').all().map((c) => c.name);
if (!k.includes('konular')) db.exec('ALTER TABLE abonelikler ADD COLUMN konular TEXT');
} catch (e) { console.warn('[notify-store] şema geçişi:', e.message); }
return db;
fs.chmodSync(DB_PATH,0o600);
db.exec('PRAGMA busy_timeout=5000');
db.exec('BEGIN IMMEDIATE');
try {
db.exec(SCHEMA);
const columns=db.prepare('PRAGMA table_info(abonelikler)').all().map(column=>column.name);
if(!columns.includes('konular'))db.exec('ALTER TABLE abonelikler ADD COLUMN konular TEXT');
if(!columns.includes('criteria_key')) {
const rows=db.prepare('SELECT * FROM abonelikler ORDER BY id').all();
const sequence=db.prepare("SELECT seq FROM sqlite_sequence WHERE name='abonelikler'").get()?.seq || 0;
db.exec('ALTER TABLE abonelikler RENAME TO abonelikler_legacy');
db.exec(SCHEMA);
const names=['id','email','ad','sehir','sektor','alim_turu','anahtar','kurumlar','konular','aktif','sonGonderim','sonGorulenler','olusturma','criteria_key'];
const insert=db.prepare(`INSERT INTO abonelikler (${names.join(',')}) VALUES (${names.map(()=>'?').join(',')})`);
const used=new Set();
for(const row of rows) {
const key=subscriptionKey({sehir:row.sehir,sektor:row.sektor,alimTuru:row.alim_turu,anahtar:row.anahtar,
konular:JSON.parse(row.konular || '[]'),kurumlar:JSON.parse(row.kurumlar || '[]')});
const identity=JSON.stringify([row.email,key]);
// Preserve legacy duplicates and their delivery history; do not discard records.
row.criteria_key=used.has(identity)?key+':legacy:'+row.id:key;used.add(identity);
insert.run(...names.map(name=>row[name] ?? null));
}
db.exec('DROP TABLE abonelikler_legacy');
db.exec('CREATE INDEX IF NOT EXISTS idx_abone_email ON abonelikler(email)');
const current=db.prepare("SELECT seq FROM sqlite_sequence WHERE name='abonelikler'").get();
if(current)db.prepare("UPDATE sqlite_sequence SET seq=? WHERE name='abonelikler'").run(Math.max(sequence,current.seq));
else db.prepare('INSERT INTO sqlite_sequence(name,seq) VALUES (?,?)').run('abonelikler',sequence);
}
db.exec('COMMIT');
}catch(error){db.exec('ROLLBACK');throw error;}
return db;
}catch(error){db.close();throw error;}
}
export function subscriptionKey(criteria) {
const normal=value=>String(value || '').trim().toLocaleLowerCase('tr-TR');
const set=values=>{if(!Array.isArray(values))throw new Error('Abonelik konu/kurum listesi geçersiz.');return [...new Set(values.map(normal))].sort();};
return createHash('sha256').update(JSON.stringify([
normal(criteria.sehir),normal(criteria.sektor),normal(criteria.alimTuru),normal(criteria.anahtar),set(criteria.konular || []),set(criteria.kurumlar || [])
])).digest('hex');
}
const bosaNull = (v) => (v == null || v === '' ? '' : String(v).trim());
@@ -70,9 +106,9 @@ export function subscribe(email, kriter = {}, ad = null) {
if (!db) throw new Error('Bildirim deposu kullanılamıyor');
try {
db.prepare(`
INSERT INTO abonelikler (email, ad, sehir, sektor, alim_turu, anahtar, kurumlar, konular, aktif, olusturma)
VALUES (?,?,?,?,?,?,?,?,1,?)
ON CONFLICT(email, sehir, sektor, alim_turu, anahtar) DO UPDATE SET
INSERT INTO abonelikler (email, ad, sehir, sektor, alim_turu, anahtar, kurumlar, konular, aktif, olusturma, criteria_key)
VALUES (?,?,?,?,?,?,?,?,1,?,?)
ON CONFLICT(email, criteria_key) DO UPDATE SET
aktif=1, kurumlar=excluded.kurumlar, konular=excluded.konular, ad=excluded.ad
`).run(
email,
@@ -81,9 +117,10 @@ export function subscribe(email, kriter = {}, ad = null) {
bosaNull(kriter.sektor),
bosaNull(kriter.alimTuru),
bosaNull(kriter.anahtar),
JSON.stringify(kriter.kurumlar || []),
JSON.stringify(kriter.konular || []),
new Date().toISOString()
JSON.stringify((kriter.kurumlar || []).map(value=>value.trim())),
JSON.stringify((kriter.konular || []).map(value=>value.trim())),
new Date().toISOString(),
subscriptionKey(kriter)
);
return list(email);
} finally {