1. Giriş Sayfası
Kullanıcı e-posta ve şifresini girer, "Giriş Yap" butonuna tıklar.
2. Başarılı Giriş
Sayfa yenilenmeden giriş yapılır, header'da kullanıcı adı görünür.
3. Sidebar Durumu
4. Cihaz Limiti
İlk girişte hiç modal çıkmaz. Sadece limit aşılırsa (2+ cihaz) uyarı gösterilir.
API Endpoint
POST /api/auth/login
{
"email": "user@example.com",
"password": "******",
"remember": true
}
AuthController::login()
DeviceService::registerSession()
// 1. Session ID al (Redis'ten)
$sessionId = session()->getId();
// 2. user_active_sessions tablosuna kaydet
DB::table('user_active_sessions')
->updateOrInsert(
['session_id' => $sessionId],
[
'user_id' => $user->id,
'device_name' => 'OS X - Chrome',
'last_activity' => now()
]
);
// 3. Limit kontrolü ve cleanup
$this->handlePostLoginDeviceLimit($user);
Session Storage
⚠️ Redis Session Driver Zorunlu!
# .env SESSION_DRIVER=redis REDIS_HOST=127.0.0.1 REDIS_PORT=6379
Session Redis'te, device tracking DB'de (hybrid sistem)
Ne Zaman Uyarı Çıkar?
Uyarı Modalı
Modal göründüğünde kullanıcı:
✅ İlk Girişte Modal YOK!
Tek cihazdan giriş yapıyorsanız hiçbir uyarı görmezsiniz. Sistem arka planda çalışır.
Cihaz Limitleri
| Ücretsiz Üye | 1 cihaz |
| Trial (Deneme) | Plan'a göre |
| Premium | Plan'a göre (1-5) |
3-Seviyeli Device Limit Hierarchy
DeviceService::getDeviceLimit($user):
1. User Override (VIP/Ban)
→ $user->device_limit (nullable)
2. Subscription Plan
→ $subscription->plan->device_limit
3. Tenant Settings Fallback
→ setting('auth_device_limit')
4. Ultimate Fallback
→ return 1;
Session Polling (30 saniye)
// player-core.js
startSessionPolling() {
setInterval(() => {
fetch('/api/auth/check-session')
.then(r => r.json())
.then(data => {
if (!data.valid) {
// Session sonlandırıldı
showDeviceLimitModal();
}
});
}, 30000);
}
Backend Check
// AuthController::checkSession()
$isValid = $deviceService
->updateSessionActivity($user);
if (!$isValid) {
// Session DB'de yok = başka yerden kapatıldı
Auth::logout();
return [
'valid' => false,
'reason' => 'device_limit_exceeded'
];
}
⚠️ Dinamik Tenant Kontrolü
// YANLIŞ (hardcoded)
if (tenant()->id == 1001) { ... }
// DOĞRU (dinamik)
if (tenant()) {
// Tüm tenant'lar için çalışır
$deviceService->registerSession($user);
}
Sidebar'da Görünen Durum
Üyelik Avantajları
| Ücretsiz | 30sn önizleme, reklamlı |
| Deneme | Tam erişim, sınırlı süre |
| Premium | Tam erişim, HLS streaming |
User::isPremium()
// 5 dakikalık cache (performans)
$cacheKey = 'user_' . $this->id .
'_is_premium_tenant_' . tenant()->id;
return Cache::remember($cacheKey, 300, function() {
return $this->subscriptions()
->where('status', 'active')
->where('current_period_end', '>', now())
->exists();
});
User::isTrialActive()
return $this->subscriptions()
->whereIn('status', ['active', 'trial'])
->where('has_trial', true)
->whereNotNull('trial_ends_at')
->where('trial_ends_at', '>', now())
->exists();
Sidebar Badge Logic
// sidebar-left.blade.php
@if($user->isTrialActive())
<span class="badge bg-yellow-500">
Deneme Üyesi
</span>
@elseif($user->isPremium())
<span class="badge bg-green-500">
Premium Üye
</span>
@else
<span class="badge bg-slate-500">
Ücretsiz Üye
</span>
@endif
Subscription Tablosu
subscriptions (tenant DB): - id - user_id - plan_id - status: 'active' | 'trial' | 'canceled' - has_trial: boolean - trial_ends_at: datetime - current_period_end: datetime
1. Şarkıya Tıkla
Herhangi bir şarkı kartına tıklayın. Player altta belirir.
2. Player Kontrolleri
3. Ücretsiz Kullanıcı Kısıtlaması
⚠️ 30 saniye önizleme sonrası şarkı durur ve Premium upgrade modal'ı gösterilir.
Premium/Trial Avantajı
player-core.js - play()
async play(song) {
// 1. Premium kontrolü
const isPremium = await this.checkPremiumStatus();
// 2. URL al
const url = isPremium
? `/api/muzibu/songs/${song.id}/stream` // HLS
: `/api/muzibu/songs/${song.id}/serve?force_mp3=1`;
// 3. Howler.js ile çal
this.playWithHowler(url, song);
}
Streaming Endpoints
/stream |
HLS (.m3u8) - Premium |
/serve |
MP3 direct - Free preview |
/key |
AES-128 decryption key |
Guest Preview Config
guestPreviewConfig: {
maxPreviewDuration: 30, // saniye
showUpgradeModal: true,
allowSeek: false, // Seek engelli
previewMessage: '30 saniyelik önizleme'
}
Seek Block (Free users)
// player-core.js:1085
if (!this.isPremium && seekTo > 30) {
console.log('🔒 Seek blocked: max 30s');
return;
}
1. Şarkı Sırası (Queue)
Player'da sıra ikonuna tıklayınca çalma listesi açılır. Sıradaki şarkıları görebilir, sürükleyip sıralayabilirsiniz.
2. Otomatik Devam
Bir şarkı bittiğinde otomatik olarak sıradaki şarkı başlar. Liste sonuna gelince yenileri yüklenir.
3. Bitmez Müzik (Infinite)
✨ Sıra azaldığında sistem otomatik olarak benzer şarkılar ekler. Müzik hiç bitmez!
4. Play Context
Queue State Management
// muzibu-store.js
state: {
queue: [], // Şarkı listesi
queueIndex: 0, // Şu anki index
playContext: {
type: 'playlist', // playlist|album|genre
id: 123,
name: 'Chill Vibes'
}
}
Queue Monitor (10 saniye)
// player-core.js:3037
startQueueMonitor() {
setInterval(() => {
const remaining = queue.length - queueIndex;
console.log(`Queue Check: ${remaining} songs`);
if (remaining < 5) {
this.refillQueue(); // Auto-refill
}
}, 10000);
}
Refill API
POST /api/muzibu/queue/refill
{
"type": "genre", // Context type
"id": 3, // Context ID
"offset": 15, // Skip already played
"limit": 15 // How many to fetch
}
Response:
{
"songs": [...], // New songs
"has_more": true
}
Aggressive Preload
// player-core.js:2944
aggressivePreload() {
// Sonraki 3 şarkıyı önceden yükle
const nextSongs = queue.slice(
queueIndex + 1,
queueIndex + 4
);
nextSongs.forEach(song => {
this.preloadSong(song);
});
}
Multi-Tenant Sistem
Her web sitesi (tenant) kendi veritabanına sahip. Veriler birbirinden tamamen izole.
Central vs Tenant DB
Redis Cache
Hızlı erişim için sıkça kullanılan veriler Redis'te saklanır. Session'lar da Redis'te.
Database Mapping
Central (tuufi_4ekim): ├── users ├── roles ├── permissions ├── tenants └── domains Tenant (tenant_muzibu_1528d0): ├── songs ├── albums ├── artists ├── playlists ├── genres ├── subscriptions ├── subscription_plans └── user_active_sessions ← NEW!
user_active_sessions Table
CREATE TABLE user_active_sessions ( id BIGINT PRIMARY KEY, user_id BIGINT, session_id VARCHAR(255) UNIQUE, ip_address VARCHAR(45), user_agent TEXT, device_type VARCHAR(50), device_name VARCHAR(255), browser VARCHAR(100), platform VARCHAR(100), last_activity TIMESTAMP, INDEX (user_id), INDEX (session_id) );
⚠️ Hybrid Session System
Redis (SESSION_DRIVER=redis): └── Session data, CSRF tokens, flash messages MySQL (user_active_sessions): └── Device tracking, queryable, reportable Why hybrid? - Redis = Fast, ephemeral - MySQL = Queryable, persistent tracking
Login sonrası user_active_sessions tablosuna kayıt oluşturuluyor.
Total Sessions: 1 ID: 3 Device: OS X - Chrome Last: 2025-12-07 18:54:17
User::isPremium() ve isTrialActive() doğru çalışıyor.
User: nurullah@nurullah.net is_premium: true is_trial: true Sub Status: active has_trial: true trial_ends_at: 2025-12-08 17:42:49
Hardcoded "1001" referansları kaldırıldı. Artık tüm tenant'lar için çalışıyor.
// ÖNCE (hardcoded)
if (tenant()->id == 1001) { ... }
// SONRA (dinamik)
if (tenant()) { ... }
Login/me response'a is_trial flag eklendi. Sidebar doğru badge gösteriyor.
// API Response
{
"is_premium": true,
"is_trial": true, // YENİ!
"trial_ends_at": "2025-12-08T..."
}
SESSION_DRIVER=redis, hybrid sistem çalışıyor. Session Redis'te, tracking DB'de.
İlk girişte modal çıkmıyor. Sadece limit aşılınca (2+ cihaz) uyarı gösterilir.
Console Log Özeti: Session polling başlatıldı (30s), Queue monitor aktif (10s), Aggressive preload çalışıyor (next 3 songs), HLS streaming hazır.
Backend (PHP/Laravel)
Modules/Muzibu/app/Services/DeviceService.php
app/Http/Controllers/Api/Auth/AuthController.php
app/Models/User.php
Modules/Subscription/app/Models/Subscription.php
Frontend (JS/Blade)
public/themes/muzibu/js/player/core/player-core.js
public/themes/muzibu/js/store/muzibu-store.js
resources/views/themes/muzibu/components/sidebar-left.blade.php
resources/views/themes/muzibu/components/device-limit-modal.blade.php