Commit Graph
24 Commits
Author SHA1 Message Date
hikmettan c5662d2e60 CPV/OKAS ham kod sınıflandırması: yayınlanmadan önce tamamlanan bir gate + sidebar filtresi
- server/cpv-classify.mjs (yeni): existing (kalem kodu) → keyword (66 kural, 45 OKAS_BOLUM kodunun tamamı kapsanıyor) → AI (analyzeSpec, sadece sözlükte gerçekten var olan kod kabul edilir) hibrit sınıflandırma
- server/index.js: cache.tenders'a eklenmeden ÖNCE her ihale için CPV sınıflandırması await'leniyor (mevcut "sektör" alanının aksine, sonradan zenginleştirilmiyor) — hem ilk yükleme hem arka plan tamamlama yolunda
- İKN bazlı kalıcı SQLite önbellek (history-store.mjs) — aynı ilan bir daha asla yeniden sınıflandırılmıyor
- /api/meta/filtreler artık cpvBolumler döndürüyor
- Frontend: Sektörler'in hemen altına CPV Kodu filtresi (arama + çoklu seçim), ihale kartında CPV rozeti, landing page'de yeni özellik: "AB uyumlu CPV sınıflandırması — Türkiye'de ilk"
2026-08-30 14:00:56 +03:00
hikmettan d9ca8a5b52 fix: kurum adi basligi eziyordu (gida ihalesi 'Hastanesi' yuzunden Saglik) — parcalar oncelik sirasiyla taraniyor 2026-08-30 02:05:01 +03:00
hikmettan 80bb4bc6c4 fix: doküman sınıflandırması ilanın tamamını tarayıp yanlış sonuç üretiyordu — yalnızca işin konusu taranıyor, eski kayıtlar geçersiz 2026-08-30 01:52:31 +03:00
hikmettan 495118ef9e feat: doküman metninden sektör zenginleştirme — başlığı sinyalsiz ilanlar ilan metninden sınıflandırılıyor (kalıcı önbellekli) 2026-08-30 01:39:46 +03:00
hikmettan 62d1b8844e fix: DT ve arşiv kayıtları sektör alanına ihale türü yazıyordu — sektör filtresi bu kayıtları bulamıyordu 2026-08-30 00:28:32 +03:00
hikmettan fd78b6e7d9 feat: 30 günlük havuz referansı (kalıcı erime) + 25 sektör, çoklu sektör seçimi ve sunucudan gelen tek kaynaklı liste 2026-08-30 00:24:33 +03:00
hikmettan 8ced4d73d5 fix: havuz uyarısı tek ölçüm yerine 7 günlük ortalamaya kıyaslasın (yavaş erime de yakalansın) 2026-08-29 23:22:16 +03:00
hikmettan eb20a05478 feat: havuz izleme — ölçüm geçmişi, ani düşüş/eksik yükleme uyarısı, /api/health/havuz 2026-08-29 22:25:55 +03:00
hikmettan 65997055fe feat: abonelik iptal ekranı (ayrılma nedeni + açıklama) & gerçek abonelik rozeti (yönetici erişimi aboneliği gizlemesin) 2026-08-29 21:47:58 +03:00
hikmettan 4b4f8a6951 chore: /api/billing/plans fiyat kimliği + webhook tanılaması 2026-08-29 12:36:23 +03:00
hikmettan 8bb3d6ee48 feat: yıllık abonelik (varsayılan) — Stripe yıllık fiyatlar + periyot checkout'a geçiyor 2026-08-29 11:58:46 +03:00
hikmettan f04401031c feat: sunucu tarafı hesap altyapısı — Stripe webhook, kotalar, gerçek API anahtarı ve belge deposu
Denetlenen 5 teknik borç kalemi de kapatıldı:

1) Stripe webhook + gerçek yetkilendirme (ÖNCEDEN YOKTU)
   - POST /api/billing/webhook — SDK'sız HMAC-SHA256 imza doğrulaması,
     tekrar saldırısına karşı ±300sn zaman penceresi, timingSafeEqual
   - checkout.session.completed / subscription.created|updated|deleted işlenir
   - Ödeme → kullanıcının planı sunucuda güncellenir (önce ödeme hiçbir şeyi
     değiştirmiyordu)

2) Plan kotaları GERÇEKTEN uygulanıyor (önce sadece tanımlıydı)
   - server/entitlements.mjs: plan hakları + kotaKontrol()
   - ekip, belge adedi/boyutu, API anahtarı kotaları sunucuda zorlanır
   - Kota aşımında 402 + anlaşılır Türkçe mesaj; arayüzde KotaUyarisi bandı
   - /api/me/plan: plan, haklar ve gerçek kullanım

