feat: takip izleme (zeyilname + sonuc bildirimi), hazirlik gorevi, belge sure uyarisi, Telegram, OCR, yasakli sorgu
28 depo taramasindan alinan alti ozellik. Kod kopyalanmadi (depolar lisanssiz), fikirler uygulandi. 1) Zeyilname / duzeltme ilani takibi (server/zeyilname.mjs): takipteki IKN'ler sunucuya yazilir; 3 saatte bir EKAP detayindan ilan sayisi ve ihale tarihi karsilastirilir, degisince "ne degisti" bildirimi (uygulama ici + e-posta + Telegram). Test: anlik kaydi eskitilince tur "yeni ilan yayimlandi; ihale tarihi 01.01.2026 → 08.10.2026" bildirimi uretti. 2) Sonuc bildirimi: takipteki ihale sonuclaninca "X firmasi Y'ye aldi, %Z kirim". Yalniz takiptekiler; kismi ihalede oran yazilmaz. 3) Ihaleden goreve (MOBIT-DURT): detayda "Hazirlik Gorevi Ac" → teslim tarihi ihale saati, pano "Teklif Hazirlaniyor", derin analizden gelen belge kontrol listesi gorevde; Kanban kartinda geri sayim rozeti. 4) Sirket belgesi sure takibi: Belge Kasasi'nda son kullanma tarihi girisi, dolmus / 30 gun icinde dolacak uyarisi; girise gunde bir bildirim. PATCH /api/me/documents/meta. 5) Telegram (server/telegram.mjs): TELEGRAM_BOT_TOKEN sunucuda; kullanici 6 haneli kodu bota yazar, "Dogrula" → getUpdates ile chat id baglanir. Arayuzde adim adim kurulum rehberi (BotFather → env → Redeploy → kod). Ihale ozeti, zeyilname ve sonuc bildirimleri Telegram'a da gider. 6) OCR (tesseract.js, tur): PDF metin katmani bossa (taranmis) ilk 8 sayfa OCR. Olcum: sentetik cetvelde guven 87, ~0,3 sn/sayfa; IKN ve kalemler dogru okundu. Kalem havuzu ve derin analiz artik taranmis PDF'ten de besleniyor. 7) Rakip Radari'nda KIK yasakli sorgusu baglantisi (firma adi panoya kopyalanir; EKAP sorguyu API'den vermiyor). Sinir deger hesaplayici KURULMADI: Kamu Ihale Genel Tebligi'ne ulasilamadi (mevzuat.gov.tr / ihale.gov.tr bu makineden erisilemiyor); formul kaynaksiz yazilmadi. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,8 @@ import { kalemleriCikar } from './kalem-cikar.mjs';
|
||||
import { searchInDocuments, searchByOkas, okasAra } from './ekap-v2.mjs';
|
||||
import { iknCikar } from './kalem-cikar.mjs';
|
||||
import { bonusHesapla, gommeDurumu, arkaPlandaIsit } from './uyum-gomme.mjs';
|
||||
import { telegramConfigured, botAdi, kodIleBagla, sendTelegram } from './telegram.mjs';
|
||||
import { bildirimleriOku, okunduIsaretle, zeyilnameDurumu, zeyilnameTara, sonucTara } from './zeyilname.mjs';
|
||||
|
||||
// Ürün araması EKAP'a uzak çağrı yapar; aynı sorgu tekrarlanırsa kaynağı yorma.
|
||||
const urunAramaCache = new Map();
|
||||
@@ -818,6 +820,52 @@ const server = http.createServer(async (req, res) => {
|
||||
if (url.pathname === '/api/health/uyum') {
|
||||
return json(res, 200, { success: true, ...gommeDurumu() });
|
||||
}
|
||||
// --- Telegram bağlantısı (kullanıcı kodu bota yazar, biz getUpdates'te görürüz) ---
|
||||
if (url.pathname.startsWith('/api/me/telegram')) {
|
||||
const user = userFromAuthHeader(req.headers['authorization']);
|
||||
if (!user) return json(res, 401, { success: false, error: 'oturum yok' });
|
||||
const tg = store.getData(user.email, 'telegram');
|
||||
if (req.method === 'GET') {
|
||||
return json(res, 200, { success: true, yapilandirilmis: telegramConfigured(), bot: await botAdi(), bagli: !!tg?.chatId, ad: tg?.ad || null, baglanma: tg?.baglanma || null });
|
||||
}
|
||||
if (req.method === 'DELETE') { store.setData(user.email, 'telegram', null); return json(res, 200, { success: true }); }
|
||||
if (req.method === 'POST') {
|
||||
try {
|
||||
const body = await readBody(req);
|
||||
if (body.islem === 'test') {
|
||||
if (!tg?.chatId) return json(res, 400, { success: false, error: 'Telegram bağlı değil' });
|
||||
await sendTelegram(tg.chatId, '✅ İhaleist bağlantı testi — bildirimler bu sohbete gelecek.');
|
||||
return json(res, 200, { success: true });
|
||||
}
|
||||
const kod = String(body.kod || '').replace(/\D/g, '');
|
||||
if (kod.length !== 6) return json(res, 400, { success: false, error: '6 haneli kod gerekli' });
|
||||
const bulunan = await kodIleBagla(kod);
|
||||
if (!bulunan) return json(res, 404, { success: false, error: 'Kod bota gönderilmiş görünmüyor. Önce bota yazın, sonra doğrulayın.' });
|
||||
store.setData(user.email, 'telegram', { ...bulunan, baglanma: new Date().toISOString() });
|
||||
await sendTelegram(bulunan.chatId, `✅ İhaleist hesabınız (${user.email}) bu sohbete bağlandı.`).catch(() => {});
|
||||
return json(res, 200, { success: true, ad: bulunan.ad });
|
||||
} catch (e) { return json(res, 502, { success: false, error: e.message }); }
|
||||
}
|
||||
}
|
||||
// --- Takip bildirimleri (zeyilname / sonuç) ---
|
||||
if (url.pathname === '/api/me/bildirimler') {
|
||||
const user = userFromAuthHeader(req.headers['authorization']);
|
||||
if (!user) return json(res, 401, { success: false, error: 'oturum yok' });
|
||||
if (req.method === 'POST') {
|
||||
const body = await readBody(req);
|
||||
return json(res, 200, { success: true, okundu: okunduIsaretle(user.email, body.idler || []) });
|
||||
}
|
||||
return json(res, 200, { success: true, bildirimler: bildirimleriOku(user.email) });
|
||||
}
|
||||
if (url.pathname === '/api/health/takip') {
|
||||
return json(res, 200, { success: true, ...zeyilnameDurumu(), telegram: telegramConfigured() });
|
||||
}
|
||||
if (url.pathname === '/api/admin/takip/tara' && req.method === 'POST') {
|
||||
const user = userFromAuthHeader(req.headers['authorization']);
|
||||
if (!user?.isAdmin) return json(res, 403, { success: false, error: 'yönetici gerekli' });
|
||||
const [z, so] = await Promise.all([zeyilnameTara(), sonucTara()]);
|
||||
return json(res, 200, { success: true, zeyilname: z, sonuc: so });
|
||||
}
|
||||
if (url.pathname === '/api/health/kalem') {
|
||||
return json(res, 200, { success: true, ...kalemStore.kalemSayilari(), depo: kalemStore.available() });
|
||||
}
|
||||
@@ -971,6 +1019,20 @@ const server = http.createServer(async (req, res) => {
|
||||
return json(res, 500, { success: false, error: 'dosya okunamadı' });
|
||||
}
|
||||
}
|
||||
// Belge üst verisi: son kullanma tarihi (süre takibi) / kategori
|
||||
if (url.pathname === '/api/me/documents/meta' && req.method === 'PATCH') {
|
||||
const user = userFromAuthHeader(req.headers['authorization']);
|
||||
if (!user) return json(res, 401, { success: false, error: 'oturum yok' });
|
||||
try {
|
||||
const body = await readBody(req);
|
||||
if (!body.id) return json(res, 400, { success: false, error: 'id gerekli' });
|
||||
const sonKullanma = body.sonKullanma ? String(body.sonKullanma).slice(0, 10) : null;
|
||||
if (sonKullanma && !/^\d{4}-\d{2}-\d{2}$/.test(sonKullanma)) return json(res, 400, { success: false, error: 'tarih YYYY-AA-GG olmalı' });
|
||||
const ok = store.updateDocument(user.email, String(body.id), { sonKullanma, kategori: body.kategori ? String(body.kategori).slice(0, 60) : null });
|
||||
if (!ok) return json(res, 404, { success: false, error: 'belge bulunamadı' });
|
||||
return json(res, 200, { success: true, belgeler: store.listDocuments(user.email) });
|
||||
} catch (e) { return json(res, 400, { success: false, error: e.message }); }
|
||||
}
|
||||
// Belge metni — docx/xlsx/zip için. PDF tarayıcıda (pdfjs) okunuyor;
|
||||
// bu türler için tarayıcıda çözücü yok, baytlar zaten burada duruyor.
|
||||
if (url.pathname === '/api/me/documents/text') {
|
||||
|
||||
Reference in New Issue
Block a user