🎡 MUZİBU MÜZİK PLATFORMU

SUBSCRIPTION & DEVICE LIMIT SİSTEMİ - MASTER DÜZENLEME PLANI
1. ÜYELİK HİYERARŞİSİ VE DİNLEME HAKLARI
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ KULLANICI TÜRLERΔ° β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ GUEST (Üye değil) β†’ 30 saniye preview β”‚ β”‚ ÜCRETSΔ°Z ÜYE β†’ 30 saniye preview β”‚ β”‚ PREMIUM / TRIAL ÜYE β†’ SΔ±nΔ±rsΔ±z dinleme β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
❌ MEVCUT HATA:
Guest ve ücretsiz üye ayrımı kodda tam yapılmamış!
Dosya: SongStreamController.php
SatΔ±r: 52-149
Sorun: Guest ($user == null) ve non-premium ($user->isPremiumOrTrial() == false) için AYNI kod bloğu tekrarlanıyor.
βœ… ÇÖZÜM:
Helper method oluştur: getPreviewStreamResponse($song, $userType)
protected function getPreviewStreamResponse(Song $song, string $userType): JsonResponse
{
    // HLS conversion başlat
    if ($song->needsHlsConversion()) {
        ConvertToHLSJob::dispatch($song);
    }

    // Stream URL hazΔ±rla
    if (!empty($song->hls_path)) {
        $streamUrl = route('api.muzibu.songs.dynamic-playlist', ['id' => $song->song_id]);
        $streamType = 'hls';
        $fallbackUrl = $this->signedUrlService->generateStreamUrl($song->song_id, 30, true);
    } else {
        $streamUrl = $this->signedUrlService->generateStreamUrl($song->song_id, 30);
        $streamType = 'mp3';
        $fallbackUrl = null;
    }

    $message = $userType === 'guest'
        ? 'KayΔ±t olun, tam dinleyin'
        : 'Premium\'a geΓ§in, sΔ±nΔ±rsΔ±z dinleyin';

    return response()->json([
        'status' => 'preview',
        'message' => $message,
        'stream_url' => $streamUrl,
        'stream_type' => $streamType,
        'fallback_url' => $fallbackUrl,
        'preview_duration' => 30,
        'is_premium' => false,
    ]);
}
2. CΔ°HAZ LΔ°MΔ°TΔ° FALLBACK SΔ°STEMΔ° (3 Seviyeli)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ CΔ°HAZ LΔ°MΔ°TΔ° BELΔ°RLEME SIRASI β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ 1. User->device_limit β†’ VIP/Test/Ban ΓΆzel durumlar β”‚ β”‚ 2. SubscriptionPlan->device_limit β†’ Premium plan limiti β”‚ β”‚ 3. Setting('auth_device_limit') β†’ Tenant global ayar β”‚ β”‚ 4. Fallback: 1 cihaz β†’ HiΓ§biri yoksa β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
βœ… DOĞRU UYGULAMA (MEVCUT):
Dosya: DeviceService.php:220-252
Method: getDeviceLimit(User $user)
public function getDeviceLimit(User $user): int
{
    // 1. User override (VIP/Test/Ban)
    if ($user->device_limit !== null && $user->device_limit > 0) {
        return $user->device_limit;
    }

    // 2. Subscription Plan device_limit
    $subscription = $user->subscriptions()
        ->whereIn('status', ['active', 'trial'])
        ->where(function($q) {
            $q->whereNull('current_period_end')
              ->orWhere('current_period_end', '>', now());
        })
        ->with('plan')
        ->first();

    if ($subscription && $subscription->plan && $subscription->plan->device_limit) {
        return (int) $subscription->plan->device_limit;
    }

    // 3. Tenant setting fallback
    if (function_exists('setting') && setting('auth_device_limit')) {
        return (int) setting('auth_device_limit');
    }

    // 4. Ultimate fallback
    return 1;
}
⚠️ DİKKAT:
BROWSER + SΔ°STEM ANIMI: Mevcut sistemde device detection Jenssegers\Agent ile yapΔ±lΔ±yor.
KayΔ±t Edilen Bilgiler:
  • device_type: desktop / mobile / tablet
  • device_name: "Windows 11 - Chrome"
  • browser: Chrome, Firefox, Safari
  • platform: Windows, macOS, Android, iOS
  • ip_address: KullanΔ±cΔ± IP