3) Kullanıcı verisi localStorage'dan sunucuya taşındı
   - server/user-store.mjs: kullanici_verisi tablosu (izinli anahtar listesi)
   - AppContext girişte sunucudan yükler, değişiklikleri sunucuya yazar;
     localStorage yalnızca çevrimdışı önbellek
   - Artık cihaz değişince veri kaybolmuyor

4) API anahtarları gerçek (önce Math.random ile tarayıcıda üretiliyordu)
   - Sunucuda crypto.randomBytes ile üretilir, YALNIZCA sha256 hash'i saklanır
   - Ham değer bir kez gösterilir; verifyApiKey ile doğrulanır, son kullanım
     izlenir, iptal anında geçerli olur

5) Belge deposu gerçek (önce boyut Math.random'dı, dosya hiç yüklenmiyordu)
   - Dosyalar kalıcı volume'e yazılır, boyut GERÇEK bayt
   - Auth'lu indirme (blob), silme diskten de siler, kota sunucuda uygulanır
2026-08-29 10:40:05 +03:00
hikmettan f73c7bd6cb feat: konu bazlı bildirim + gerçek site tarama
1) Bildirim artık KONU odaklı (il değil)
   - Kullanıcı hangi konularda ihale alabildiğini seçer; varsayılan tüm Türkiye
   - İl/alım türü/favori kurum 'daha da daralt' altında isteğe bağlı
   - notify-store: konular kolonu + otomatik şema geçişi
   - notifier: konu başlıkları ihale başlığı/sektör/branş/kalemlerde aranır

2) Gerçek firma sitesi tarama (webScraperService mock'unun yerine)
   - server/site-scan.mjs: siteyi GERÇEKTEN çeker, ana sayfa + ürün/hizmet/
     hakkımızda iç sayfalarını tarar, HTML'i metne çevirir, AI ile firmanın
     tedarik edebildiği ihale konularını çıkarır ('uydurma' yasaklı prompt)
   - /api/company/scan (auth'lu)
   - ai.mjs: systemOverride desteği (farklı görevler için ayrı sistem promptu)
   - Arayüz: 'Sitenizi tarayıp konuları otomatik önerelim' → tıklayarak ekleme

Not: eski webScraperService.js sabit anahtar-kelime sözlüğüydü; domain adında
'tanriverdi' geçince tıbbi ürün listesi uyduruyordu.
2026-08-29 10:19:44 +03:00
hikmettan b44b45c67e chore: /api/notify/email/config gönderen adresini ve EMAIL_FROM'un ayarlı olup olmadığını raporlasın
EMAIL_FROM'un konteynere ulaşıp ulaşmadığını tahmin etmek yerine ölçmek için.
2026-08-29 09:58:14 +03:00
hikmettan c64889782f feat: yönetici için 'şimdi gönder' (POST /api/notify/run?zorla=1)
Bildirim turunu temel-alma adımını atlayarak çalıştırır; abonelik kriterine uyan
ihaleler hemen e-posta olarak gönderilir. Test ve talep üzerine özet için.
2026-08-29 03:22:03 +03:00
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
hikmettan 667b335ea7 fix: 10 dakikada bir havuz 120 kayda düşüyordu
Sorun: önbellek süresi dolunca getTenders cache.tenders'ı ilk partiyle (120 ilan)
DEĞİŞTİRİYOR, kalanı arka planda dolduruyordu. Yani her 10 dakikada bir kullanıcı
birkaç dakika boyunca eksik liste görüyordu (canlıda 8.258 havuza karşı 5.319
yüklü olarak yakalandı).

Çözüm: yenilemede mevcut dolu liste sunulmaya devam ediyor; yeni liste ayrı
kurulup TAMAMLANINCA atomik olarak devreye alınıyor. Yalnızca ilk açılışta
(elde hiç veri yokken) ilk parti anında gösteriliyor.

Doğrulama: zorla yenileme sırasında liste 3.795'te sabit kaldı (öncesinde 120).
2026-08-29 03:03:03 +03:00
hikmettan 03d5a04c91 perf: canlı ihale havuzunun tamamı yükleniyor
Sorun: /api/tenders yalnızca 1.060 kayıt döndürüyordu — üç sabit sınır yüzünden
(ilan.gov.tr 8 sayfa=160, DT 300, arşiv 600). Havuz ise 5.000+.

Çözüm:
- ilan.gov.tr sayfaları artık PARALEL çekiliyor (6 eşzamanlı), sıralı değil
- İlk yanıt için 6 sayfa yeter; kalan sayfalar ARKA PLANDA doldurulur, kullanıcı
  beklemez (ölçüm: ilk yanıt 0.05s, havuz arka planda 120 → 3.059 ilana çıktı)
- Sayfa sınırı 8 → 300 (6.000 ilan), DT 300 → 5.000, arşiv 600 → 5.000
- Hepsi env ile ayarlanabilir: ILAN_MAX_PAGES, ILAN_ILK_PARTI, ILAN_ESZAMANLI,
  DT_LIMIT, HISTORY_LIMIT
- totalCount artık arşivi de içeriyor; 'yüklü' sayısı 'havuz'u geçmiyor

Not: yanıt nginx tarafından gzip'leniyor (8.5x), tüm havuz ~600 KB — boyut
darboğaz değildi, sayfalama darboğazdı.
2026-08-29 02:30:49 +03:00
hikmettan 5282543471 feat: zamanlayıcı teşhis ucu + geniş arşiv dolumu
- /api/health/scheduler: DT/sonuç taraması durumu, son DT turu, boş bellek,
  Chromium varlığı — uzaktan teşhis için
- Dolum eşiği 2.000 → 25.000, pencere 180 → 365 gün (arşiv 6 aylık havuzun
  %7'sinde erken duruyordu)
2026-08-28 22:37:11 +03:00
hikmettan 1838de95f5 feat: ihale dokümanları + OKAS kalem verisi + doğrudan temin (Playwright) + kalıcı arşiv
1) İhale evrakları (yeni)
   - server/documents.mjs: EKAP doküman URL üretimi (imzalı), ilan metni +
     OKAS kalem çıkarımı, Playwright ile doküman metni (varsa)
   - /api/documents/url · /ilan · /text · /config uçları
   - TenderDocuments paneli: 'EKAP Dokümanlarını Aç' + 'İlan Metnini AI ile
     Analiz Et' (11K+ karakter gerçek ilan metni doğrudan AI'ya gider)
   - NOT: eski EKAP host'u bot isteklerine bağlantı kesiyor; bağlantı
     kullanıcının tarayıcısında açılır, sunucu tarafı metin için Chromium gerekir

2) Kalem verisi gerçek oldu
   - ekap-scan: her ihale için IhaleDetay'dan OKAS ihtiyaç kalemleri çekilir
   - history-store: okas_kalemler kolonu + otomatik şema geçişi
   - --kalem-doldur modu: mevcut kayıtlara IKN'den çözerek kalem ekler
   - scheduler: tarama sonrası kalem doldurmayı zincirler
   - PriceAnalysis: yöntem şeffaflığı notu (birim fiyatlar kamuya açık değil;
     kategori bazlı yaklaşık değer olduğu açıkça yazıyor)

