🔧 Device Profile Sorunu - Çözüm Planı

Test Sunucusu için Tam Detaylı Uygulama Rehberi

📅 19 Şubat 2026 🧪 Test Sunucusu ⏱️ Süre: ~30 dakika

🐛 Sorunun Özeti

Kod Hatası

// DeviceProfile.php:108-121
public static function findOrCreateByFingerprint(int $userId, array $data): self
{
    $fingerprint = self::generateFingerprint($data);

    // ❌ SORUN: Sadece fingerprint ile arıyor (user_id kontrolü yok!)
    $profile = self::where('fingerprint', $fingerprint)->first();

    if ($profile) {
        // ❌ SORUN: Başka kullanıcının device'ını ÇALıyor!
        if ($profile->user_id !== $userId) {
            $profile->update(['user_id' => $userId]);
        }
        return $profile;
    }
    // ...
}
Ne Oluyor?
  1. Ahmet giriş yapıyor → Fingerprint: "Chrome+Win+1536x864" → device_profile: 3401 (user_id: 2899)
  2. Always giriş yapıyor → Aynı özellikler → Fingerprint: "Chrome+Win+1536x864"
  3. Sistem: "Fingerprint aynı, demek ki aynı cihaz!" → 3401'i bulur
  4. Sistem: "user_id farklı, güncelle!" → 3401'in sahibini 2905 (Always) yapar
  5. Ahmet'in cihazı artık Always'e ait! ❌

Etkilenen Kayıtlar

Kullanıcı Kullandığı Device Device Sahibi Yanlış Kayıt IP Kanıtı
Always (2905) 3401 Ahmet (2899) 3 play
Play IP: 176.41.33.58
Device IP: 88.230.97.118
Soner (2916) 3073 Sait (2431) 2 play
Play IP: 85.105.46.138
Device IP: 88.248.29.26

✅ Çözüm Stratejisi

Mantık Değişikliği

❌ ÖNCESİ (Yanlış)
UNIQUE: fingerprint
Arama: WHERE fingerprint = 'ABC'
Sonuç: Farklı kullanıcılar aynı device'ı paylaşıyor
✅ SONRASI (Doğru)
UNIQUE: (user_id, fingerprint)
Arama: WHERE user_id = 2905 AND fingerprint = 'ABC'
Sonuç: Her kullanıcının kendi device'ı var

Neden Bu Çözüm?

Farklı Kullanıcılar → Farklı Device
Always ve Ahmet'in aynı özelliklerde bilgisayarı olsa bile, her birinin ayrı device profili olur
IP Değişimi Sorun Değil
Kullanıcı wifi → mobil data geçişinde IP değişir ama user_id aynı, aynı device profile kullanılır
Aynı Kullanıcı Farklı Cihaz → Farklı Profile
Always laptop ve telefonda → 2 farklı fingerprint → 2 farklı device profile (istenen davranış)

📝 ADIM ADIM UYGULAMA PLANI

1

Mevcut Durumu Kaydet (Backup)

Herhangi bir sorun olursa geri dönebilmek için
1.1. Sorunlu kayıtları dışa aktar
mysql -u root -p'4p4CH3u$3r' tenant_muzibu_1528d0 -e "
SELECT * FROM muzibu_song_plays
WHERE (user_id = 2905 AND device_profile_id = 3401)
   OR (user_id = 2916 AND device_profile_id = 3073)
ORDER BY created_at;
" > /tmp/device_profile_yanlış_kayitlar.sql
1.2. Device profiles tablosu yedeği
mysqldump -u root -p'4p4CH3u$3r' tenant_muzibu_1528d0 \
  muzibu_device_profiles > /tmp/device_profiles_backup.sql