3. LOGIN AKIŞI (Cihaz Limit Kontrolü)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 1. KullanΔ±cΔ± email/password girer β”‚ β”‚ 2. Credentials doğrulanΔ±r β”‚ β”‚ 3. DeviceService.checkDeviceLimitBeforeLogin() Γ§ağrΔ±lΔ±r β”‚ β”‚ β”œβ”€β”€ Limit AŞILMADI β†’ Normal login devam β”‚ β”‚ └── Limit AŞILDI β†’ 403 + device_limit_exceeded response β”‚ β”‚ 4. Frontend Device Selection Modal aΓ§ar β”‚ β”‚ β”œβ”€β”€ Aktif cihazlar listelenir (IP, Browser, Platform, Son) β”‚ β”‚ └── KullanΔ±cΔ± Γ§Δ±karmak istediği cihazΔ± seΓ§er β”‚ β”‚ 5. terminateDevice() API Γ§ağrΔ±lΔ±r (credential-based) β”‚ β”‚ 6. SeΓ§ilen cihazΔ±n session'Δ± silinir β”‚ β”‚ 7. KullanΔ±cΔ± tekrar login yapar (artΔ±k limit altΔ±nda) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
❌ MEVCUT HATA 1: CSRF Token Mismatch
Sorun: SPA (Single Page Application) yapΔ±sΔ±nda CSRF token yenilenmesi eksik!
Senaryo:
1. KullanΔ±cΔ± sayfayΔ± aΓ§ar (CSRF token alΔ±r)
2. Uzun sΓΌre bekler (session timeout)
3. Login yapmaya çalışır
4. ❌ CSRF token mismatch hatası!
βœ… ÇÖZÜM: Otomatik CSRF Token Yenileme
Dosya: resources/views/themes/muzibu/layouts/app.blade.php
// CSRF token yenileme interceptor ekle
axios.interceptors.response.use(
    response => response,
    error => {
        // CSRF token mismatch hatasΔ±
        if (error.response?.status === 419) {
            console.warn('CSRF token expired, refreshing...');

            // Yeni token al
            return axios.get('/sanctum/csrf-cookie').then(() => {
                // Token yenilendi, isteği tekrar gânder
                return axios(error.config);
            });
        }
        return Promise.reject(error);
    }
);
❌ MEVCUT HATA 2: Device Selection Modal Eksik
Sorun: checkDeviceLimitBeforeLogin() backend'de var ama frontend modal entegrasyonu yok!
Dosya: DeviceService.php:282-321
Return: can_login, devices[], limit dΓΆndΓΌrΓΌyor ama frontend kullanmΔ±yor!
βœ… ÇÖZÜM: Frontend Login Flow GΓΌncelle
Dosya: public/themes/muzibu/js/features/auth.js
async login() {
    try {
        const response = await axios.post('/api/login', this.loginForm);

        // Login başarılı
        window.location.reload();

    } catch (error) {
        // CSRF token yenileme zaten interceptor'da

        // Device limit kontrolΓΌ
        if (error.response?.status === 403 &&
            error.response?.data?.error === 'device_limit_exceeded') {

            // Device selection modal aΓ§
            this.activeDevices = error.response.data.devices;
            this.deviceLimit = error.response.data.limit;
            this.showDeviceSelectionModal = true;
            return;
        }

        // Diğer hatalar
        this.authError = error.response?.data?.message || 'Giriş başarısız';
    }
}
4. SESSION POLLING SΔ°STEMΔ° (30 Saniye)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ SESSION POLLING AKIŞI (Her 30 Saniye) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ 1. KullanΔ±cΔ± giriş yapar β†’ Redis session başlar β”‚ β”‚ 2. Frontend setInterval(30000) başlatΔ±r β”‚ β”‚ 3. Her 30 saniyede /api/device/ping Γ§ağrΔ±lΔ±r β”‚ β”‚ 4. DeviceService::updateSessionActivity() Γ§alışır β”‚ β”‚ β”œβ”€β”€ user_active_sessions.last_activity gΓΌncellenir β”‚ β”‚ └── Redis session TTL yenilenir (120 dakika) β”‚ β”‚ 5. Response: { device_limit_status, active_devices } β”‚ β”‚ 6. Eğer device_limit_exceeded β†’ Modal gΓΆster β”‚ β”‚ 7. KullanΔ±cΔ± logout olursa β†’ Polling durdur β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
βœ… DOĞRU UYGULAMA (MEVCUT):
Dosya: DeviceService.php:110-151
Method: updateSessionActivity()
public function updateSessionActivity(): array
{
    $user = auth('web')->user() ?? auth('sanctum')->user();

    if (!$user) {
        return ['status' => 'unauthenticated'];
    }

    $sessionId = session()->getId();

    // MySQL: last_activity gΓΌncelle
    UserActiveSession::where('session_id', $sessionId)
        ->where('user_id', $user->id)
        ->update(['last_activity' => now()]);

    // Redis: Session TTL yenile (120 dakika)
    Redis::expire("laravel_session:{$sessionId}", 7200);

    // Mevcut cihaz limiti kontrol
    $deviceLimit = $this->getDeviceLimit($user);
    $activeDevices = $this->getActiveDevicesCount($user);

    return [
        'status' => 'active',
        'device_limit' => $deviceLimit,
        'active_devices' => $activeDevices,
        'device_limit_exceeded' => $activeDevices > $deviceLimit,
    ];
}
⚠️ DİKKAT: HARDCODED DEĞER!
Sorun: Polling interval frontend'de hardcoded: 30000 ms
Dosya: public/themes/muzibu/js/player/core/player-core.js
SatΔ±r: ~850 (setInterval iΓ§inde)
βœ… ÇÖZÜM: Config DosyasΔ±na Taşı
Yeni Dosya: config/muzibu.php
return [
    'session' => [
        'polling_interval' => env('MUZIBU_SESSION_POLLING', 30000), // ms
        'ttl' => env('MUZIBU_SESSION_TTL', 7200), // saniye
    ],
];
Blade Template:
window.muzibuPlayerConfig = {
    sessionPollingInterval: {{ config('muzibu.session.polling_interval') }},
};
5. STREAM AKIŞI (HLS + MP3 Fallback)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ STREAM URL Δ°STEĞİ AKIŞI β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ 1. Frontend /api/muzibu/songs/{id}/stream Γ§ağrΔ±sΔ± yapar β”‚ β”‚ 2. SongStreamController::stream() method Γ§alışır β”‚ β”‚ 3. User kontrolΓΌ: β”‚ β”‚ β”œβ”€β”€ Guest ($user == null) β”‚ β”‚ β”‚ └─→ getPreviewStreamResponse($song, 'guest') β”‚ β”‚ β”œβ”€β”€ Free Member (!isPremiumOrTrial()) β”‚ β”‚ β”‚ └─→ getPreviewStreamResponse($song, 'free') β”‚ β”‚ └── Premium/Trial Member (isPremiumOrTrial()) β”‚ β”‚ └─→ Full stream access β”‚ β”‚ 4. HLS kontrolΓΌ: β”‚ β”‚ β”œβ”€β”€ hls_path VARSA β†’ HLS playlist dΓΆndΓΌr β”‚ β”‚ β”‚ β”œβ”€β”€ Timeout: 6 saniye β”‚ β”‚ β”‚ └── Fallback: MP3 signed URL β”‚ β”‚ └── hls_path YOKSA β†’ ConvertToHLSJob dispatch et β”‚ β”‚ 5. Response: stream_url, stream_type, fallback_url β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
❌ MEVCUT HATA: DUPLICATE CODE
Guest ve Free Member için aynı kod bloğu tekrarlanıyor!
Dosya: SongStreamController.php
SatΔ±r 52-96: Guest user stream logic (48 satΔ±r)
SatΔ±r 100-149: Non-premium user stream logic (49 satΔ±r)
Sorun: AYNI kod bloğu kopyalanmış! (DRY ihlali)
βœ… ÇÖZÜM: Helper Method Oluştur
Yeni Method: getPreviewStreamResponse()
public function stream(Song $song)
{
    $user = auth('web')->user() ?? auth('sanctum')->user();

    // 1. Guest user
    if (!$user) {
        return $this->getPreviewStreamResponse($song, 'guest');
    }

    // 2. Free member
    if (!$user->isPremiumOrTrial()) {
        return $this->getPreviewStreamResponse($song, 'free');
    }

    // 3. Premium/Trial member
    return $this->getFullStreamResponse($song, $user);
}