3) Doğrudan temin (Playwright)
   - Dockerfile: node:22-slim + nginx + Playwright/Chromium
   - scheduler: DT worker periyodik (DT_INTERVAL_SAAT, varsayılan 4 saat)
   - Bellek koruması: boş bellek DT_MIN_BOS_MB altındaysa tarama atlanır ve
     uyarı loglanır; DT_ENABLED=0 ile tamamen kapatılabilir

4) Kalıcı arşiv
   - Coolify'da /app/server/data volume bağlandı; arşiv artık deploy'lar
     arası korunuyor

Ayrıca: TenderDetailModal'daki sabit '3-5 Firma' tahmini kaldırıldı, yerine
gerçek geçerli teklif sayısı gösteriliyor.
2026-08-28 22:31:40 +03:00
hikmettan 168052c267 feat: geçmiş veri motoru + gerçek AI + KİK kararları + TED + kırmızı bayrak + planlar
Roadmap'in 6 maddesi de gerçek çalışır halde:

1) Geçmiş veri motoru (EKAP v2)
   - server/ekap-v2.mjs: AES-256-CBC imzalı EKAP v2 istemcisi (zero-dep),
     ihale arama + sonuç ilanı parse (kazanan firma, sözleşme bedeli,
     yaklaşık maliyet, teklif sayısı), TR firma adı normalize
   - server/history-store.mjs: SQLite deposu (IKN+kısım anahtarlı, upsert)
   - server/ekap-scan.mjs: tarih aralıklı artımlı tarayıcı (6 paralel)
   - server/scheduler.mjs: sunucu içi periyodik tarama (SCAN_* env ile)
   - /api/tenders artık geçmiş sonuçları da birleştiriyor → kazanan firma,
     rakip ve kırmızı bayrak analizleri gerçek veriyle doluyor

