Files
animexe/app/Http/Controllers/Admin/AnalyticsController.php
T
2026-07-14 00:01:48 +03:00

359 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Analytics\PageView;
use App\Models\Analytics\WatchEvent;
use App\Models\Analytics\AiQuery;
use App\Models\Analytics\VisitorSession;
use App\Models\Anime;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class AnalyticsController extends Controller
{
public function index(Request $request)
{
$period = $request->input('period', '7d');
$from = match ($period) {
'today' => now()->startOfDay(),
'30d' => now()->subDays(30),
'90d' => now()->subDays(90),
default => now()->subDays(7),
};
$cacheKey = 'admin_analytics_' . $period;
$cached = Cache::remember($cacheKey, 300, function () use ($from, $period) {
return $this->buildAnalytics($from, $period);
});
extract($cached);
// Gerçek zamanlı veriler (cache'lenmiyor)
$recentViews = PageView::with('user:id,name')
->where('created_at', '>=', $from)
->orderByDesc('id')
->limit(20)
->get();
$blockedIps = collect();
$recentBots = collect();
try {
$blockedIps = DB::table('blocked_ips')->orderByDesc('blocked_at')->limit(20)->get();
$recentBots = DB::table('analytics_bot_logs')->orderByDesc('id')->limit(30)->get();
} catch (\Exception) {}
return view('admin.analytics.index', compact(
'period', 'from',
'totalViews', 'uniqueVisitors', 'watchSeconds', 'aiTotal', 'newUsers',
'viewsDelta', 'todayViews', 'yesterdayViews',
'trendLabels', 'trendData', 'watchTrendData',
'hourlyData',
'topAnimes',
'topEpisodes',
'deviceStats', 'browserStats', 'pageTypeStats',
'geoStats',
'activeUsers',
'aiByType', 'aiTopQuestions', 'aiTopUsers',
'recentViews',
'referrerStats', 'directTraffic',
'botViews', 'humanViews', 'botRatio', 'botTopIps', 'botByName',
'blockedIps', 'recentBots',
'sessions', 'avgSessionTime', 'avgPages',
));
}
private function buildAnalytics($from, string $period): array
{
// ── Özet kartlar ──────────────────────────────────────────────────────
$totalViews = PageView::where('created_at', '>=', $from)->count();
$uniqueVisitors = PageView::where('created_at', '>=', $from)->distinct('session_id')->count('session_id');
$watchSeconds = WatchEvent::where('created_at', '>=', $from)->sum('seconds_watched');
$aiTotal = AiQuery::where('created_at', '>=', $from)->count();
$newUsers = User::where('created_at', '>=', $from)->count();
$yesterday = now()->subDay();
$todayViews = PageView::where('created_at', '>=', now()->startOfDay())->count();
$yesterdayViews = PageView::whereBetween('created_at', [$yesterday->startOfDay(), $yesterday->endOfDay()])->count();
$viewsDelta = $yesterdayViews > 0 ? round(($todayViews - $yesterdayViews) / $yesterdayViews * 100) : 0;
// ── Görüntüleme trendi (gün bazlı) ────────────────────────────────────
$viewsByDay = PageView::where('created_at', '>=', $from)
->selectRaw('DATE(created_at) as date, COUNT(*) as cnt')
->groupBy('date')
->orderBy('date')
->pluck('cnt', 'date');
$trendLabels = [];
$trendData = [];
$cur = clone $from;
while ($cur->lte(now())) {
$key = $cur->format('Y-m-d');
$trendLabels[] = $cur->format($period === 'today' ? 'H:i' : 'd M');
$trendData[] = $viewsByDay[$key] ?? 0;
$cur->addDay();
}
// ── Saatlik dağılım (bugün) ───────────────────────────────────────────
$hourlyRaw = PageView::where('created_at', '>=', now()->startOfDay())
->selectRaw('HOUR(created_at) as hour, COUNT(*) as cnt')
->groupBy('hour')
->pluck('cnt', 'hour');
$hourlyData = array_map(fn($h) => $hourlyRaw[$h] ?? 0, range(0, 23));
// ── İzleme süresi trendi ─────────────────────────────────────────────
$watchByDay = WatchEvent::where('created_at', '>=', $from)
->selectRaw('DATE(created_at) as date, ROUND(SUM(seconds_watched)/3600, 1) as hours')
->groupBy('date')
->orderBy('date')
->pluck('hours', 'date');
$watchTrendData = array_map(fn($k) => (float)($watchByDay[$k] ?? 0), array_keys(array_flip($trendLabels)));
// ── Top 10 anime ─────────────────────────────────────────────────────
$topAnimeIds = PageView::where('created_at', '>=', $from)
->whereNotNull('anime_id')
->selectRaw('anime_id, COUNT(*) as cnt')
->groupBy('anime_id')
->orderByDesc('cnt')
->limit(10)
->pluck('cnt', 'anime_id');
$topAnimes = Anime::whereIn('id', $topAnimeIds->keys())
->get(['id', 'title', 'cover_image'])
->map(fn($a) => [
'title' => $a->title,
'views' => $topAnimeIds[$a->id] ?? 0,
'cover' => $a->cover_url,
'slug' => $a->slug,
])
->sortByDesc('views')
->values();
// ── Top bölümler ─────────────────────────────────────────────────────
$topEpisodes = WatchEvent::where('analytics_watch_events.created_at', '>=', $from)
->selectRaw('anime_id, season_number, episode_number, episode_id,
SUM(seconds_watched) as total_sec,
COUNT(*) as plays,
ROUND(AVG(percent_complete), 0) as avg_pct')
->groupBy('anime_id', 'season_number', 'episode_number', 'episode_id')
->orderByDesc('total_sec')
->limit(10)
->get();
$epAnimes = Anime::whereIn('id', $topEpisodes->pluck('anime_id')->unique())->pluck('title', 'id');
$topEpisodes = $topEpisodes->map(fn($e) => [
'anime' => $epAnimes[$e->anime_id] ?? 'Bilinmiyor',
'label' => "S{$e->season_number}E{$e->episode_number}",
'plays' => $e->plays,
'hours' => round($e->total_sec / 3600, 1),
'avg_pct' => $e->avg_pct,
]);
// ── Cihaz / tarayıcı / sayfa türü ────────────────────────────────────
$deviceStats = PageView::where('created_at', '>=', $from)
->selectRaw('device, COUNT(*) as cnt')
->groupBy('device')
->pluck('cnt', 'device');
$browserStats = PageView::where('created_at', '>=', $from)
->selectRaw('browser, COUNT(*) as cnt')
->groupBy('browser')
->orderByDesc('cnt')
->pluck('cnt', 'browser');
$pageTypeStats = PageView::where('created_at', '>=', $from)
->selectRaw('page_type, COUNT(*) as cnt')
->groupBy('page_type')
->orderByDesc('cnt')
->pluck('cnt', 'page_type');
// ── Coğrafi dağılım ───────────────────────────────────────────────────
$geoStats = PageView::where('created_at', '>=', $from)
->whereNotNull('city')
->selectRaw('city, country, COUNT(*) as cnt')
->groupBy('city', 'country')
->orderByDesc('cnt')
->limit(15)
->get(['city', 'country', DB::raw('COUNT(*) as cnt')]);
// ── En aktif kullanıcılar ─────────────────────────────────────────────
$activeUserIds = PageView::where('created_at', '>=', $from)
->whereNotNull('user_id')
->selectRaw('user_id, COUNT(*) as views, COUNT(DISTINCT DATE(created_at)) as days')
->groupBy('user_id')
->orderByDesc('views')
->limit(10)
->get();
$activeUserList = User::whereIn('id', $activeUserIds->pluck('user_id'))
->get(['id', 'name', 'email', 'created_at'])
->keyBy('id');
$activeUsers = $activeUserIds->map(fn($r) => [
'user' => $activeUserList[$r->user_id] ?? null,
'views' => $r->views,
'days' => $r->days,
])->filter(fn($r) => $r['user']);
// ── AI istatistikleri ─────────────────────────────────────────────────
$aiByType = AiQuery::where('created_at', '>=', $from)
->selectRaw('query_type, COUNT(*) as cnt')
->groupBy('query_type')
->orderByDesc('cnt')
->pluck('cnt', 'query_type');
$aiTopQuestions = AiQuery::where('created_at', '>=', $from)
->where('query_type', 'chat')
->whereNotNull('query_text')
->selectRaw('query_text, COUNT(*) as cnt')
->groupBy('query_text')
->orderByDesc('cnt')
->limit(10)
->get();
$aiByUser = AiQuery::where('created_at', '>=', $from)
->whereNotNull('user_id')
->selectRaw('user_id, COUNT(*) as cnt')
->groupBy('user_id')
->orderByDesc('cnt')
->limit(5)
->get();
$aiUserList = User::whereIn('id', $aiByUser->pluck('user_id'))->pluck('name', 'id');
$aiTopUsers = $aiByUser->map(fn($r) => [
'name' => $aiUserList[$r->user_id] ?? 'Bilinmiyor',
'cnt' => $r->cnt,
]);
// ── Referrer ─────────────────────────────────────────────────────────
$referrerRaw = PageView::where('created_at', '>=', $from)
->whereNotNull('referrer')
->where('referrer', '!=', '')
->selectRaw('referrer, COUNT(*) as cnt')
->groupBy('referrer')
->orderByDesc('cnt')
->limit(30)
->pluck('cnt', 'referrer');
$referrerStats = collect();
foreach ($referrerRaw as $url => $cnt) {
try {
$parsed = parse_url($url);
$domain = $parsed['host'] ?? $url;
$domain = preg_replace('/^www\./', '', $domain);
} catch (\Throwable) {
$domain = $url;
}
if ($referrerStats->has($domain)) {
$referrerStats[$domain] += $cnt;
} else {
$referrerStats[$domain] = $cnt;
}
}
$referrerStats = $referrerStats->sortDesc()->take(15);
$directTraffic = PageView::where('created_at', '>=', $from)
->where(fn($q) => $q->whereNull('referrer')->orWhere('referrer', ''))
->count();
// ── Bot istatistikleri ────────────────────────────────────────────────
$botViews = 0;
$humanViews = 0;
$botRatio = 0;
$botTopIps = collect();
$botByName = collect();
try {
$botViews = PageView::where('created_at', '>=', $from)->where('is_bot', 1)->count();
$humanViews = PageView::where('created_at', '>=', $from)->where('is_bot', 0)->count();
$botRatio = ($botViews + $humanViews) > 0 ? round($botViews / ($botViews + $humanViews) * 100) : 0;
$botTopIps = DB::table('analytics_bot_logs')
->where('created_at', '>=', $from)
->selectRaw('ip, COUNT(*) as cnt, MAX(user_agent) as ua, MAX(action) as action')
->groupBy('ip')
->orderByDesc('cnt')
->limit(15)
->get();
$botByName = DB::table('analytics_bot_logs')
->where('created_at', '>=', $from)
->selectRaw('bot_name, COUNT(*) as cnt, action')
->groupBy('bot_name', 'action')
->orderByDesc('cnt')
->limit(20)
->get();
} catch (\Exception) {}
// ── Oturum istatistikleri ─────────────────────────────────────────────
$sessions = collect();
$avgSessionTime = 0;
$avgPages = 0;
try {
$avgSessionTime = (int) DB::table('analytics_sessions')
->where('started_at', '>=', $from)
->where('is_bot', 0)
->avg('total_seconds');
$avgPages = round((float) DB::table('analytics_sessions')
->where('started_at', '>=', $from)
->where('is_bot', 0)
->avg('pages_visited'), 1);
$sessions = DB::table('analytics_sessions')
->where('started_at', '>=', $from)
->orderByDesc('started_at')
->limit(30)
->get();
} catch (\Exception) {}
return compact(
'totalViews', 'uniqueVisitors', 'watchSeconds', 'aiTotal', 'newUsers',
'viewsDelta', 'todayViews', 'yesterdayViews',
'trendLabels', 'trendData', 'watchTrendData',
'hourlyData',
'topAnimes', 'topEpisodes',
'deviceStats', 'browserStats', 'pageTypeStats',
'geoStats',
'activeUsers',
'aiByType', 'aiTopQuestions', 'aiTopUsers',
'referrerStats', 'directTraffic',
'botViews', 'humanViews', 'botRatio', 'botTopIps', 'botByName',
'sessions', 'avgSessionTime', 'avgPages'
);
}
public function blockIp(Request $request)
{
$data = $request->validate([
'ip' => 'required|ip',
'reason' => 'nullable|string|max:255',
'expires_at' => 'nullable|date|after:now',
]);
DB::table('blocked_ips')->updateOrInsert(
['ip' => $data['ip']],
[
'reason' => $data['reason'] ?? 'Manuel engel',
'auto_blocked' => 0,
'blocked_at' => now(),
'expires_at' => $data['expires_at'] ?? null,
]
);
\Illuminate\Support\Facades\Cache::forget('blocked_ip_' . $data['ip']);
return back()->with('success', $data['ip'] . ' engellendi.');
}
public function unblockIp(Request $request)
{
$ip = $request->input('ip');
DB::table('blocked_ips')->where('ip', $ip)->delete();
\Illuminate\Support\Facades\Cache::forget('blocked_ip_' . $ip);
return back()->with('success', $ip . ' engeli kaldırıldı.');
}
}