protected function getPreviewStreamResponse(Song $song, string $userType): JsonResponse
{
    // HLS conversion
    if ($song->needsHlsConversion()) {
        ConvertToHLSJob::dispatch($song);
    }

    // Stream URL (30 saniye limit)
    if (!empty($song->hls_path)) {
        $streamUrl = route('api.muzibu.songs.dynamic-playlist', [
            'id' => $song->song_id
        ]);
        $streamType = 'hls';
        $fallbackUrl = $this->signedUrlService->generateStreamUrl(
            $song->song_id, 30, true
        );
    } else {
        $streamUrl = $this->signedUrlService->generateStreamUrl(
            $song->song_id, 30
        );
        $streamType = 'mp3';
        $fallbackUrl = null;
    }

    $message = $userType === 'guest'
        ? 'KayΔ±t olun, tam dinleyin'
        : 'Premium\'a geΓ§in, sΔ±nΔ±rsΔ±z dinleyin';

    return response()->json([
        'status' => 'preview',
        'message' => $message,
        'stream_url' => $streamUrl,
        'stream_type' => $streamType,
        'fallback_url' => $fallbackUrl,
        'preview_duration' => 30,
        'is_premium' => false,
    ]);
}

protected function getFullStreamResponse(Song $song, User $user): JsonResponse
{
    // Full stream logic (sΔ±nΔ±rsΔ±z)
    // ...
}
⚠️ DİKKAT: HARDCODED DEĞER!
Preview duration: 30 saniye hardcoded (4 yerde!)
HLS timeout: 6 saniye hardcoded
Chunk count: 3 chunk hardcoded
βœ… ÇÖZÜM: Config DosyasΔ±na Taşı
Dosya: config/muzibu.php
return [
    'stream' => [
        'preview_duration' => env('MUZIBU_PREVIEW_DURATION', 30), // saniye
        'hls_timeout' => env('MUZIBU_HLS_TIMEOUT', 6), // saniye
        'preview_chunks' => env('MUZIBU_PREVIEW_CHUNKS', 3), // adet
    ],
];
KullanΔ±m:
$previewDuration = config('muzibu.stream.preview_duration');
$this->signedUrlService->generateStreamUrl($song->song_id, $previewDuration);
6. PREMIUM STATUS DEĞİŞİKLİĞİ (Cache Invalidation)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PREMIUM STATUS CACHE AKIŞI β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ 1. User::isPremiumOrTrial() Γ§ağrΔ±sΔ± yapΔ±lΔ±r β”‚ β”‚ 2. Cache key: premium_status_{user_id}_{tenant_id} β”‚ β”‚ 3. Cache TTL: 5 dakika (300 saniye) β”‚ β”‚ 4. Cache HIT β†’ Direkt return β”‚ β”‚ 5. Cache MISS β†’ DB sorgusu + Cache write β”‚ β”‚ β”‚ β”‚ SORUN: Subscription değiştiğinde cache SΔ°LΔ°NMΔ°YOR! β”‚ β”‚ β”œβ”€β†’ Yeni ΓΌyelik başlatΔ±lΔ±r β†’ 5 dakika premium gΓΆrmez! β”‚ β”‚ β”œβ”€β†’ Üyelik iptal edilir β†’ 5 dakika premium gΓΆrmeye devam! β”‚ β”‚ └─→ Üyelik sΓΌresi biter β†’ 5 dakika premium gΓΆrmeye devam! β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
❌ MEVCUT HATA: SUBSCRIPTION OBSERVER EKSİK!
Sorun: Subscription değişikliklerinde cache otomatik silinmiyor!
Eksik Dosya: app/Observers/SubscriptionObserver.php
SonuΓ§: Premium status 5 dakika gecikmeyle gΓΌncelleniyor!
βœ… ÇÖZÜM: SubscriptionObserver Oluştur
Yeni Dosya: app/Observers/SubscriptionObserver.php
namespace App\Observers;

