Premium Üye Durumu Tutarsızlığı

Detaylı Analiz ve Çözüm Önerileri

Kritik Bug
9 Ocak 2026 Analiz Tarihi Claude AI Muzibu Premium System

Basit Anlatım (Herkes İçin)

Problem: Muzibu.com.tr sitesinde premium üye olan kullanıcı, sayfa ilk açıldığında "Premium Üye" olarak görünüyor. Ancak herhangi bir şarkıya, albüme veya çalma listesine tıkladığında sistem "Ücretsiz Üye" olarak değiştiriyor ve "Premium'a Geç" uyarısı gösteriyor.

Neden Oluyor: Sistem, kullanıcının premium durumunu iki farklı yerden kontrol ediyor:

  • Sayfa ilk yüklenirken: Database'den doğru bilgi çekiliyor → "Premium Üye" ✅
  • Şarkıya tıklayınca: API çağrısı yapılıyor ve burada yanlış kontrol var → Kullanıcı bilgileri güncelleniyor → "Ücretsiz Üye" ❌

Sonuç: Premium üye olan kullanıcı, müzik dinleyemiyor. Sistem sürekli "abonelik gerekiyor" diyor.

🎯 Çözüm: API'deki yanlış kontrol mantığı düzeltilmeli. User model'indeki mevcut kontrolü kullanmalı.

Teknik Detaylar (Geliştiriciler İçin)

Etkilenen Dosyalar:
  • Modules/Muzibu/app/Http/Controllers/Api/SongStreamController.php (512-548. satırlar)
  • public/themes/muzibu/js/player/core/player-core.js (3207-3218. satırlar)
  • resources/views/themes/muzibu/components/sidebar-left.blade.php (138-146. satırlar)
  • app/Models/User.php (379-416. satırlar - isPremium metodu)
Ana Sorun:

getSubscriptionData() metodu, User::isPremium() metodunu kullanmak yerine kendi mantığını yazıyor. Bu mantık subscription_expires_at field'ini okurken user model'ini fresh load etmiyor.

Single Source of Truth:

users.subscription_expires_at field'i tüm sistemde tek doğru kaynaktır. User::isPremium() metodu bunu doğru kullanıyor, ama API endpoint'teki getSubscriptionData() metodu çelişen sonuç üretiyor.

Cache Problemi:

Auth guard'dan gelen user object'i request başında yükleniyor ve fresh değil. $user->fresh()->subscription_expires_at veya $user->refresh() kullanılmalı.

Hata Akış Diyagramı

1

Sayfa İlk Yükleniyor (Varsayılan Şarkı)

Blade Template: window.muzibuPlayerConfig.currentUser objesi oluşturuluyor

Backend: User::isPremium() metodu çağrılıyor

✅ subscription_expires_at = '2026-02-15 14:30:00' (gelecek tarih) ✅ isPremium() = true ✅ currentUser.is_premium = true ✅ Sidebar → "Premium Üye" gösteriliyor
Doğru
2

Kullanıcı Rastgele Şarkıya/Playlist'e Tıklıyor

Frontend: playSong(32125) → API çağrısı tetikleniyor

GET /api/muzibu/songs/32125/stream 🔐 Auth: web guard (session-based) 📡 Controller: SongStreamController@stream()
Tetikleme
3

Backend: YANLIŞ Premium Kontrolü!

Dosya: SongStreamController.phpgetSubscriptionData() metodu (512-548. satırlar)

⚠️ HATA: User model'i fresh değil!

❌ $user = auth('web')->user(); // Request başında yüklenmiş (eski data) ❌ $expiresAt = $user->subscription_expires_at; // NULL veya eski tarih dönüyor ❌ $hasPremium = $expiresAt && $expiresAt->isFuture(); // FALSE ❌ return ['is_premium' => false, ...] // YANLIŞ SONUÇ!

Neden? Auth guard'dan gelen user object'i cache'lenmiş/eski. Fresh load edilmiyor!

HATA!
4

Frontend: Kullanıcı Bilgilerini Güncelliyor

Dosya: player-core.js (3207-3218. satırlar)

