Teknik anlatı

Bultine’ın mutfağı: bir RSS kaynağı akışa nasıl düşüyor?

Bu sayfa docs/teknik-stack-blog.md ve mevcut kod tabanındaki gerçeklere dayanır. Üretimde koşmayan parçalar açıkça “plan” diye işaretlenir; rakip ürün iddiası üretilmez.

Diyelim ki sabah Bultine’ı açtın. Akışta bir kaynakdan gelen haber duruyor: kısa bir özet, altında “AI ile oluşturuldu — orijinali oku” satırı.

Sen o feed’i eklemiş olabilirsin ya da sistemde zaten vardı. Bu yazı, arka planda ne olduğunu anlatıyor.

Ürün tek cümleyle

Her dildeki RSS ve Atom kaynaklarını tek akışta toplarsın. AI özet, kural motoru ve günlük brifing var; saklanan şey başlık, kısa alıntı ve kaynak linkidir.

Bugün nerede duruyoruz?

Kod tabanında canlı olan katmanlar:

  • Arayüz + API: SvelteKit (Vercel)
  • Veritabanı + giriş: Supabase (Postgres, Auth)
  • Çekme: rss-parser; zamanlama Vercel Cron (`/api/cron/ingest`)
  • AI: Claude Haiku ile 1–2 cümlelik özet; kota DB’de atomik
  • Kurallar + brifing: koşul → aksiyon motoru; günlük derleme
  • Ödeme: Stripe (test mode) Free/Pro plan ve limit zorlaması

Dokümandaki kuyruk (BullMQ), WebSub, Firecrawl, X izleme, newsletter giriş kapısı ve Meilisearch ölçek yolunda — henüz MVP’de koşmuyuyor; hacim gelince F2 ve sonrası.

Kaynak eklemek: bir feed, birçok abone

Bir RSS URL’si yapıştırdığında sistem feed’i doğrular, sources tablosuna bakar: bu feed zaten var mı? Varsa yeni satır açılmaz — yalnızca seninle kaynak arasına bir abonelik eklenir. Yoksa kaynak oluşturulur ve ilk çekim yapılır.

Aynı yayıncı yüzlerce kişi tarafından eklenirse feed bir kez çekilir, makaleler bir kez saklanır. Okundu / yıldız durumu ayrı, kullanıcıya özel tabloda yaşar. Alternatif — herkese kendi makale kopyası — hem depolamayı hem AI faturasını şişirirdi.

Ne zaman çekilir?

MVP’de yoklama Vercel Cron ile periyodik çalışır (Hobby planda günde bir). İstek-yanıt döngüsüne bağlanmaz: kimse bakmıyorken iş yapılır. Ölçek gelince kuyruk + next_poll_at deseni (dokümandaki çekirdek IP) F2’ye aday.

Yeni makale gelince

Her yeni öğe için pratikte:

  1. Dedup — kaynak + dış kimlik / link hash ile daha önce görülmüş mü?
  2. Yalnızca başlık, kısa alıntı ve kaynak linki yazılır.
  3. Özet henüz yoksa, kota uygunsa Claude ile üretilip makaleye bağlanır.
  4. Kullanıcı kuralları değerlendirilir; eşleşmeler yalnızca o kullanıcıya yazılır.

Özet: makale başına bir kez

Özet makaleye aittir, kullanıcıya değil. Aynı haber bin aboneye gidiyorsa model bir kez çağrılır; önbellekteki özeti görmek kotadan düşmez. Üretim tetiklemek Free/Pro günlük kota ile sınırlıdır. Her özet “AI ile oluşturuldu — orijinali oku” etiketi taşır (EU AI Act).

Neden tam metni saklamıyoruz?

Teknik değil, hukuki bir sınır — ve mimariyi doğrudan şekillendiriyor. Asla-listesi: paywall arkası kazınmaz; telifli eser depolanmaz ve yeniden dağıtılmaz. Güvenli çizgi başlık + 1–2 cümle özet/alıntı + kaynak linki.

Pratikte ham metin özet için işlenebilir, sonra atılır. Veritabanında kalan şey özet, metadata ve orijinal linktir.

Kural motoru

Her yeni makale bir olaydır. Örnek: kaynak veya anahtar kelime eşleşirse etiketle, öne çıkar veya sustur. Kurallar makale geliş anında uygulanır; sonuç paylaşımlı makale satırına değil, kullanıcıya özel eşleşme tablosuna yazılır — böylece bir abonenin etiketi diğerine sızmaz.

Ödeme ve limitler

Stripe abonelikleri planı profiles üzerinde günceller. Kaynak sayısı ve günlük AI kotası DB’de atomik zorlanır: Free tavanı aşan yeni kaynak reddedilir; Pro’da eklenen kaynaklar Free’ye düşünce silinmez, yalnızca yeni ekleme kapanır. Limit, iş kuyruğa / üretime girmeden önce kontrol edilir.

Neden bu kadar parça?

Ürün üç şeyi aynı anda ister:

  1. Süreklilik — kimse bakmıyorken feed’leri kibarca yoklamak
  2. Ekonomi — aynı haberi bin kişiye bir kez özetlemek
  3. Sınır — sakladığımız şey haberin kendisi değil, ona giden yol

SvelteKit tek başına feed yoklamaz; Supabase tek başına özet üretmez; Claude tek başına “hangi feed ne zaman” bilmez. Birlikte, akışındaki haberi kısa bir özete indirirler.