✅ Kontrol: ls -lh /tmp/*.sql
2

Sorunlu Kayıtları Temizle

⚠️ DİKKAT: Veri silme işlemi!
⚠️ Ne Yapılacak?
  • Always'in yanlış 3 play kaydı SİLİNECEK (device 3401 ile olanlar)
  • Soner'in yanlış 2 play kaydı SİLİNECEK (device 3073 ile olanlar)
  • Toplam 5 kayıt silinecek
2.1. Önce kontrol et (kaç kayıt silinecek?)
mysql -u root -p'4p4CH3u$3r' tenant_muzibu_1528d0 -e "
SELECT
    user_id,
    (SELECT name FROM users WHERE id = sp.user_id) as user_name,
    device_profile_id,
    COUNT(*) as will_delete
FROM muzibu_song_plays sp
WHERE (user_id = 2905 AND device_profile_id = 3401)
   OR (user_id = 2916 AND device_profile_id = 3073)
GROUP BY user_id, device_profile_id;
"
Beklenen: Always (3), Soner (2)
2.2. SİL (ONAY SONRASI!)
mysql -u root -p'4p4CH3u$3r' tenant_muzibu_1528d0 -e "
DELETE FROM muzibu_song_plays
WHERE (user_id = 2905 AND device_profile_id = 3401)
   OR (user_id = 2916 AND device_profile_id = 3073);
"
✅ Kontrol: SELECT COUNT(*) → 0 dönmeli
3

Migration Dosyası Oluştur

Database index değişikliği
3.1. Migration dosyası oluştur
php artisan make:migration add_composite_unique_to_device_profiles --path=Modules/Muzibu/database/migrations/tenant
3.2. Migration içeriği (aşağıdaki kod bloğunda)
Dosya: Modules/Muzibu/database/migrations/tenant/2026_02_19_XXXXXX_add_composite_unique_to_device_profiles.php
4

Model Kodunu Düzelt

DeviceProfile.php değişiklikleri
4.1. findOrCreateByFingerprint() fonksiyonu
Satır 108-121: user_id koşulu EKLE, user_id güncelleme KALDIR
5

Migration Çalıştır

Sadece tenant veritabanında
5.1. Tenant migration
php artisan tenants:migrate --force
✅ Kontrol: SHOW INDEXES FROM muzibu_device_profiles;
unique_user_device (user_id, fingerprint) görünmeli
6

Test Et

Farklı kullanıcılarla giriş yap
Test Senaryoları:
  1. Always olarak giriş yap → Şarkı çal → Kendi device profile'ını mı kullanıyor?
  2. Ahmet olarak giriş yap → Şarkı çal → Kendi device profile'ını mı kullanıyor?
  3. Soner olarak giriş yap → Şarkı çal → Kendi device profile'ını mı kullanıyor?
  4. Database kontrol: 3 farklı device_profile_id oluştu mu?
Test SQL
SELECT
    sp.user_id,
    u.name,
    sp.device_profile_id,
    dp.user_id as device_owner,
    CASE WHEN sp.user_id = dp.user_id THEN '✅' ELSE '❌' END as match
FROM muzibu_song_plays sp
JOIN users u ON sp.user_id = u.id
JOIN muzibu_device_profiles dp ON sp.device_profile_id = dp.device_profile_id
WHERE sp.user_id IN (2905, 2899, 2916)
ORDER BY sp.created_at DESC
LIMIT 10;
✅ Başarı Kriteri: Tüm match sütunu ✅ olmalı
7

Cache Temizle

Değişikliklerin yansıması için
php artisan cache:clear && \
php artisan config:clear && \
php artisan route:clear && \
php artisan view:clear

📄 DOSYA İÇERİKLERİ

Migration Dosyası

Modules/Muzibu/database/migrations/tenant/2026_02_19_XXXXXX_add_composite_unique_to_device_profiles.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('muzibu_device_profiles', function (Blueprint $table) {
            // 1. Eski index'i kaldır
            $table->dropIndex('muzibu_device_profiles_fingerprint_index');

            // 2. Yeni composite unique index ekle
            $table->unique(['user_id', 'fingerprint'], 'unique_user_device');
        });
    }

    public function down(): void
    {
        Schema::table('muzibu_device_profiles', function (Blueprint $table) {
            // Rollback: Composite unique kaldır, eski index geri ekle
            $table->dropUnique('unique_user_device');
            $table->index('fingerprint');
        });
    }
};

Model Değişikliği

Modules/Muzibu/App/Models/DeviceProfile.php
❌ ÖNCESİ (Satır 108-121):
public static function findOrCreateByFingerprint(int $userId, array $data): self
{
    $fingerprint = self::generateFingerprint($data);

    // Önce fingerprint ile ara (unique olduğu için)
    $profile = self::where('fingerprint', $fingerprint)->first();

    if ($profile) {
        // Cihaz zaten kayıtlı - user_id'yi güncelle (farklı kullanıcı aynı cihazı kullanabilir)
        if ($profile->user_id !== $userId) {
            $profile->update(['user_id' => $userId]);  // ❌ SORUN!
        }
        return $profile;
    }

    // Yeni cihaz - oluştur
    try {
        return self::create(array_merge($data, [
            'fingerprint' => $fingerprint,
            'user_id' => $userId,
        ]));
    } catch (\Illuminate\Database\QueryException $e) {
        // ...
    }
}
✅ SONRASI (Satır 108-121):
public static function findOrCreateByFingerprint(int $userId, array $data): self
{
    $fingerprint = self::generateFingerprint($data);

    // ✅ user_id VE fingerprint ile ara
    $profile = self::where('fingerprint', $fingerprint)
                   ->where('user_id', $userId)
                   ->first();

    if ($profile) {
        // Cihaz zaten kayıtlı, döndür
        return $profile;
    }

    // Yeni cihaz - oluştur
    try {
        return self::create(array_merge($data, [
            'fingerprint' => $fingerprint,
            'user_id' => $userId,
        ]));
    } catch (\Illuminate\Database\QueryException $e) {
        // Race condition: Başka bir request aynı anda oluşturmuş olabilir
        if ($e->getCode() === '23000') {
            $profile = self::where('fingerprint', $fingerprint)
                           ->where('user_id', $userId)
                           ->first();
            if ($profile) {
                return $profile;
            }
        }
        throw $e;
    }
}

✅ TEST KONTROL LİSTESİ

🔧 SORUN GİDERME

❓ Migration Hatası: "Duplicate entry"
Sebep: Aynı (user_id, fingerprint) kombinasyonu birden fazla var
Çözüm:
-- Duplicate kayıtları bul
SELECT user_id, fingerprint, COUNT(*) as cnt
FROM muzibu_device_profiles
GROUP BY user_id, fingerprint
HAVING cnt > 1;

-- En eskilerini tut, diğerlerini sil
-- (Manuel kontrol gerekir!)
❓ Kullanıcı Hala Yanlış Device Kullanıyor
Sebep: Browser localStorage'da eski device_profile_id cache'lenmiş
Çözüm:
Kullanıcıya söyle: F12 → Console → localStorage.removeItem('muzibu_device_profile_id') → Sayfayı yenile
❓ Rollback Gerekirse
Çözüm:
-- 1. Migration geri al
php artisan migrate:rollback --step=1 --path=Modules/Muzibu/database/migrations/tenant

-- 2. Backup'tan geri yükle
mysql -u root -p'4p4CH3u$3r' tenant_muzibu_1528d0 < /tmp/device_profiles_backup.sql

-- 3. Model kodunu eski haline getir (git reset)
19 Şubat 2026 • Muzibu.com.tr