where('is_published', true) ->orderBy('trending_order') ->get(); $autoTrending = $this->getAutoTrending(20); // Son skor hesaplama zamanı $lastComputed = cache()->get('trending_score_computed_at'); return view('admin.trending.index', compact('manual', 'autoTrending', 'lastComputed')); } // ── Manuel trending yönetimi ────────────────────────────────────────────── public function toggle(Request $request, Anime $anime) { $newState = !$anime->is_trending; if ($newState) { $maxOrder = Anime::where('is_trending', true)->max('trending_order') ?? 0; $anime->update([ 'is_trending' => true, 'trending_order' => $maxOrder + 1, 'trending_score' => $anime->trending_score + 200, // Manuel boost ]); } else { $anime->update(['is_trending' => false, 'trending_order' => 0]); $this->reorderAll(); } if ($request->wantsJson()) { return response()->json(['ok' => true, 'is_trending' => $newState]); } return back()->with('success', $newState ? '"'.$anime->title.'" trend listesine eklendi.' : '"'.$anime->title.'" trend listesinden çıkarıldı.'); } public function reorder(Request $request) { $request->validate(['ids' => 'required|array', 'ids.*' => 'integer']); foreach ($request->ids as $i => $id) { Anime::where('id', $id)->update(['trending_order' => $i + 1]); } return response()->json(['ok' => true]); } public function move(Request $request, Anime $anime) { $direction = $request->input('direction'); $current = $anime->trending_order; if ($direction === 'up' && $current > 1) { $swap = Anime::where('is_trending', true)->where('trending_order', $current - 1)->first(); if ($swap) { $swap->update(['trending_order' => $current]); $anime->update(['trending_order' => $current - 1]); } } elseif ($direction === 'down') { $swap = Anime::where('is_trending', true)->where('trending_order', $current + 1)->first(); if ($swap) { $swap->update(['trending_order' => $current]); $anime->update(['trending_order' => $current + 1]); } } return back(); } public function search(Request $request) { $results = Anime::where('is_published', true) ->where('title', 'like', "%{$request->query('q', '')}%") ->select('id', 'title', 'cover_image', 'is_trending', 'release_year', 'trending_score') ->take(8)->get() ->map(fn($a) => [ 'id' => $a->id, 'title' => $a->title, 'cover' => $a->coverUrl, 'is_trending' => (bool) $a->is_trending, 'year' => $a->release_year, 'trending_score'=> round($a->trending_score, 1), ]); return response()->json(['results' => $results]); } // ── Trend Skoru Hesaplama (YouTube algoritması) ─────────────────────────── /** * Admin butonu: tüm animelerin trend skorunu hesapla ve kaydet. * POST /admin/trending/compute-scores */ public function computeScores() { $count = self::runScoreComputation(); cache()->put('trending_score_computed_at', now()->toDateTimeString(), 3600); cache()->flush(); // Anasayfa cache'ini temizle return response()->json([ 'ok' => true, 'updated' => $count, 'message' => "{$count} anime için trend skoru güncellendi.", ]); } /** * YouTube-benzeri Trend Skoru Algoritması * ───────────────────────────────────────── * score = view_24h × 12 ← Son 24 saatin izlenme sayısı (en yüksek ağırlık) * + view_7d × 4 ← Son 7 günün izlenme sayısı * + view_30d × 1 ← Son 30 günün izlenme sayısı * + watch_minutes_7d × 0.8 ← Gerçek izleme dakikası (kalite sinyali) * + new_episode_bonus ← Yeni bölüm varsa büyük bonus * + rating × 4 ← Kalite sinyali * + manual_boost ← Manuel trending = +250 * * Decay: Eski içeriklerin skoru doğal olarak düşer (view_count azalır). * Herhangi bir yeni bölüm veya izlenme olmadan skor sıfıra yaklaşır. */ public static function runScoreComputation(): int { $now = now(); $day1 = $now->copy()->subDay(); $day7 = $now->copy()->subDays(7); $day30 = $now->copy()->subDays(30); $animes = DB::table('animes') ->where('is_published', true) ->select('id', 'rating', 'is_trending', 'status') ->get(); $updated = 0; foreach ($animes as $anime) { // ── Bölüm izlenme sayıları (view_count zaman dilimine göre) ────── // Episode.updated_at → son izleme zamanının proxy'si $views = DB::table('episodes') ->where('anime_id', $anime->id) ->where('is_published', true) ->selectRaw(" SUM(CASE WHEN updated_at >= ? THEN view_count ELSE 0 END) as v24h, SUM(CASE WHEN updated_at >= ? THEN view_count ELSE 0 END) as v7d, SUM(CASE WHEN updated_at >= ? THEN view_count ELSE 0 END) as v30d ", [$day1, $day7, $day30]) ->first(); // ── Gerçek izleme dakikası (analytics_watch_events) ───────────── $watchMinutes = 0; try { $watchMinutes = DB::table('analytics_watch_events') ->where('anime_id', $anime->id) ->where('created_at', '>=', $day7) ->sum('seconds_watched') / 60; } catch (\Throwable) {} // ── Yeni bölüm bonusu ──────────────────────────────────────────── $newEpBonus = 0; $latestEpDate = DB::table('episodes') ->where('anime_id', $anime->id) ->where('is_published', true) ->max('created_at'); if ($latestEpDate) { $epAge = now()->diffInHours($latestEpDate); if ($epAge <= 24) $newEpBonus = 80; // Bugün yeni bölüm → çok büyük boost elseif ($epAge <= 72) $newEpBonus = 40; // Son 3 gün elseif ($epAge <= 168) $newEpBonus = 15; // Son 7 gün elseif ($epAge <= 720) $newEpBonus = 5; // Son 30 gün } // ── Ongoing bonus ───────────────────────────────────────────────── $ongoingBonus = ($anime->status === 'ongoing') ? 10 : 0; // ── Manuel trending boost ───────────────────────────────────────── $manualBoost = $anime->is_trending ? 250 : 0; // ── Skor hesapla ───────────────────────────────────────────────── $score = ($views->v24h ?? 0) * 12 + ($views->v7d ?? 0) * 4 + ($views->v30d ?? 0) * 1 + $watchMinutes * 0.8 + $newEpBonus + $ongoingBonus + ((float)($anime->rating ?? 5)) * 4 + $manualBoost; DB::table('animes') ->where('id', $anime->id) ->update(['trending_score' => round($score, 2)]); $updated++; } return $updated; } /** * Auto-trending: trending_score'a göre sırala. * Fallback: score kolonu yoksa eski yönteme dön. */ public static function getAutoTrending(int $limit = 12): \Illuminate\Support\Collection { try { return Anime::where('is_published', true) ->orderByDesc('trending_score') ->take($limit) ->get(); } catch (\Throwable) { // trending_score kolonu henüz oluşturulmamış → eski yöntem return Anime::where('is_published', true) ->withSum(['episodes as recent_views' => fn($q) => $q->where('is_published', true)->where('updated_at', '>=', now()->subDays(30)) ], 'view_count') ->orderByDesc('recent_views') ->take($limit) ->get(); } } private function reorderAll(): void { $animes = Anime::where('is_trending', true)->orderBy('trending_order')->get(); foreach ($animes as $i => $a) { $a->update(['trending_order' => $i + 1]); } } }