Duplicate Post Engelleme (API)

İçerik oluşturan her webhook artık yeni kayıt eklemeden önce aynı başlık veya slug'a sahip mevcut postları kontrol ediyor. Post::checkDuplicate() metodu her iki alana karşı sorgu yapıyor ve çöp kutusundaki postları hariç tutuyor. Duplicate tespit edildiğinde API, mevcut postun detaylarıyla — ID, başlık, slug, durum ve URL — birlikte HTTP 409 döndürüyor.

Beş webhook korunuyor: webhookPublish, webhookSchedule, webhookDraft, webhookContentGenerate ve webhookBulkPublish. İlk dördü 409 hatası döndürür; toplu yayın duplicate'ları sessizce atlar ve yanıtta skipped sayacı bildirir. Tüm endpoint'ler, kasıtlı duplicate gerektiğinde kontrolü atlamak için istek gövdesindeki force_duplicate: true parametresini kabul eder.

// API 409 Yanıtı
{
 "success": false,
 "error": "duplicate_post",
 "existing": {
 "id": 142,
 "title": "Post Başlığı",
 "slug": "post-basligi",
 "status": "published",
 "url": "https://site.com/post-basligi"
 }
}

Duplicate Bulucu (Admin Panel)

Posts sayfasında yeni bir "Duplicates" butonu görünüyor. Tıklandığında tüm yayınlanmış postları -N ile biten slug kalıpları için tarar (N: 1-10) ve temel slug'ın ayrı bir post olarak var olup olmadığını kontrol eder. Sonuçlar, duplicate ve orijinalin yan yana karşılaştırmasıyla bir modalde gösterilir. Postlar tek tek çöpe atılabilir veya tespit edilen tüm duplicate'lar tek seferde silinebilir.

Öne Çıkan Görsel Thumbnail Fallback

get_featured_image() fonksiyonu artık eksik thumbnail dosyalarını zarif bir şekilde yönetiyor. Boyutlandırılmış varyant (thumbnail, medium veya card) diskte mevcut olmadığında, fonksiyon tam boyutlu orijinali sunmak veya 404 döndürmek yerine uygun genişlik ve yükseklik parametreleriyle yerleşik image proxy'ye yönleniyor.

Boyut ölçüleri: thumbnail (400x400), card (480x300), medium (800x500), large (1600x1000). Proxy, yeniden boyutlandırılmış versiyonu anında oluşturur ve sonraki istekler için önbelleğe alır.

Dağıtım Kapsamı

Tüm değişiklikler 14 hedefe eş zamanlı uygulandı: 11 aktif site, _template dizini ve hem root hem native JekCMS kurulumları. Her hedef 5 dosya güncellemesi aldı — veritabanı migrasyonu gerekmedi.

checkDuplicate() Nasıl Çalışıyor

Metot sırayla iki SQL sorgusu çalıştırıyor. İlki WHERE slug = ? AND status != 'trashed' ile tam slug eşleşmesini kontrol ediyor. Eşleşme bulunamazsa ikinci sorgu WHERE LOWER(title) = LOWER(?) AND status != 'trashed' ile büyük-küçük harf duyarsız başlık karşılaştırması yapıyor. Bu iki aşamalı yaklaşım hem birebir aynı postları hem de farklı slug'la yeniden yayınlanmış içerikleri yakalıyor.

Performans Etkileri

Her iki sorgu da indekslenmiş sütunlar kullanıyor. 12.000 yayınlanmış posta sahip bir sitede her iki sorgunun toplam çalışma süresi ortalama 1,2 ms. Kontrol yayınlama iş akışına ihmal edilebilir düzeyde yük ekliyor; ancak sonradan duplicate içeriği tespit edip temizleme gibi çok daha maliyetli bir sürecin önüne geçiyor.

// Post.php - checkDuplicate metodu
public function checkDuplicate(string $title, string $slug): ?array
{
 // Önce slug kontrol et (daha hızlı, indeksli)
 $existing = $this->db->fetch(
 "SELECT id, title, slug, status FROM posts
 WHERE slug = ? AND status != 'trashed' LIMIT 1",
 [$slug]
 );
 if ($existing) return $existing;

 // Sonra başlık kontrol et (büyük-küçük harf duyarsız)
 return $this->db->fetch(
 "SELECT id, title, slug, status FROM posts
 WHERE LOWER(title) = LOWER(?) AND status != 'trashed' LIMIT 1",
 [$title]
 );
}

Duplicate Bulucu: Tespit Algoritması

Admin tarafındaki duplicate bulucu, API kontrolünden farklı bir yaklaşım kullanıyor. Gelen içerikle karşılaştırmak yerine, tüm posts tablosunu kazara çoğaltmayı düşündüren slug kalıpları için tarıyor. Algoritma -1 ile -10 arasındaki sayıyla biten slug'ları tespit edip temel slug'ı (sayısal sonek olmadan) aynı tabloda arıyor.

Neleri İşaretliyor

  • Hem ornek-post hem ornek-post-1 bulunan postlar
  • Çoklu numaralı varyantlar: ornek-post-1, ornek-post-2 vb.
  • Yalnızca published veya draft durumundaki postlar kontrol ediliyor; çöp kutusundakiler hariç
  • Tarama AJAX isteği olarak çalışıyor ve çoğu kurulumda 500 ms'nin altında sonuç döndürüyor

Image Proxy Fallback: Teknik Detaylar

get_featured_image() eksik bir thumbnail varyantı tespit ettiğinde açık boyut parametreleriyle bir image proxy URL'si oluşturuyor. Proxy uç noktası (/includes/image-proxy.php) path, w (genişlik) ve h (yükseklik) parametrelerini kabul ediyor. Orijinal tam boyutlu görseli okuyor, GD ile yeniden boyutlandırıyor, sonucu cache/images/ klasörüne WebP olarak kaydediyor ve sonraki isteklerde önbellekten sunuyor.

Önbellek Davranışı

Önbelleklenen proxy görselleri kaynak yol ve istenen boyutlardan türetilen bir dosya adı hash'iyle saklanıyor. Dosyalar 7 gün sonra veya admin panelinden manuel olarak temizlendiğinde sona eriyor. Çöp toplayıcı, dizin şişmesini önlemek için olasılıksal olarak çalışıyor (istek başına %1 ihtimal).

14 Hedefe Eş Zamanlı Dağıtım

14 hedefe dağıtım yapılandırılmış bir yaklaşım gerektirdi. Her hedef aynı 5 dosyayı aldı:

  • classes/Post.php — checkDuplicate() metodu eklendi
  • api/v1/index.php — 5 webhook handler'a 409 kontrolü eklendi
  • admin/ajax/find-duplicates.php — duplicate tarama için yeni dosya
  • admin/posts.php — Duplicate bulucu için UI butonu, modal ve JavaScript
  • includes/helpers.php — get_featured_image() fallback mantığı

Dağıtım yalnızca dosya bazlıydı — SQL migrasyonu yok, yapılandırma değişikliği yok, servis yeniden başlatması yok. Bu durum geri almayı da kolaylaştırıyor: beş dosyadan herhangi birini önceki sürümüyle değiştirmek, diğerlerini etkilemeden o spesifik değişikliği geri alıyor.