2) Gerçek AI şartname analizi
   - server/ai.mjs: 6 sağlayıcılı fallback zinciri (GLM/DeepSeek/Gemini/
     Grok/GPT/Claude), JSON şemalı çıktı, uydurma yasak promptu
   - pdfjs-dist ile tarayıcıda PDF metin çıkarma (dinamik import)
   - AiSpecAnalyzerModal: sahte sonuç kaldırıldı, gerçek dosya + dürüst hata

3) KİK kurul kararları
   - server/kik.mjs: EKAP v2 JSON API (Sec-1..6 imzalı), uyuşmazlık/
     düzenleyici/mahkeme; karar tam metni (ayrı AES anahtarıyla URL)
   - KikDecisions sekmesi: arama + takipteki idarelerden hızlı öneri

4) Fiyatlandırma + ödeme
   - server/billing.mjs: Stripe Checkout (zero-dep, form-encoded)
   - Pricing sekmesi: 3 plan, aylık/yıllık, yapılandırılmadıysa dürüst uyarı

5) TED (AB ihaleleri)
   - server/ted.mjs: anonim TED v3 API, 3 mod (açık/tüm TR/AB'de kazanan TR)
   - rate-limit korumalı (429 HTML gövdesi ele alınır), çok dilli alan çözümü

6) Kırmızı bayrak & şeffaflık
   - analytics.js: redFlags (tek teklif, düşük kırım, tahmin üstü bedel,
     aşırı düşük teklif, tedarikçi yoğunlaşması) + marketConcentration (HHI)
   - RedFlags sekmesi: seviyeli uyarılar + yöntem şeffaflığı notu
2026-08-28 10:37:50 +03:00
hikmettan 3e129c2988 feat: tüm mock veriyi kaldır + Resend e-posta entegrasyonu
- mockTenders.js silindi; referans listeleri (sektör/il/kurum) constants.js'e taşındı
- AppContext: tenders/liveStats/competitorWatchlist/vaultDocuments/notifications/
  savedFilters/apiKeys/scrapedCapabilities → gerçek/boş; sahte Math.random online
  sayacı kaldırıldı; competitor & belge kasası artık localStorage
- LiveStatsBar: gerçek alanlar (canlı havuz/yüklü/bugün/kalem/takibimdeki)
- AnalyticsDashboard: KPI'lar (katılım/kazanma oranı/kazanılan bedel) + aylık hacim
  + sektörel dağılım gerçek companyStats/monthlyStats/sectorStats'tan; ciro hedefi
  kullanıcı tarafından belirlenir; veri yoksa dürüst boş durum
- ContractSearch & Rakip Radar: gerçek firmStats
- analytics.js: companyStats/monthlyStats/parseDateTR eklendi
- Resend: server/email.mjs + /api/notify/email/config + /api/notify/email/test
  (auth'lu, kullanıcının kendi adresine); PwaSettings gerçek e-posta test kartı
2026-08-28 08:49:10 +03:00
hikmettan 13f3e9d366 feat(auth): Google ile Giriş + landing/login + çalışan çıkış + admin rolü
- server/auth.mjs: ID token'ı oauth2.googleapis.com/tokeninfo ile doğrula (ek bağımlılık yok), HMAC imzalı app token, ADMIN_EMAILS ile admin
- POST /api/auth/google, GET /api/auth/google/config, GET /api/auth/me
- LoginPage: GIS butonlu landing/giriş; giriş yapılmadan uygulama açılmaz
- Header çıkış butonu artık çalışıyor; kullanıcı adı/avatar Google'dan
- AI Model Ayarları sekmesi sadece admin'e görünür
- Env: GOOGLE_CLIENT_ID + ADMIN_EMAILS (Coolify'dan)
2026-08-28 00:43:08 +03:00
hikmettan 89cfd03d92 feat: canlı veri — ilan.gov.tr açık ihale + EKAP doğrudan temin (22/d)
- server/index.js: ilan.gov.tr proxy (açık ihale + kira/satış), TLS zincir düzeltmesi
- server/dt-worker.mjs: EKAP doğrudan temin toplayıcı (Playwright, imzasız oturum içi fetch)
- server/dt-store.mjs + il-plaka.mjs: DT verisini tender modeline çevirme
- src/services/tenderApi.js + AppContext: mock veri yerine canlı API
- Doğrudan Temin sekmesi 256 gerçek 22/d kaydıyla dolu
2026-08-26 20:45:02 +03:00