yenilemede ihale detay onbelleğini tazele
Quality gates / test-build (push) Canceled after 0s
Quality gates / container (push) Canceled after 0s

This commit is contained in:
2026-09-06 21:23:45 +03:00
parent fd5db7544e
commit 4eb90615e8
4 changed files with 68 additions and 4 deletions
+30
View File
@@ -0,0 +1,30 @@
/**
* Resmî ihale detayları için süreli, sınırlı bellek önbelleği.
*
* Liste yenilense bile detay metni ayrı bir kaynaktan geldiği için sınırsız
* süre saklanamaz; zeyilname veya tarih değişikliği eski ayrıntıyı geçersiz
* kılmalıdır. Saat bağımlılığını test edebilmek için now parametresi açıktır.
*/
export function createDetailCache({ maxEntries = 500, ttlMs = 10 * 60 * 1000 } = {}) {
if (!Number.isInteger(maxEntries) || maxEntries < 1) throw new Error('Detay önbelleği sınırı geçersiz.');
if (!Number.isFinite(ttlMs) || ttlMs <= 0) throw new Error('Detay önbelleği süresi geçersiz.');
const entries = new Map();
return {
get(key, now = Date.now()) {
const entry = entries.get(key);
if (!entry) return undefined;
if (now - entry.fetchedAt >= ttlMs) {
entries.delete(key);
return undefined;
}
return entry.value;
},
set(key, value, now = Date.now()) {
entries.delete(key);
entries.set(key, { value, fetchedAt: now });
while (entries.size > maxEntries) entries.delete(entries.keys().next().value);
},
clear() { entries.clear(); },
get size() { return entries.size; },
};
}
+8 -4
View File
@@ -64,6 +64,7 @@ import { belgeyeSor, teklifDenetle, teklifTaslak, ihaleMetni, gereklilikMetni }
import { subscribe, list as listSubs, remove as removeSub, deliveryQueue, resolveDelivery, available as notifyAvailable } from './notify-store.mjs';
import { bildirimleriIsle } from './notifier.mjs';
import { scanSite } from './site-scan.mjs';
import { createDetailCache } from './detail-cache.mjs';
// ilan.gov.tr sunucusu ara sertifikayı (GeoTrust TLS RSA CA G1) göndermiyor;
// zinciri tamamlamak için PEM'i yerelden ekliyoruz.
@@ -336,7 +337,7 @@ function htmlToText(htmlStr) {
.trim();
}
const cache = { tenders: null, fetchedAt: 0, totalCount: 0, todayCount: 0, detail: new Map() };
const cache = { tenders: null, fetchedAt: 0, totalCount: 0, todayCount: 0, detail: createDetailCache() };
let arkaPlanDoluyor = false;
@@ -596,6 +597,9 @@ async function getTenders(force = false) {
if (fresh && !force) return cache;
const ilk = await ilanSayfalari(0, ILK_PARTI);
// Listeyi zorla yenileyen kullanıcı güncel kaynak ayrıntısını da görmeli;
// eski detay metni yeni kartla birleştirilmemeli.
if (force) cache.detail.clear();
const numFound = ilk.find((r) => r.numFound)?.numFound || 0;
const ads = ilk.flatMap((r) => r.ads || []);
@@ -624,17 +628,17 @@ async function getTenders(force = false) {
}
async function getDetail(ilanId) {
if (cache.detail.has(ilanId)) return cache.detail.get(ilanId);
const cached = cache.detail.get(ilanId);
if (cached) return cached;
const raw = await ilanDetail(ilanId);
const text = htmlToText(raw.content || '');
const detail = { ...announcementFields(text), adNo: raw.adNo || null };
const sourceTender=cache.tenders.find(t=>t.id===`ilan-${ilanId}`);
const sourceTender=(cache.tenders || []).find(t=>t.id===`ilan-${ilanId}`);
if(sourceTender) {
try { observeTenders([{...sourceTender,...detail,id:`ilan-${ilanId}:detail`}]); }
catch { detail.sourceWarning='İlan alındı ancak kaynak geçmişi kaydedilemedi.'; }
}
cache.detail.set(ilanId, detail);
if (cache.detail.size > 500) cache.detail.delete(cache.detail.keys().next().value);
return detail;
}