use Modules\Subscription\app\Models\Subscription;
use Illuminate\Support\Facades\Cache;

class SubscriptionObserver
{
    public function created(Subscription $subscription)
    {
        $this->flushPremiumCache($subscription->user_id);
    }

    public function updated(Subscription $subscription)
    {
        $this->flushPremiumCache($subscription->user_id);
    }

    public function deleted(Subscription $subscription)
    {
        $this->flushPremiumCache($subscription->user_id);
    }

    protected function flushPremiumCache(int $userId)
    {
        $tenantId = tenant() ? tenant()->id : 1;
        $cacheKey = "premium_status_{$userId}_{$tenantId}";

        Cache::forget($cacheKey);

        \Log::info("Premium cache flushed", [
            'user_id' => $userId,
            'tenant_id' => $tenantId,
            'cache_key' => $cacheKey,
        ]);
    }
}
Register Observer:
Dosya: app/Providers/AppServiceProvider.php
use Modules\Subscription\app\Models\Subscription;
use App\Observers\SubscriptionObserver;

public function boot()
{
    Subscription::observe(SubscriptionObserver::class);
}
βœ… EK ÇÖZÜM: Logout'ta da Cache Temizle
Dosya: app/Http/Controllers/Api/Auth/AuthController.php
public function logout()
{
    $user = auth('web')->user() ?? auth('sanctum')->user();

    if ($user) {
        // Premium cache temizle
        $tenantId = tenant() ? tenant()->id : 1;
        $cacheKey = "premium_status_{$user->id}_{$tenantId}";
        Cache::forget($cacheKey);

        // Device session temizle
        app(DeviceService::class)->unregisterSession();

        // Auth logout
        Auth::logout();
    }

    return response()->json(['message' => 'Logged out']);
}
7. HARDCODED DEĞERLER (Config'e Taşınacaklar)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ HARDCODED DEĞERLER LΔ°STESΔ° β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ 1. Preview Duration: 30 saniye (4 yerde hardcoded!) β”‚ β”‚ 2. HLS Timeout: 6 saniye β”‚ β”‚ 3. Preview Chunks: 3 adet β”‚ β”‚ 4. Session Polling: 30000 ms (frontend) β”‚ β”‚ 5. Session TTL: 7200 saniye (Redis) β”‚ β”‚ 6. Premium Cache TTL: 300 saniye (5 dakika) β”‚ β”‚ 7. Device Limit Fallback: 1 cihaz β”‚ β”‚ 8. Frontend deviceLimit: 1 (hardcoded!) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
❌ MEVCUT HATA 1: Frontend Device Limit Hardcoded
Dosya: public/themes/muzibu/js/player/core/player-core.js
SatΔ±r: ~40
Kod: deviceLimit: 1
βœ… ÇÖZÜM: Backend'den Δ°njection
Dosya: resources/views/themes/muzibu/layouts/app.blade.php
@auth
    @php
        $deviceService = app(\Modules\Muzibu\app\Services\DeviceService::class);
        $deviceLimit = $deviceService->getDeviceLimit(auth()->user());
    @endphp

    window.muzibuPlayerConfig = {
        deviceLimit: {{ $deviceLimit }},
        sessionPollingInterval: {{ config('muzibu.session.polling_interval') }},
        previewDuration: {{ config('muzibu.stream.preview_duration') }},
    };
@endauth
Frontend KullanΔ±mΔ±:
// player-core.js
const playerConfig = {
    deviceLimit: window.muzibuPlayerConfig?.deviceLimit || 1,
    sessionPollingInterval: window.muzibuPlayerConfig?.sessionPollingInterval || 30000,
    previewDuration: window.muzibuPlayerConfig?.previewDuration || 30,
};
βœ… ÇÖZÜM: Merkezi Config DosyasΔ± Oluştur
Yeni Dosya: config/muzibu.php
return [
    // Stream Settings
    'stream' => [
        'preview_duration' => env('MUZIBU_PREVIEW_DURATION', 30),
        'hls_timeout' => env('MUZIBU_HLS_TIMEOUT', 6),
        'preview_chunks' => env('MUZIBU_PREVIEW_CHUNKS', 3),
    ],

    // Session Settings
    'session' => [
        'polling_interval' => env('MUZIBU_SESSION_POLLING', 30000),
        'ttl' => env('MUZIBU_SESSION_TTL', 7200),
    ],

    // Cache Settings
    'cache' => [
        'premium_status_ttl' => env('MUZIBU_PREMIUM_CACHE_TTL', 300),
    ],

    // Device Settings
    'device' => [
        'default_limit' => env('MUZIBU_DEVICE_LIMIT_FALLBACK', 1),
    ],
];
8. SETTINGS ENTEGRASYONU (Tenant AyarlarΔ±)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ TENANT SETTINGS SΔ°STEMΔ° β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ MEVCUT SETTINGS (KullanΔ±lΔ±yor): β”‚ β”‚ β”œβ”€ ID: 23 β†’ auth_device_limit (Cihaz limit) β”‚ β”‚ └─ ID: 21 β†’ auth_session_lifetime (Session sΓΌre) β”‚ β”‚ β”‚ β”‚ EKLENECEK SETTINGS: β”‚ β”‚ β”œβ”€ muzibu_preview_duration (Preview sΓΌre) β”‚ β”‚ β”œβ”€ muzibu_hls_timeout (HLS timeout) β”‚ β”‚ β”œβ”€ muzibu_session_polling (Polling interval) β”‚ β”‚ └─ muzibu_premium_cache_ttl (Premium cache TTL) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
βœ… DOĞRU KULLANIM:
Settings değerleri setting() helper ile çekilmeli.
// DeviceService.php (MEVCUT - DOĞRU)
if (function_exists('setting') && setting('auth_device_limit')) {
    return (int) setting('auth_device_limit');
}

// SongStreamController.php (EKLENMELΔ°)
$previewDuration = setting('muzibu_preview_duration')
    ?? config('muzibu.stream.preview_duration');

// Session Polling (EKLENMELΔ°)
$pollingInterval = setting('muzibu_session_polling')
    ?? config('muzibu.session.polling_interval');
⚠️ DΔ°KKAT: Γ–ncelik SΔ±rasΔ±
1. Setting değeri: setting('key') - Tenant âzel ayar
2. Config değeri: config('muzibu.key') - Uygulama varsayılanı
3. Fallback: Hardcoded (son Γ§are!)
9. Γ–ZET: TÜM DÜZELTMELER (Γ–ncelik SΔ±rasΔ±)
πŸ”΄ YÜK SEK Γ–NCELΔ°K (Kritik Hatalar)
  • 1. CSRF Token Auto-Renewal (419 hatasΔ± dΓΌzelt)
  • 2. SongStreamController Duplicate Code (DRY ihlali)
  • 3. SubscriptionObserver Oluştur (Premium cache)
  • 4. Device Selection Modal Frontend Entegrasyonu
🟑 ORTA Γ–NCELΔ°K (Δ°yileştirmeler)
  • 5. config/muzibu.php Oluştur (Hardcoded değerler)
  • 6. Frontend Device Limit Injection (Backend'den al)
  • 7. Settings Entegrasyonu (Yeni ayarlar ekle)
  • 8. Logout'ta Premium Cache Temizle
🟒 DÜŞÜK Γ–NCELΔ°K (DokΓΌmantasyon)
  • 9. API Response Standardizasyonu
  • 10. Loglama Δ°yileştirme (Session, Device, Stream)
  • 11. Unit Test Yazma (DeviceService, Stream)

πŸ“Š RAPOR TAMAMLANDI

TΓΌm sistem akışlarΔ± analiz edildi β€’ Hatalar tespit edildi β€’ ÇâzΓΌmler dokΓΌmante edildi

🎡 Muzibu Music Platform β€’ Master Plan v1.0 β€’ 7 AralΔ±k 2025