// API'den gelen yanlış data const streamData = { is_premium: false, // ❌ YANLIŞ! is_trial: false }; // Frontend güncelleme if (this.currentUser) { this.currentUser.is_premium = streamData.is_premium; // false yazılıyor ❌ this.currentUser.is_trial = streamData.is_trial; // false yazılıyor }
Yanlış Data
5

Sidebar: "Ücretsiz Üye" Gösteriliyor!

Dosya: sidebar-left.blade.php (138-146. satırlar)

get memberType() { if (!this.currentUser?.is_premium) { // ✅ true (çünkü is_premium=false) return 'Ücretsiz Üye'; // ❌ BURAYA GİRİYOR! } if (this.currentUser?.is_trial) { return 'Deneme Üyesi'; } return 'Premium Üye'; }

❌ SONUÇ: Kullanıcı "Ücretsiz Üye" olarak gösteriliyor!

❌ "Premium'a Geç" butonu çıkıyor!

Hatalı Görünüm

Kod Karşılaştırması: Doğru vs Yanlış

Doğru: User::isPremium() (User.php)

// app/Models/User.php (379-416. satırlar) public function isPremium(): bool { // Tenant yoksa false if (!$this->isMuzibuTenant()) { return false; } // ✅ ULTRA FAST: Önce tenant users tablosundan kontrol if ($this->subscription_expires_at && $this->subscription_expires_at->isFuture()) { return true; } // ✅ Tenant DB'den fresh kontrol $tenantExpiry = \DB::table('users') ->where('id', $this->id) ->value('subscription_expires_at'); if ($tenantExpiry && \Carbon\Carbon::parse($tenantExpiry)->isFuture()) { return true; } // ✅ Fallback: Subscription tablosu return \Cache::remember($cacheKey, 300, function () { return $this->subscriptions() ->where('status', 'active') ->where('current_period_end', '>', now()) ->exists(); }); }

Yanlış: getSubscriptionData() (SongStreamController.php)

// SongStreamController.php (512-548. satırlar) protected function getSubscriptionData($user): array { if (!$user) { return ['is_premium' => false, ...]; } // ❌ PROBLEM: User fresh değil! // Auth guard'dan gelen user cache'lenmiş $expiresAt = $user->subscription_expires_at; // ❌ NULL veya eski tarih dönüyor! $hasPremium = $expiresAt && $expiresAt->isFuture(); if (!$hasPremium) { // ❌ YANLIŞ SONUÇ! return [ 'is_premium' => false, 'trial_ends_at' => null, 'subscription_ends_at' => null, ]; } // ... }

Neden Bu Hata Oluştu?

1️⃣ Auth Guard Cache

Laravel'in auth guard'ı (web/sanctum), request başında kullanıcıyı yüklüyor ve cache'liyor. Sonraki auth()->user() çağrıları aynı object'i döndürüyor. Eğer başka bir süreç (observer, command, job) subscription_expires_at field'ini güncellediyse, mevcut request'teki user object'i bu değişikliği görmüyor.

2️⃣ DRY Prensibi İhlali

Sistem zaten User::isPremium() metoduna sahip ve bu metod doğru çalışıyor. Ama SongStreamController kendi mantığını yazmış (duplicate logic). Bu iki mantık çelişen sonuçlar üretiyor.

3️⃣ Fresh Load Eksikliği

getSubscriptionData() metodu, $user->subscription_expires_at değerini direkt okuyor. Ama $user->fresh()->subscription_expires_at veya DB::table('users')->where('id', $user->id)->value('subscription_expires_at') gibi fresh data çekmiyor.

Çözüm Önerileri

ÖNERİLEN ÇÖZÜM

Çözüm 1: User::isPremium() Kullan

getSubscriptionData() metodunu sil ve doğrudan User::isPremium() metodunu kullan. Bu metod zaten fresh data çekiyor ve doğru çalışıyor.

// SongStreamController.php - getSubscriptionData() metodunu DEĞİŞTİR protected function getSubscriptionData($user): array { if (!$user) { return [ 'is_premium' => false, 'is_trial' => false, 'trial_ends_at' => null, 'subscription_ends_at' => null, ]; } // ✅ ÇÖZÜM: User model metodunu kullan $isPremium = $user->isPremium(); $isTrial = $user->isTrialActive(); if (!$isPremium) { return [ 'is_premium' => false, 'is_trial' => false, 'trial_ends_at' => null, 'subscription_ends_at' => null, ]; } // Premium ise tarihleri getir $expiresAt = $user->fresh()->subscription_expires_at; // Trial subscription kontrolü $trialSubscription = $user->subscriptions() ->whereIn('status', ['active', 'trial']) ->where('has_trial', true) ->whereNotNull('trial_ends_at') ->where('trial_ends_at', '>', now()) ->first(); return [ 'is_premium' => true, 'is_trial' => $isTrial, 'trial_ends_at' => $trialSubscription ? $trialSubscription->trial_ends_at->toIso8601String() : null, 'subscription_ends_at' => $expiresAt ? $expiresAt->toIso8601String() : null, ]; }

Avantajlar:

  • Single source of truth (User model)
  • Duplicate code kaldırılıyor
  • User::isPremium() zaten fresh check yapıyor
  • Daha az kod, daha kolay bakım
ALTERNATİF

Çözüm 2: Fresh Load Ekle

Mevcut getSubscriptionData() metodunu koruyarak sadece fresh load ekle.

// SongStreamController.php protected function getSubscriptionData($user): array { if (!$user) { return ['is_premium' => false, ...]; } // ✅ FRESH LOAD: DB'den güncel veriyi çek $freshUser = $user->fresh(['subscriptions']); $expiresAt = $freshUser->subscription_expires_at; $hasPremium = $expiresAt && $expiresAt->isFuture(); // ... geri kalan kod aynı }

Avantajlar:

  • Minimum kod değişikliği
  • Mevcut mantık korunuyor

Dezavantajlar:

  • Duplicate logic devam ediyor
  • Extra DB query (fresh load)
HIZ OPTİMİZASYONU

Çözüm 3: Cache Invalidation

Observer kullanarak subscription değişikliğinde auth cache'i temizle.

// app/Observers/SubscriptionObserver.php public function updated(Subscription $subscription) { // ✅ Auth cache temizle $user = $subscription->user; if ($user) { // Auth guard cache'i temizle auth()->guard('web')->setUser($user->fresh()); // Premium cache'i temizle \Cache::forget('user_' . $user->id . '_is_premium_tenant_' . tenant()->id); } }

Not: Bu çözüm tek başına yeterli değil. Çözüm 1 ile birlikte kullanılmalı.

Test Senaryosu

Test Adımları

  1. Premium üye hesabıyla giriş yap
  2. Ana sayfa yüklendiğinde sidebar'da "Premium Üye" yazdığını kontrol et
  3. Herhangi bir şarkıya/albüme/playlist'e tıkla
  4. Sidebar'da hala "Premium Üye" yazdığını kontrol et (değişmemeli!) ✅
  5. "Premium'a Geç" butonu ÇIKMAMALI ✅
  6. Şarkı sorunsuz çalmalı ✅

Debug Komutları

// Tinker ile premium kontrolü php artisan tinker $user = User::find(YOUR_USER_ID); echo "subscription_expires_at: " . $user->subscription_expires_at; echo "isPremium(): " . ($user->isPremium() ? 'true' : 'false'); echo "isPremiumOrTrial(): " . ($user->isPremiumOrTrial() ? 'true' : 'false'); // DB'den direkt kontrol $expiry = DB::table('users')->where('id', YOUR_USER_ID)->value('subscription_expires_at'); echo "DB expiry: " . $expiry;

Önleyici Tedbirler (Gelecek İçin)

Single Source of Truth

Bir veri için tek kaynak belirle. Premium kontrolü için User::isPremium() kullan, başka yerde aynı mantığı yazma.

Fresh Load Awareness

Auth guard'dan gelen user object'i cache'lenmiş olabilir. Kritik işlemlerde $user->fresh() veya direkt DB query kullan.

Unit Test

API endpoint'leri için unit test yaz. Premium/Trial kullanıcı senaryolarını test et.

Code Review

Duplicate logic oluşturmadan önce mevcut metodları kontrol et. Code review sürecinde DRY prensibi ihlallerini yakala.

Claude AI tarafından 9 Ocak 2026 tarihinde hazırlanmıştır.

Muzibu Premium System - Bug Analysis & Solution Report