🔒 Muzibu Premium v4

MEVCUT TABLO + BACKEND GÜVENLİK 🔐 Secure

JS Manipülasyonu İmkansız

📅 26 Kasım 2025 | 🎯 Tenant 1001 | 🔒 Backend Validated
🔒 Güvenlik Öncelikli Sistem

⚠️ SORUN: Frontend Güvenilmez

  • ❌ JavaScript manipüle edilebilir (DevTools, extension)
  • ❌ LocalStorage/Cookie hacklenebilir
  • ❌ API call'ları sahte veri gönderebilir
  • ❌ Zamanlayıcılar atlanabilir

✅ ÇÖZÜM: Backend Validation

  • Mevcut tablo kullan: muzibu_song_plays (zaten var!)
  • Backend kontrol: Her API call'da limit check
  • 30 saniye kuralı: Backend'de validate
  • Sıfır JS güven: Tüm logic server-side

🎯 Sistem Kuralları

Kullanıcı Kural Kontrol
🚫 Guest Her şarkının 30 saniyesi Frontend fade-out (güvenlik gereksiz)
👤 Normal Üye Günde 5 şarkı (30+ saniye dinlenen) Backend API: muzibu_song_plays tablosundan count
⭐ Premium/Deneme Sınırsız Backend: user.isPremium() check
💾 Mevcut Tablo Kullan - Sıfır Yeni Tablo!

✅ MEVCUT: muzibu_song_plays

muzibu_song_plays (ZATEN VAR!)
├── id
├── song_id
├── user_id
├── ip_address
├── user_agent
├── device_type
├── created_at
└── updated_at

🔧 TEK EKSİK: duration_listened

Migration ekle:

// 2025_11_26_add_duration_to_song_plays.php

Schema::table('muzibu_song_plays', function (Blueprint $table) {
    $table->integer('duration_listened')->default(0)
          ->after('user_id')
          ->comment('Kaç saniye dinlendi (30+ = tam dinleme)');

    // Index ekle (performans için)
    $table->index(['user_id', 'created_at', 'duration_listened'],
                  'user_daily_plays_idx');
});

✅ Bu Kadar!

- ❌ Yeni tablo YOK
- ❌ users tablosuna alan ekleme YOK
- ✅ Sadece 1 kolon ekle: duration_listened
- ✅ Mevcut song_plays mekanizması çalışıyor zaten

⚙️ Backend Logic - Güvenli Kod

1️⃣ User Model - Helper Methods

// app/Models/User.php

/**
 * Kullanıcı bugün kaç şarkı dinledi? (30+ saniye)
 */
public function getTodayPlayedCount(): int
{
    return DB::table('muzibu_song_plays')
        ->where('user_id', $this->id)
        ->where('duration_listened', '>=', 30)
        ->whereDate('created_at', today())
        ->count();
}

/**
 * Şarkı çalabilir mi?
 */
public function canPlaySong(): bool
{
    // Premium/Trial → Sınırsız
    if ($this->isPremium() || $this->isTrialActive()) {
        return true;
    }

    // Normal üye → Günde 5 şarkı
    return $this->getTodayPlayedCount() < 5;
}

/**
 * Kalan şarkı hakkı
 */
public function getRemainingPlays(): int
{
    if ($this->isPremium() || $this->isTrialActive()) {
        return -1; // Sınırsız
    }

    return max(0, 5 - $this->getTodayPlayedCount());
}

2️⃣ API Controller - Stream Endpoint

// Modules/Muzibu/app/Http/Controllers/Api/SongStreamController.php

public function stream(int $songId): JsonResponse
{
    $user = auth()->user();

    // Guest → 30 saniye preview (frontend'de fade-out)
    if (!$user) {
        return response()->json([
            'status' => 'preview',
            'stream_url' => $song->getStreamUrl(),
            'preview_duration' => 30,
            'message' => 'Kayıt olun, tam dinleyin'
        ]);
    }

    // Limit kontrolü (BACKEND'DE!)
    if (!$user->canPlaySong()) {
        return response()->json([
            'status' => 'limit_exceeded',
            'played_today' => $user->getTodayPlayedCount(),
            'limit' => 5,
            'message' => 'Günlük 5 şarkı limitiniz doldu'
        ], 403);
    }

    // OK → Stream ver
    return response()->json([
        'status' => 'ok',
        'stream_url' => $song->getStreamUrl(),
        'remaining' => $user->getRemainingPlays()
    ]);
}

3️⃣ Track Play - Şarkı Dinleme Kaydı

// Frontend her 5 saniyede progress rapor eder
// Backend son raporu saklar

public function trackPlay(Request $request, int $songId): JsonResponse
{
    $durationListened = $request->input('duration', 0);

    // Güvenlik: Maximum şarkı süresi
    $song = Song::findOrFail($songId);
    if ($durationListened > $song->duration_seconds + 10) {
        return response()->json(['error' => 'invalid_duration'], 400);
    }

    // Insert/Update
    DB::table('muzibu_song_plays')->updateOrInsert(
        [
            'user_id' => auth()->id(),
            'song_id' => $songId,
            'created_at' => now() // Aynı gün, aynı şarkı
        ],
        [
            'duration_listened' => $durationListened,
            'ip_address' => $request->ip(),
            'user_agent' => $request->userAgent(),
            'device_type' => $this->detectDevice(),
            'updated_at' => now()
        ]
    );

    return response()->json([
        'success' => true,
        'remaining' => auth()->user()->getRemainingPlays()
    ]);
}

🔒 Güvenlik Önlemleri

  • Duration validation: Şarkı süresinden fazla rapor edilemez
  • Rate limiting: Aynı kullanıcı saniyede 1'den fazla rapor edemez
  • User ID check: Auth middleware ile garanti
  • 30 saniye backend: Frontend değil, backend karar verir
🔄 Sistem İşleyiş Akışı

1️⃣ Kullanıcı Şarkıya Tıklar

Frontend: API.stream(songId) çağrısı yapar

⬇️

2️⃣ Backend Limit Kontrolü

Query:
SELECT COUNT(*) FROM muzibu_song_plays WHERE user_id = ? AND created_at >= TODAY() AND duration_listened >= 30

⬇️

❌ Limit Doldu (5/5)?

Response 403:
{ "error": "limit_exceeded", "played_today": 5 }
Frontend modal gösterir, şarkı çalmaz

VEYA

✅ Limit OK (3/5)?

Response 200:
{ "stream_url": "...", "remaining": 2 }
Frontend şarkıyı çalar

⬇️

3️⃣ Frontend Progress Raporu

Her 5 saniyede: API.trackPlay(songId, currentTime)
Örnek: 10s, 15s, 20s, 25s, 30s, 35s...

⬇️

4️⃣ Backend Kayıt

duration_listened güncellenir
30+ saniye olduğunda → "Tam dinleme" sayılır
Bir sonraki API call'da limit'e sayılır

⬇️

5️⃣ Şarkı Biter

Son progress raporu: duration_listened = son_pozisyon
Frontend sidebar'ı günceller: "4/5 şarkı"

✅ Neden Güvenli?

  • 🔒 Her şarkı stream: Backend kontrol eder
  • 🔒 30 saniye: Backend DB'den okur, JS değil
  • 🔒 Manipülasyon: Frontend'i hack'lesen bile limit backend'de
  • 🔒 Progress raporu: Duration validation var, sınır aşamaz