🚨 Muzibu Real-time Sistem Planı

Cihaz Limiti & Subscription Expire - Anında Tetikleme Sistemi
📅 6 Aralık 2025 🎯 Tenant: muzibu.com ⚡ Real-time Event Sistemi

🔴 Mevcut Sorunlar

📱

SORUN 1: Cihaz Limitinden Çıkarılan Kullanıcı

Senaryo:

  1. Kullanıcı PC'de müzik dinliyor
  2. Telefon'dan giriş yapıyor (device limit = 1)
  3. Backend PC'nin session'ını siliyor
  4. AMA PC'deki browser bunu bilmiyor!
  5. PC'de hala müzik dinlemeye devam ediyor!
Mevcut Kod:
// DeviceService.php - Satır 168
DB::table('sessions')->where('id', $oldest->id)->delete();
// ❌ Sadece DB'den siliniyor, browser'a bildirim YOK!

⏱️ Max Gecikme: Sonsuz! (Manuel refresh yapana kadar)

👑

SORUN 2: Subscription Bitince

Senaryo:

  1. Kullanıcı premium üye (trial_ends_at: 23:59:59)
  2. Saat 23:50'de müzik dinliyor (sınırsız)
  3. Saat 00:00 → Trial bitti!
  4. AMA cache var (5dk TTL)
  5. 00:05'e kadar hala premium dinliyor!
Mevcut Mantık:
// User.php - isPremium()
Cache::remember($cacheKey, 300, function() {
    return $this->subscriptions()
        ->where('current_period_end', '>', now())
        ->first();
});
// ⚠️ Cache 5dk geçerli, expire anında tetiklenmiyor!

⏱️ Max Gecikme: 5 dakika

💡 İki Çözüm Yaklaşımı

Hızlı Çözüm (Polling)

⏱️ Süre:
2-3 Saat
💰 Maliyet:
Ücretsiz (Ekstra servis yok)
📊 Performans:
Orta (Her 30s API çağrısı)
⚠️ Gecikme:
Max 30 saniye
🚀

Tam Çözüm (WebSocket)

⏱️ Süre:
1-2 Gün
💰 Maliyet:
Ücretli (Pusher/Laravel WebSockets)
📊 Performans:
Mükemmel (Real-time, API yok)
⚠️ Gecikme:
Anında (1-2 saniye)

⚡ HIZLI ÇÖZÜM: Polling Sistemi

1

Frontend: Periodic Session Check

Dosya: player-core.js
// Her 30 saniyede session kontrolü
setInterval(async () => {
    const response = await fetch('/api/auth/check-session');
    const data = await response.json();

    if (!data.valid) {
        // Session geçersiz (başka cihazdan çıkarıldı)
        this.handleForceLogout();
    }

    if (data.is_premium !== this.isPremium) {
        // Premium status değişti
        this.handlePremiumChange(data.is_premium);
    }
}, 30000); // 30 saniye
💡
Mantık
Her 30s → Session + Premium durumu kontrol et
2

Backend: Session Check Endpoint

Route: /api/auth/check-session
public function checkSession(Request $request)
{
    // Session geçerli mi kontrol et
    $sessionExists = DB::table('sessions')
        ->where('id', session()->getId())
        ->exists();

    if (!$sessionExists) {
        return response()->json([
            'valid' => false,
            'reason' => 'device_limit_exceeded'
        ]);
    }

    // Premium status
    $user = Auth::user();
    $isPremium = $user ? $user->isPremiumOrTrial() : false;

    return response()->json([
        'valid' => true,
        'is_premium' => $isPremium
    ]);
}
🔒
Kontrol Noktaları
✅ Session DB'de var mı?
✅ Premium aktif mi?
3

Frontend: Force Logout Handler

Dosya: player-core.js
handleForceLogout() {
    // Müziği durdur
    this.stopCurrentPlayback();

    // Modal göster
    this.showModal({
        title: 'Başka Cihazdan Giriş Yapıldı',
        message: 'Hesabınıza başka bir cihazdan giriş yapıldı.',
        icon: '🔐',
        actions: [
            { label: 'Tamam', action: 'close' }
        ]
    });

    // Logout
    this.isLoggedIn = false;
    this.currentUser = null;

    // Guest preview moduna geç
    setTimeout(() => {
        window.location.reload();
    }, 2000);
}
4

