Admin panelden yönetilen, çok müşterili ve kategorili fiyat takip sistemi.
Fiyat değişimlerinde müşterilere Telegram bildirimi gider. Ürünün adı
"Cheapo", logosu web/public/logo.png (site içinde Logo bileşeni ile
kullanılır); repo ve klasör adı geriye dönük uyumluluk için Price_Tracker_Bot
olarak kalıyor.
Price_Tracker_Bot/
├── bot/
│ ├── main.py Telegram botu (müşteri bağlama)
│ ├── checker.py Fiyat kontrolcüsü (fiyatı okur ve kaydeder)
│ ├── notifier.py Fiyat değişiminde Telegram bildirimi
│ ├── adapters/ Fiyat okuyucular (json_ld.py: düz HTTP; browser.py: gerçek tarayıcı, sırayla json_ld/next_data/data_testid/price_class dener)
│ ├── requirements.txt
│ └── .env Gizli anahtarlar (GitHub'a gitmez)
├── web/
│ ├── src/app/admin/ Admin sayfaları (yeni kayıtlar, müşteri talepleri, Premium talepleri, hesap kaldırma talepleri, müşteriler, kategoriler, kaynaklar, ürünler,takipler, fiyat geçmişi, bildirimler)
│ ├── src/app/login/ Tek giriş sayfası (admin ve müşteri için)
│ ├── src/app/portal/ Müşteri portalı (ürünlerim, bildirimler, talepler, plan, rapor, kıyas, alışveriş listesi)
│ ├── src/app/page.tsx Herkese açık tanıtım sayfası (landing page)
│ ├── src/lib/supabase/ Supabase bağlantıları (tarayıcı ve sunucu)
│ ├── src/proxy.ts Giriş koruması
│ ├── src/components/ Ortak bileşenler (yan menü, butonlar, logo)
│ ├── src/components/landing/ Landing page bölümleri (hero, 3 adımda hazır, kategoriler, SSS, planlar)
│ └── .env.local Panel ayarları (GitHub'a gitmez)
└── supabase/ Veritabanı şema dosyaları (SQL)
| Parça | Seçim |
|---|---|
| Veritabanı ve giriş | Supabase |
| Admin panel | Next.js |
| Bot | Python 3.14 |
| Fiyat okuma | httpx (düz HTTP), Playwright (gerçek tarayıcı) |
| Bildirim | Telegram Bot API |
Admin panel ──► Supabase ◄── Bot ──► Telegram
(yönetim) (veritabanı) (fiyat (müşteriye
çeker) bildirim)
- Admin, panelden müşteri, kategori ve takip edilecek ürünleri tanımlar.
- Bot, Supabase'den aktif ürünleri okur ve her ürünün fiyatını kaynağından çeker.
- Fiyat geçmişe kaydedilir. Fiyat değiştiyse ürünü takip eden müşterilere Telegram'dan bildirim gider.
Bir ürünün fiyatı müşteri başına değil, ürün başına bir kez çekilir. Aynı ürünü birden çok müşteri takip etse de kaynağa tek istek gider.
Her ürünün kendi kontrol aralığı vardır (check_interval_minutes). Kontrolcü
ne kadar sık çalıştırılırsa çalıştırılsın, yalnızca aralığı dolan ürünlere
bakar.
Kontrol aralığı en az 5 dakikadır. Bir ürünün linki değiştirilirse eski fiyat sıfırlanır, böylece bot yeni sayfadaki fiyatı eski ürünle kıyaslayıp yanlış bildirim göndermez.
Aynı turda birden fazla ürünün kontrol zamanı gelmişse, ürünler art arda
gecikmesiz çekilmez; her ürün arasında 2-5 saniye rastgele bekleme vardır
(checker.py). Bu, aynı siteye kısa sürede çok sayıda istek gidip bot
koruması tarafından engellenme riskini azaltır.
Admin panel /admin altındadır (/admin/login hariç, girişsiz erişilemez). Kök
adres (/) herkese açık tanıtım sayfasıdır (landing page).
Bir ürünün fiyatı bir önceki kontrole göre değiştiğinde, ürünü takip eden her müşteri için şu kurallara bakılır:
- Her değişimde bildir seçiliyse: her fiyat değişiminde bildirim gider.
- Hedef fiyat girilmişse: fiyat hedefin üstündeyken hedefe ya da altına inerse bildirim gider. Fiyat hedefin altında kaldığı sürece tekrar bildirim gitmez.
Ürünün ilk kez okunduğunda (önceki fiyat yokken) bildirim gitmez. Gönderilen
her bildirim notification_log tablosuna kaydedilir.
Bir müşteriye bildirim gönderimi başarısız olursa (örn. geçersiz/erişilemeyen
Telegram chat ID), bu hata sadece o müşteri için loglanır; aynı turdaki diğer
müşterilere giden bildirimleri durdurmaz (notifier.py).
Her müşterinin panelde otomatik üretilen bir bağlama kodu vardır. Panelden
müşteri için bağlama linki kopyalanıp müşteriye gönderilir. Müşteri linke
tıklayıp Start'a bastığında (bot /start KOD komutunu alır) bot Telegram
chat ID'sini o müşteriye kaydeder.
Panele yalnızca tek bir admin hesabı girebilir. Giriş Supabase Auth ile
yapılır. Veritabanında her tabloda satır bazlı güvenlik (RLS) açıktır ve
kurallar is_admin() fonksiyonuna bağlıdır; bu fonksiyon yalnızca admin
hesabının kimliğini (UID) kabul eder. Başka bir hesap açılsa bile hiçbir veri
görülemez. Bot ise service_role ile çalışır ve bu kuralları atlar.
Giriş tek bir sayfadan (/login) yapılır. Yönlendirme mantığı proxy.ts'te:
giriş yapan hesabın customers tablosunda kaydı varsa müşteri portalına
(/portal) gider. Kaydı yoksa, e-postası ADMIN_EMAIL ortam değişkenindeki
adresle eşleşiyorsa admin panele (/admin) gider; eşleşmiyorsa (ne
müşteri kaydı ne admin e-postası) sistem bu hesabı güvenlik gereği otomatik
oturumdan çıkarır ve /login'e geri gönderir. Bu kontrol, kendi kendine kayıt
(/signup) açıldıktan sonra eklendi: daha önce "müşteri kaydı yoksa
admindir" varsayımı vardı, ki tek admin hesabı dışında kimsenin auth hesabı
olmadığı dönemde güvenliydi; ama başarısız/yarım kalan bir kayıt denemesi
(auth hesabı oluşur ama customers satırı oluşmazsa) o kullanıcıyı
yanlışlıkla admin gibi gösterebilirdi — artık böyle bir hesap gerçek admin
verisini göremez (RLS zaten engeller) ama artık admin paneline hiç
yönlendirilmez de, direkt atılır. Her istekte bu kontrol tekrar yapılır,
yani bir müşteri adres çubuğuna elle /admin yazsa da otomatik olarak
/portal'a geri gönderilir (ve tersi). Müşteri çöp kutusuna atılmışsa
(customers.deleted_at dolu) bu kontrol "kayıt yok" saymaz; oturumu
kapatılıp /login'e "hesap kaldırılmış" mesajıyla geri gönderilir.
Giriş sayfası koyu temalı, ortada bölünmüş kartlı bir tasarıma sahiptir
(components/login-showcase.tsx, components/login-showcase-data.ts).
Arka planda örnek ürün/fiyat kartları çapraz şeritler halinde sürekli akar
(saf CSS animasyonu, globals.css içindeki lg- ön ekli kurallar); bu
kartlardaki ürün/fiyat bilgileri sayfa herkese açık olduğu için tamamen
kurgusal örnek verilerdir, gerçek müşteri verisi değildir. Sağdaki form
e-posta/şifre alanları, şifre göster-gizle, "Beni hatırla" (şu an yalnızca
görsel, kalıcı bir davranışı yok) ve "Şifremi unuttum" (henüz pasif, ileride
eklenecek) içerir.
Ziyaretçi landing page'deki "Kayıt ol" butonuyla (/signup) ad, e-posta ve
şifresini girip hesabını anında açar; admin onayı gerekmez. Sunucu tarafında
(app/actions.ts → signUpCustomer) önce Supabase Auth'ta kullanıcı
oluşturulur (email_confirm: true, doğrulama maili beklenmez), sonra
customers tablosunda plan: "free" ile bir kayıt açılır, ardından otomatik
oturum açılıp /portal'a yönlendirilir. İki adım da service_role ile
(RLS'i atlayarak) yapılır çünkü bu noktada henüz bir oturum yoktur; adım 2
başarısız olursa adım 1'de açılan auth hesabı geri silinir (yetim hesap
kalmasın diye).
Önceden bu akış "talep bırak, admin elle hesap açsın" şeklindeydi (leads
tablosu + Talepler sayfası); bu sistem tamamen kaldırıldı. Premium'a geçmek
isteyen müşteri önce Ücretsiz kaydolur, sonra portaldan (Planım sayfası)
Premium talebi gönderir — aşağıya bakın.
Hesabı açılan müşteri /login'den giriş yapıp /portal'a yönlenir. Portal
koyu temalıdır, masaüstünde yan menü, telefonda alt sekmelerle çalışır
(components/portal-shell.tsx). Sayfalar:
- Ürünlerim: özet kutuları, hedefinin altına inen ürün için fırsat bandı, ürün kartları (fiyat grafiği, en düşük/en yüksek fiyat, değişim yüzdesi, hedefe yakınlık, favori yıldızı), arama, sıralama ve kart/liste görünümü. Sağ kolonda plan kartı, Telegram durumu ve son 3 bildirim.
- Favorilerim: "Ürünlerim"deki bir kartın yıldızına basılan ürünler
burada toplanır (
subscriptions.is_favorite). Ayrı, hafif bir takip katmanı değildir; sadece zaten takip edilen ürünler arasında hızlı erişim sağlar, bildirim ayarını etkilemez. - Bildirimlerim: Telegram'dan gelen bildirimlerin günlere göre kaydı.
- Talep gönder: kategori ve ürün seçerek talep. Aynı isimli ürünleri farklı marketlerde ayırt edebilmek için her ürünün yanında hangi marketten olduğu görünür. Seçilen her ürün için müşteri kendi bildirim tercihini belirler ("Her değişimde bildir" ve/veya hedef fiyat); en az biri seçilmeden talep gönderilemez. Plan sınırı seçim sırasında önizlenir ve aşılırsa gönderilmeden uyarılır. Taleplerin durumu adım adım izlenir (Gönderildi, İnceleniyor, Takibe eklendi).
- Planım: Ücretsiz ve Premium planın karşılaştırması.
- Hesap ayarları: profil adı, şifre değiştirme (Supabase Auth üzerinden,
mevcut şifre doğrulanır), Telegram bağlantısını kesme, ve "hesabı kaldır"
talebi gönderme (
customers.removal_requested_at; admin panelde Hesap kaldırma talepleri sayfasından görür, onaylarsa hesap normal "sil" akışıyla çöp kutusuna atılır, reddederse talep temizlenir). - Yardım & SSS: üstte üç hızlı rehber kartı (ilk ürünü takibe alma,
Telegram bağlama, ilk sepeti oluşturma; karta tıklayınca adımlar açılır),
altında kısa sorular (akordiyon). İçerik
components/portal-help.tsxiçinde sabit metindir, veritabanına bağlı değildir. - Alışveriş listesi (yalnızca Premium): market ürünlerinden sepet oluşturma ve satıcı bazında toplam kıyaslama.
- Haftalık rapor ve Ürün kıyası (yalnızca Premium): Ücretsiz müşteri kilitli önizleme görür.
Haftalık rapor, price_daily görünümündeki günlük son fiyatlardan ve bildirim
kayıtlarından hesaplanır (son 7 ya da 30 gün); ürün listesinde ve seçili ürün
detayında kategorinin yanında hangi mağazadan takip edildiği de gösterilir.
Ürün kıyası, aynı ürünün farklı satıcılardaki fiyatını yan yana gösterir: yönetici, aynı ürünün her satıcıdaki
kaydını admin panelinde ürün düzenlerken aynı karşılaştırma grubuna
(products.comparison_group, örn. coca-cola-1-5l) koyar. Müşteri bir ürünü
takip ettiğinde, o ürünün grubundaki tüm satıcılar kıyas sayfasında görünür.
Karşılaştırma grupları, içindeki ürünlerin kategorisine göre (Market,
Elektronik, Giyim vb.) ayrı başlıklar altında gruplanır; üstte her
kategoriye hızlıca kaydıran butonlar vardır (birden fazla kategori olduğunda
anlamlı, tek kategoride de başlık yine görünür). Her kategori bölümü kendi
içinde bağımsız sayfalanır (5 ürün grubu/sayfa), çok sayıda karşılaştırma
grubu birikse de sayfa uzayıp gitmez.
"Destek al" ve "Premium için yaz" düğmeleri (hem portal ana sayfasındaki plan
kartında hem Planım sayfasında) Gmail'in web compose linkine
(https://mail.google.com/mail/?view=cm&fs=1&to=norelsoft@gmail.com) yeni
sekmede açılır. mailto: yerine bu yöntem seçildi çünkü kullanıcının
bilgisayarında varsayılan bir masaüstü mail uygulaması kurulu olmayabilir;
bu durumda mailto: linki sessizce hiçbir şey yapmaz.
Alışveriş listesi tek bir sepetten ibaret değildir: müşteri birden fazla
sepet oluşturabilir (basket_lists tablosu: customer_id, name,
category_id), her sepetin bir sektörü (kategorisi) vardır ve "Ürün ekle"
paneli o sepette sadece aynı sektördeki takip edilen ürünleri önerir. Sepetler
üstteki şeritte sekme gibi durur; sekmenin ⋮ menüsünden yeniden adlandırılabilir
ya da silinebilir (silinince içindeki ürünler de gider). Bir sepetin içindeki
ürünler basket_items tablosunda tutulur (list_id + product_id); "Ürün
ekle" panelinden eklenmeyen hiçbir ürün listede görünmez. Eklenen ürün bir
karşılaştırma grubuna bağlıysa, o gruptaki diğer satıcıların fiyatları da
otomatik olarak aynı satırda gösterilir ve satıcı bazında toplam hesaplanıp en
ucuz satıcı vurgulanır; tek satıcıdan takip edilen ürünler de eklenebilir,
sadece kıyaslama çıkmaz.
Farklı marka ama aynı ihtiyacı karşılayan ürünler (örn. bir markette Banvit
Tavuk Göğsü, başka markette Erpiliç Tavuk But) admin panelinde aynı
karşılaştırma grubuna konamaz, çünkü gerçekte aynı ürün değiller. Bunun için
müşteri, sepetindeki iki veya daha fazla ürünü elle "eşdeğer" olarak
eşleştirebilir: "Eşdeğer olarak eşleştir" ile seçim moduna girer, ürünleri
işaretler, bir grup ismi verir (basket_matches tablosu: customer_id,
name; basket_items.match_id bu gruba bağlanır). Eşleştirilen ürünler tek
satırda, her birinin hangi markette hangi ürün olduğu küçük bir alt yazıyla
birlikte gösterilir; eşleştirme istendiği an geri alınabilir.
Portalın veritabanı erişimi satır bazlı güvenlik (RLS) kurallarıyla sağlanır:
müşteri sadece kendi customers, subscriptions, customer_requests,
notification_log ve takip ettiği ürünlerin price_history satırlarını
okuyabilir; ayrıca aktif ürünleri ve aktif kaynakları okuyabilir. Çoğu tabloya
yazma yetkisi yoktur (talep göndermek sunucu işleminden geçer). Tek istisna
customers tablosu: müşteri kendi satırında sadece name, telegram_chat_id
ve removal_requested_at sütunlarını güncelleyebilir (RLS satır kuralı +
sütun bazlı grant) — profil adı, Telegram bağlantısını kesme ve hesap
kaldırma talebi bu sayede çalışır. plan, deleted_at gibi hassas alanlara
müşteri elle yazamaz.
Müşteri portaldan bir kategori ve o kategorideki ürünleri seçip talep
gönderir (customer_requests + customer_request_products); her ürün için
kendi bildirim tercihini de (hedef fiyat ve/veya "her değişimde bildir")
aynı anda belirler, bu tercihler customer_request_products tablosunda
(target_price, notify_on_any_change) saklanır. Admin, panelin
Müşteri talepleri sayfasından bu talepleri görür ve durumunu değiştirir
(Bekliyor / İnceleniyor / Tamamlandı / Reddedildi). Durum Tamamlandı
yapıldığında seçilen ürünler, müşterinin talep sırasında belirlediği hedef
fiyat ve bildirim tercihiyle birlikte otomatik olarak müşterinin takiplerine
(subscriptions) eklenir; admin'in ayrıca Takipler sayfasından ayarlamasına
gerek kalmaz (isterse yine değiştirebilir).
Ücretsiz planda en fazla 1 kategori ve 3 ürün takip edilebilir (premium'da
sınır yok). Bu sınır, müşteri portaldan yeni talep gönderirken kontrol edilir;
sınır aşılıyorsa talep reddedilir ve mevcut kullanım anlaşılır bir mesajla
gösterilir. Sınırlar web/src/lib/plan-limits.ts dosyasında tanımlıdır.
Admin, Takipler sayfasından bu sınırın üstünde elle ekleme yapabilir.
Admin, Ürünler sayfasından bir ürüne "Şimdi kontrol et" diyebilir. Bu, ürünün
force_check_requested bayrağını işaretler. Bot en fazla 1 dakika içinde bu
bayrağı görüp ürünü hemen kontrol eder ve bayrağı sıfırlar; ürünün kendi
kontrol aralığını beklemesine gerek kalmaz.
Kök sayfa (/) bölümlere ayrılmıştır ve her bölüm web/src/components/landing/
altında ayrı bir dosyadır: hero (cihaz sahnesi ve kayan ürün duvarı), 3 adımda
hazır, kategoriler, Merak edilenler (bot sohbeti şeklinde), planlar ve talep
formu. Talep formu web/src/components/lead-form.tsx dosyasındadır ve
leads tablosuna kaydeder.
Renkler globals.css içindeki @theme bloğunda tanımlıdır. Animasyonlar
saf CSS ile yazılmıştır ve bölüme göre önek alır: hv- (hero), hiw- (3 adımda
hazır), pl- (planlar), pf- (SSS sohbeti), lf- (talep formu). Kaydırınca
başlayan animasyonlar küçük bir IntersectionObserver ile tetiklenir, ayarlarda
"hareketi azalt" seçiliyse tüm animasyonlar kapanır (prefers-reduced-motion).
Hero'daki cihaz ekranlarında görünen ürünler ve fiyatlar örnek veridir, gerçek takip verisi değildir.
Admin paneli koyu temalıdır ve müşteri portalından ayrıdır. web/src/app/admin/layout.tsx
içindeki force-dark sınıfı, globals.css'teki özel dark varyantı sayesinde
admin içindeki tüm dark: sınıflarını işletim sistemi temasından bağımsız açar;
giriş sayfası ve portal etkilenmez. Renkler yine globals.css'te, .force-dark
içinde gri ve yeşil paletin yeniden tanımlanmasıyla (koyu turkuaz) verilir.
- Yan menü (
sidebar.tsx): Gelenler, Katalog, Takip gruplarına ayrılmıştır. Gelenler grubu: Yeni kayıtlar, Müşteri talepleri, Premium talepleri, Hesap kaldırma talepleri. Bekleyen kayıt sayısı olanlarda (Müşteri/Premium/Hesap kaldırma talepleri — Yeni kayıtlar hariç, o bir bekleme kuyruğu değil bilgi akışıdır) kırmızı rozet görünür (sayılaradmin/layout.tsx'te hesaplanır). - Üst çubuk ve arama (
admin-topbar.tsx,admin-nav.ts): sayfa başlığı ve Ctrl+K ile açılan sayfa arama penceresi. - Ana sayfa: "Dikkat gerektirenler" (okunamayan ürünler, bekleyen talepler, Telegram'a bağlanmamış müşteriler), istatistik kartları, son bildirimler ve ürün durumu. Hepsi Supabase'den gerçek veriyle gelir.
- Ürünler: arama, durum filtreleri (Hatalı, Sırada, Pasif), durum rozetleri
ve tek tıkla "şimdi kontrol et". Ürün ekleme ve düzenleme sağdan açılan
çekmecede yapılır (
product-drawer.tsx); aynı çekmecede ürünün karşılaştırma grubu da girilir. - Yeni kayıtlar: kendi kendine kayıt olan müşteriler, en yeni üstte;
isim, e-posta (Supabase Auth'tan
service_roleile okunur,customerstablosunda e-posta tutulmaz), plan, Telegram durumu ve katılım tarihi. Arama, aktif/pasif yapma ve silme (çöp kutusuna atma) içerir. - Müşteri talepleri: gelen kutusu düzeni; durum filtreleri, arama ve renkli durum seçicisi.
- Premium talepleri: müşterinin portaldaki "Planım" sayfasından gönderdiği Premium'a geçme talepleri (bıraktığı telefon/not varsa görünür). "Onayla, Premium yap" planı otomatik Premium yapar; "Reddet" talebi reddedilmiş olarak işaretler. İkisi de geçmişte kalır (silinmez), sadece bekleyen taleplerde buton görünür.
- Hesap kaldırma talepleri: müşterilerin "Hesap ayarları"ndan gönderdiği kaldırma taleplerinin listesi, üstte arama çubuğu. Satıra tıklayınca genişler ve hesabın etkisini gösterir (takip edilen ürün sayısı, Telegram durumu, üyelik tarihi, aldığı bildirim sayısı); "Talebi reddet" (talebi temizler) ve "Hesabı kalıcı olarak sil" (normal çöp kutusu akışına atar) butonları vardır.
- Bildirimler: günlere göre gruplanmış akış, mesajlar Telegram balonu şeklinde, müşteri seçici ve arama.
- Fiyat geçmişi: her kaydın bir önceki kayda göre değişimi, ürün seçilince fiyat grafiği (güncel, en düşük, en yüksek, toplam değişim).
- Kategoriler ve Kaynaklar: kart görünümü; her kartta bağlı ürün (ve kategoride müşteri) sayısı. Bağlı ürünü olan kategori ya da kaynak silinemez.
- Takipler: müşteriye göre gruplu kartlar; her müşteride Telegram durumu (bağlı değilse bildirim gidemediği belirtilir), ürünün hangi marketten takip edildiği (etiket), güncel fiyatı ve hedefe uzaklığı. Ekleme ve düzenleme sağdan açılan çekmecede yapılır.
- Müşteriler: kart görünümü; plan (Ücretsiz/Premium) düzenleme çekmecesinden değiştirilebilir, bağlı olmayan müşterinin bağlama linki karttan kopyalanır. Kartta kategoriler, takip sayısı ve müşterinin bildirimlerine kısayol vardır.
Tüm admin sayfaları yenilenmiştir.
Web paneli Vercel'e deploy edilir (repo mono-repo olduğu için Root Directory
web seçilmelidir). Gerekli ortam değişkenleri web/.env.local ile aynıdır:
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY,
TELEGRAM_BOT_USERNAME, SUPABASE_SERVICE_ROLE_KEY, ADMIN_EMAIL.
master'a her push otomatik yeni bir production deploy tetikler.
Bot (bot/main.py) sürekli çalışan bir süreçtir (Telegram dinleme + 60
saniyede bir fiyat kontrolü), bu yüzden Vercel gibi serverless platformlarda
çalışamaz. Sürekli ayakta kalan bir süreç çalıştırabilen ayrı bir yerde
(örn. Railway, bir VPS) host edilmesi gerekir; Root Directory bot, Start
Command python main.py olmalıdır.
- Python 3.10 veya üstü
- Git
- Bir Supabase projesi
- BotFather'dan alınmış bir Telegram bot tokenı
bot/.envdosyasını oluştur (GitHub'a gitmez):
TELEGRAM_BOT_TOKEN=
SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
Service role anahtarı çok güçlüdür: sadece botta kullanılır, panele ve GitHub'a asla konmaz.
- Sanal ortamı kur ve kütüphaneleri yükle:
cd bot
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txtBotlara engel koyan siteleri (örn. Cloudflare kullananları) okumak için gerçek bir tarayıcı gerekir. Bunun için Playwright'ı ve Chromium'u bir kez kur:
pip install playwright
playwright install chromiumBu yalnızca kaynağın yöntemi Tarayıcı (Playwright) olan ürünler için kullanılır; diğer ürünler eskisi gibi düz HTTP ile okunur.
- Botu çalıştır:
python main.pypython main.py çalıştığı sürece bot hem Telegram'ı dinler hem arka planda her dakika hangi ürünlerin kontrol zamanı geldiğine bakar, ayrı bir komut gerekmez. python checker.py'yi elle bir kez çalıştırmak (tek seferlik test için) hâlâ mümkündür.
Yeni bir terminal açıldığında önce sanal ortam tekrar etkinleştirilir
(.venv\Scripts\Activate.ps1).
web/.env.localdosyasını oluştur (GitHub'a gitmez):
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
TELEGRAM_BOT_USERNAME=
SUPABASE_SERVICE_ROLE_KEY=
ADMIN_EMAIL=
TELEGRAM_BOT_USERNAME, müşteri bağlama linkini oluşturmak için botun
kullanıcı adıdır (başında @ olmadan). ADMIN_EMAIL, admin panele kimin
girebileceğini belirleyen tek e-posta adresidir (bkz. "Panel erişimi");
büyük/küçük harf ve boşluk farkı olmadan Supabase Auth'taki gerçek admin
e-postanla birebir aynı olmalı.
Panelde yalnızca sb_publishable_ ile başlayan anahtar kullanılır.
Service role anahtarı panele asla konmaz.
SUPABASE_SERVICE_ROLE_KEY sadece hesap açma işleminde, sunucu tarafında
("use server" dosyasında) kullanılır ve tarayıcıya asla gönderilmez.
NEXT_PUBLIC_ öneki almadığından client koduna erişilemez.
- Paketleri kur ve çalıştır:
cd web
npm install
npm run devPanel http://localhost:3000 adresinde açılır.
- Supabase'de Authentication → Users bölümünden admin hesabını oluştur.
| Tablo | Amaç |
|---|---|
categories |
Market, E-ticaret, Uçak bileti vb. |
customers |
Müşteriler, Telegram chat ID, bağlama kodu |
customer_categories |
Müşteri - kategori eşleşmesi |
sources |
Fiyat kaynağı ve çekme yöntemi (api, json_ld, http, browser) |
products |
Takip edilen ürünler (ürün başına tek kayıt) |
subscriptions |
Müşteri - ürün takibi, hedef fiyat |
price_history |
Fiyat geçmişi |
notification_log |
Gönderilen bildirim kayıtları |
customer_premium_requests |
Portaldan gönderilen Premium'a geçme talepleri (status: bekliyor/onaylandi/reddedildi) |
Ek olarak:
products.comparison_group: aynı grup adını taşıyan ürünler aynı ürünün farklı farklı satıcılardaki kayıtlarıdır (Ürün kıyası ve Alışveriş listesi bu alanı kullanır). Admin panelde Ürünler sayfasının liste/grup görünüm anahtarıyla bu gruplar kart halinde bir arada görülebilir.basket_lists: müşterinin oluşturduğu sepetler (customer_id,name,category_id). Her sepetin bir sektörü vardır; "Ürün ekle" paneli o sepette sadece aynı sektördeki ürünleri önerir.basket_items: bir sepete eklenen ürünler (list_id,product_id,match_id).basket_matches: müşterinin elle "eşdeğer" olarak birleştirdiği ürün grupları (customer_id,name).customers.removal_requested_at: müşteri "Hesap ayarları"ndan hesap kaldırma talebi gönderdiğinde dolar; admin panelde Hesap kaldırma talepleri sayfasından görülür, onaylanırsa hesap normal silme akışıyla çöp kutusuna atılır, reddedilirse bu alan temizlenir.customer_premium_requests: müşteri aynı anda sadece bir bekleyen talebe sahip olabilir (customer_idüzerinde kısmi unique index,status = 'bekliyor'iken). Onaylanan/reddedilen talepler silinmez, geçmiş olarak tabloda kalır.price_daily(görünüm):price_history'nin günlük son fiyat özeti (Türkiye saatine göre).security_invokerile çalışır, yani sorgulayan kullanıcının RLS izinleri geçerlidir.
Her kaynağın bir okuma yöntemi vardır (sources.method, panelde Kaynaklar
sayfası). Şu an iki yöntem gerçekten çalışır durumdadır; dropdown'da başka
seçenek yoktur (ileride resmi API sunan bir kaynak eklenirse yeni bir yöntem
olarak eklenir):
-
JSON-LD (sayfa verisi): düz HTTP isteği ile sayfadaki JSON-LD fiyat verisi okunur. Hızlıdır, ama bazı siteler JSON-LD'de güncel olmayan ya da yanlış satış kanalına ait bir fiyat verebilir (bkz. aşağıdaki not).
-
Tarayıcı (Playwright): headless Chromium ile sayfa açılır (
bot/adapters/browser.py). Cloudflare gibi bot korumasını geçmek için varsayılan "HeadlessChrome" kimliği yerine normal bir Chrome kimliği kullanılır. Sayfa açıldıktan sonra fiyat, sırayla dört ayrı ayrıştırıcıyla aranır, ilk bulan kazanır — sırası bilerek "sayfada gerçekten görünen fiyat" önce, "sayfanın beyan ettiği yapısal veri" en son olacak şekildedir:- CSS class'ı
normalPrice/discountedPrice(adapters/price_class.py) — class adı "...normalPrice" ya da "...discountedPrice" ile biten fiyat kutusu (indirimli ürünlerdediscountedPricetercih edilir). data-testid(adapters/data_testid.py) — sayfanın düz HTML'indedata-testid="discountedPrice"etiketli fiyat kutusu.__NEXT_DATA__(adapters/next_data.py) — Next.js sitelerinin sayfaya gömdüğü JSON içindeki fiyat.- JSON-LD (
adapters/json_ld.py) — sayfanın kendiProductnesnesininoffersalanı (sayfadaki rastgele birOfferdeğil, özellikle bu ürüne ait olan).
Her sayfa birkaç saniye sürer. Kontrolcü tarayıcıyı yalnızca bu yöntemdeki ürünler için başlatır. Hangi alt yöntemin tuttuğu ürünün
last_statusalanına yazılır ("ok · next_data"gibi) ve panelde iki yerde görünür: admin Ürünler sayfasındaki Yöntem sütununda (ürün bazında, boşsa JSON-LD ile okunmuş demektir) ve Kaynaklar sayfasındaki kart üzerinde renkli bir rozet olarak (o kaynağın ürünlerinden en az biri hangi alt yöntemle okunduysa). Aynı kaynağın farklı ürünleri, sayfa şablonları farklıysa, birden fazla alt yöntemle okunabilir — bu bir hata değildir.Neden JSON-LD en sona alındı: Bazı sitelerde JSON-LD'nin
offers.pricealanının sayfada gerçekten görünen fiyattan farklı (başka bir satış kanalına ait olabilecek) bir değer verdiği görüldü. JSON-LD hatasız, geçerli bir fiyatla "başarılı" döndüğü için hata fırlatmıyor ve döngü orada duruyordu — yani sıralamada önce olsaydı, DOM'daki doğru fiyata hiç sıra gelmezdi. Bu yüzden önce sayfada fiilen görüneni (CSS kutusu /data-testid) deniyoruz, JSON-LD sadece hiçbiri bulunamazsa son çare olarak kullanılıyor. - CSS class'ı
Yeni bir kaynak eklerken önce düz HTTP ile (JSON-LD) denenmelidir; site 403 veriyorsa kaynağın yöntemi "Tarayıcı (Playwright)" yapılır — bu durumda dört alt yöntemden hangisinin uyduğunu bot kendisi bulur, elle seçim yapılmaz. Sunucunun IP adresi bot koruması tarafından farklı değerlendirilebileceği için, canlıya alırken tarayıcı yöntemi orada da denenmelidir. Bazı siteler ürün sayfasını teslimat adresine göre kişiselleştirir; adres seçilmemiş bir oturumdan bazı ürünler gerçekte satışta olsa da 404 dönebilir — bu bot hatası değildir, ilgili ürün admin panelden pasif yapılabilir.
Admin panelde bir kayıt silindiğinde veritabanından hemen kaldırılmaz;
deleted_at sütunu doldurulur ("çöpe atılır") ve normal listelerden kaybolur.
Gerçek silme yalnızca Çöp kutusu sayfasından "Kalıcı olarak sil" ile,
onay istendikten sonra yapılır. Kapsam: products, sources, categories,
customers, subscriptions, notification_log, price_history,
customer_requests — panelde silme işlemi olan her tablo.
Genel action'lar tek dosyada toplanmıştır (app/admin/trash/actions.ts):
softDelete, restoreFromTrash, permanentlyDelete; tablo adı parametre
olarak verilir. Mevcut "sil" butonlarının fonksiyon imzaları değişmedi, sadece
içleri artık gerçek delete yerine deleted_at güncelliyor. Çöp kutusu
sayfası (app/admin/trash/page.tsx) bu 9 tablodan deleted_at dolu olan
kayıtları tek bir listede toplar; arama ve tabloya göre filtre içerir. Sol
menüdeki "Çöp kutusu" rozeti, çöpteki toplam kayıt sayısını gösterir.
Bunun güvenli çalışması için birkaç yer özellikle düzeltildi:
- Admin panelindeki tüm ana listeleme sorgularına (
Ürünler,Kaynaklar,Kategoriler,Müşteriler,Takipler,Talepler,Bildirimler,Fiyat geçmişi,Müşteri talepleri)deleted_at is nullfiltresi eklendi. price_dailygörünümü de çöpteki fiyat kayıtlarını hesaba katmayacak şekilde güncellendi (haftalık/aylık rapor bunu kullanıyor).- Bot: çöpe atılmış bir ürün artık kontrol edilmiyor (
checker.py), çöpe atılmış bir aboneliğe/müşteriye bildirim gitmiyor (notifier.py), çöpe atılmış bir müşteri Telegram'ını bağlayamıyor (main.py). - Giriş mantığı: "müşteri kaydı yoksa admindir" varsayımı yüzünden,
çöpe atılmış bir müşteri basitçe filtrelenirse yanlışlıkla admin paneline
yönlendirilebilirdi. Bunun yerine
proxy.ts(asıl güvenlik sınırı) velogin/page.tsx, müşteri bulunduğu ama çöpe atılmış olduğu durumu ayrıca kontrol eder: oturum kapatılır,/login'e "hesap kaldırılmış" mesajıyla geri gönderilir.
Not: Müşteri portalındaki (customer-facing) bazı sorgular — ör. ürün kıyası,
alışveriş listesi, portal ana sayfası — henüz deleted_at filtresi almadı;
bu sayfalarda çöpe atılmış bir ürün/kategori kısa süreliğine görünmeye devam
edebilir. İleride ele alınacak bir sonraki adım.
- Supabase projesi ve veritabanı şeması (8 tablo)
- Telegram botu oluşturuldu
- Müşteri bağlama akışı (
/start KODile chat ID kaydı) - Bot -> müşteri Telegram bildirimi doğrulandı
- Fiyat okuyucu (JSON-LD): sayfadan fiyat ve stok bilgisi çekiyor
- Kontrolcü: aktif ürünlerin fiyatını okuyup geçmişe kaydediyor
- Fiyat değişiminde Telegram bildirimi (her değişimde ve hedef fiyata düşünce)
- Ürün bazlı kontrol aralığı (sırası gelmeyen ürün atlanıyor)
- Panel iskeleti: admin girişi/çıkışı ve giriş koruması
- Panel: yan menülü düzen ve özet kartlarıyla ana sayfa
- Panel: müşteri yönetimi (ekleme, satır içi düzenleme, silme, Telegram bağlama linki)
- Panel: kategori yönetimi (ekleme, satır içi düzenleme, silme)
- Panel: kaynak yönetimi (ekleme, satır içi düzenleme, silme)
- Panel: ürün yönetimi (ekleme, satır içi düzenleme, silme, kategori ve kaynak seçimi)
- Panel: takip yönetimi (müşteri-ürün eşleştirme, hedef fiyat ve bildirim kuralı)
- Panel: fiyat geçmişi (listeleme, ürüne göre filtreleme, kayıt silme)
- Panel: bildirim kayıtları (listeleme, müşteriye göre filtreleme, kayıt silme)
- Panel: müşteriye kategori atama (Market, E-ticaret, Uçak bileti vb.)
- Panel: admin sayfaları
/adminaltına taşındı, kök adres genel kullanım için ayrıldı - Veritabanı: müşteri hesabı bağlantısı (
auth_user_id), plan alanı,leadsvecustomer_requeststabloları - Landing page: tanıtım, plan karşılaştırması ve talep formu (leads tablosuna kaydediyor)
- Panel: Talepler sayfası (leads listeleme, durum değiştirme, silme)
- Panel: talepten müşteri hesabı açma (Supabase Auth + customers kaydı)
- Tek giriş sayfası (/login), hesap türüne göre /admin veya /portal'a yönlendirme(koyu temalı, çapraz akan ürün kartlı arka plan tasarımıyla yenilendi)
- Müşteri portalı: giriş, takip edilen ürünler ve fiyatları görme
- Müşteri portalı: yeni talep gönderme (kategori + ürün seçimi) ve kendi taleplerini görme
- Panel: Müşteri talepleri sayfası, Tamamlandı'da otomatik takip ekleme
- Ücretsiz plan sınırı (1 kategori, 3 ürün), müşteri talep gönderirken kontrol ediliyor
- Bot: Telegram hesabı başka müşteriye bağlıysa anlaşılır hata mesajı (çökmüyor)
- Bot sürekli çalışır: Telegram dinleme ve fiyat kontrolü tek süreçte (60 saniyede bir kontrol turu)
- Panel: "Şimdi kontrol et" düğmesi, bot en fazla 1 dakika içinde işliyor
- Landing page yenilendi: animasyonlu hero, 3 adımda hazır, kategoriler, planlar, SSS sohbeti ve talep formu
- Admin paneli yenilendi (1. aşama): koyu tema, gruplu menü ve rozetler, Ctrl+K arama, yeni ana sayfa, Ürünler sayfası ve düzenleme çekmecesi
- Admin paneli yenilendi (2. aşama): Talepler, Müşteri talepleri, Bildirimler, Fiyat geçmişi (grafikli), Kategoriler ve Kaynaklar sayfaları
- Admin paneli yenilendi (3. aşama): Takipler ve Müşteriler sayfaları, panelden müşteri planı değiştirme
- Müşteri portalı yenilendi: koyu tema, yan menü, geniş ürün sayfası, bildirimler, gelişmiş talep formu, Planım
- Premium: Haftalık ve aylık rapor ile Satıcılar arası ürün kıyası (aynı ürünün market kayıtları admin panelinde "Karşılaştırma grubu" ile bağlanır)
- Premium: Alışveriş listesi (sektöre göre birden fazla sepet oluşturma/yeniden adlandırma/silme, satıcı bazında toplam kıyaslama, farklı markaları elle "eşdeğer" olarak eşleştirme)
- Bot: gerçek tarayıcı (Playwright) ile okuma, Cloudflare korumalı siteler için; JSON-LD, NEXT_DATA, data-testid, CSS class fiyat kutusu olmak üzere 4 ayrı ayrıştırıcıyı sırayla dener
- Panel: Ürünler sayfasında hangi ayrıştırıcının kullanıldığını gösteren "Yöntem" sütunu, liste/grup (karşılaştırma grubuna göre) görünüm anahtarı
- Panel: uzun listelerde sayfalama (Fiyat geçmişi: sunucu taraflı, sayfa başına 25/50/100/200; Ürünler, Müşteri talepleri, Bildirimler: tarayıcı taraflı, sayfa başına seçilebilir) ve Takipler'de müşteri başına akordiyon (5'ten fazla takibi olan müşteriler varsayılan kapalı başlar)
- Panel: Çöp kutusu (soft delete) — 9 tabloda silme artık geri yüklenebilir, kalıcı silme onay ister; bot ve giriş mantığı çöpe atılmış kayıtları görmezden gelecek şekilde güncellendi
- Müşteri portalı: Hesap ayarları sayfası (profil adı, şifre değiştirme, Telegram bağlantısını kesme, hesap kaldırma talebi)
- Müşteri portalı: Favorilerim sayfası (takip edilen ürünlerden yıldızlananlar)
- Müşteri portalı: Yardım & SSS sayfası (rehber kartları + kısa SSS)
- Panel: ürünlere karşılaştırma grubu alanı
- Panel: Vercel'e deploy edildi (Root Directory
web, gerekli ortam değişkenleri tanımlandı) - Panel: Talepler sayfasında hesabı zaten açılmış talepler için de silme butonu görünür hale getirildi (önceden sadece hesap açılmamış taleplerde vardı)
- Bot: aynı turda birden fazla ürün çekilirken aralarına rastgele 2-5 saniye bekleme eklendi (bot koruması riskini azaltmak için)
- Müşteri portalı: Haftalık rapor sayfasında ürünlerin hangi mağazadan takip edildiği gösteriliyor
- Müşteri portalı: "Destek al" ve "Premium için yaz" düğmeleri Gmail web compose linkine bağlandı (masaüstü mail uygulaması gerektirmeden çalışır)
- Planım sayfası ve landing page planlar bölümü: Premium özellik listesine eksik olan "Alışveriş listesi" eklendi
- Bot: bir müşteriye bildirim gönderimi başarısız olursa diğer müşterilerin bildirimini engellemiyor (notifier.py try/except)
- Müşteri portalı: hesap ayarlarında Telegram bağlantısını kesme/profil güncelleme için
customerstablosuna sınırlı (sütun bazlı) kendi-satırını-güncelleme RLS izni eklendi - Müşteri portalı: Talep gönder formunda her ürün için hedef fiyat / her değişimde bildir seçimi, onaylanınca otomatik takibe aktarılıyor
- Müşteri portalı ve admin panel: ürün listelerinde (Talep gönder, Takipler) hangi marketten olduğunu gösteren etiket eklendi
- Ürün kıyası sayfası: karşılaştırma grupları kategoriye göre başlıklı bölümlere ayrıldı, üstte kategoriye hızlı kaydırma butonları eklendi
- Panel: Hesap kaldırma talepleri sayfası (arama, genişleyen kartlarda hesap etkisi, talebi reddetme/hesabı silme), sol menüde bekleyen sayısı rozeti
- Ürün kıyası sayfası: kategori bölümleri artık kendi içinde sayfalanıyor (5 ürün grubu/sayfa)
-
leadstablosu ve Talepler sayfası tamamen kaldırıldı (SQL ve kod), yerine kendi kendine kayıt geldi - Landing page: self-servis kayıt (
/signup), navbar'da "Giriş yap" / "Kayıt ol" butonları, eski gömülü talep formu kaldırıldı - Güvenlik: admin panele girişi "müşteri kaydı yoksa admindir" varsayımı yerine
ADMIN_EMAILortam değişkeniyle açıkça belirleme; eşleşmeyen/yetim hesaplar otomatik oturumdan atılıyor - Panel: Premium talepleri sayfası (
customer_premium_requeststablosu), portaldaki Planım sayfasından telefon/not ile talep gönderme, onayla/reddet (geçmiş korunur) - Panel: Yeni kayıtlar sayfası (self-servis kayıt olan müşteriler, arama, aktif/pasif, silme)
- Bot: fiyat ayrıştırıcı sırası değişti (önce DOM'daki görünen fiyat, JSON-LD en sona alındı) ve
price_class/json_ldayrıştırıcıları yanlış fiyat çekme sorununu düzeltecek şekilde güçlendirildi