Frontend: Premium Change Handler

Dosya: player-core.js
handlePremiumChange(newStatus) {
    const oldStatus = this.isPremium;
    this.isPremium = newStatus;

    if (oldStatus && !newStatus) {
        // Premium → Normal: Sınırsız → 30s preview
        this.showToast('Üyeliğiniz sona erdi. 30s preview moduna geçildi.', 'warning');
        
        // Mevcut şarkıyı preview mode ile yeniden yükle
        if (this.currentSong) {
            this.reloadCurrentSongWithPreview();
        }
    }

    if (!oldStatus && newStatus) {
        // Normal → Premium: 30s → Sınırsız
        this.showToast('Premium üyeliğiniz aktif! Sınırsız dinleyebilirsiniz.', 'success');
        
        // Preview timer'ları temizle
        this.clearPreviewTimers();
    }
}

⏰ Laravel Scheduler: Subscription Expire

Gece Yarısı Otomatik Kontrol

Dosya: app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    // Her gece 00:01'de expired subscription'ları kontrol et
    $schedule->call(function () {
        $expiredSubs = Subscription::where('status', 'active')
            ->where('current_period_end', '<=', now())
            ->get();

        foreach ($expiredSubs as $sub) {
            // Status güncelle
            $sub->update(['status' => 'expired']);

            // Cache temizle (Observer otomatik tetiklenir)
            // SubscriptionObserver::updated() çalışır

            Log::info('Subscription expired', [
                'user_id' => $sub->user_id,
                'subscription_id' => $sub->subscription_id
            ]);
        }
    })->dailyAt('00:01');
}
⏱️
Çalışma Zamanı
Her gece 00:01 → Expired subscription'ları expire et → Cache flush (Observer)

🚀 TAM ÇÖZÜM: Laravel WebSockets / Pusher

Real-time Event Broadcasting

📦 Paket
composer require beyondcode/laravel-websockets
# veya
composer require pusher/pusher-php-server
💰 Maliyet
Laravel WebSockets: Ücretsiz
Pusher: $49/ay
Event 1: Device Limit Exceeded
// Backend: DeviceService.php
event(new DeviceLimitExceeded($user->id, $oldSessionId));

// Frontend: Echo (Laravel Echo)
Echo.private(`user.${userId}`)
    .listen('DeviceLimitExceeded', (e) => {
        if (e.sessionId === currentSessionId) {
            muzibu.handleForceLogout();
        }
    });
Event 2: Subscription Expired
// Backend: SubscriptionObserver.php
event(new SubscriptionExpired($subscription->user_id));

// Frontend: Echo
Echo.private(`user.${userId}`)
    .listen('SubscriptionExpired', (e) => {
        muzibu.handlePremiumChange(false);
    });
Avantajlar
✅ Anında tetikleme (1-2s gecikme)
✅ Sunucu yükü yok (API polling yok)
✅ Kullanıcı deneyimi mükemmel

📊 Karşılaştırma Tablosu

Özellik Polling (Hızlı) WebSocket (Tam)
Geliştirme Süresi 2-3 Saat 1-2 Gün
Maliyet Ücretsiz $0-49/ay
Gecikme (Device Limit) Max 30 saniye 1-2 saniye
Gecikme (Subscription) Max 5 dakika Anında
Sunucu Yükü Orta (Her 30s API) Düşük (Event-driven)
Ölçeklenebilirlik Orta Yüksek
Kullanıcı Deneyimi İyi Mükemmel

💡 ÖNERİ

2 Aşamalı Yaklaşım

1️⃣
Aşama 1: Hızlı Çözüm (Şimdi)

✅ Polling sistemi kur (2-3 saat)

✅ Laravel Scheduler ekle (Subscription expire)

✅ Test et, canlıya al

Sonuç: Max 30s gecikmeli ama çalışır bir sistem

2️⃣
Aşama 2: Tam Çözüm (Sonra)

🚀 Laravel WebSockets entegre et (1-2 gün)

🚀 Real-time event'lere geç

🚀 Polling sistemini kaldır

Sonuç: Anında tetikleme, mükemmel UX

👍
Neden Bu Yaklaşım?
✅ Hemen çalışır bir çözüm (bugün canlı!)
✅ Gelecekte upgrade ederken sıfır kesinti
✅ Risk minimum, maliyet optimize