359 lines
16 KiB
PHP
359 lines
16 KiB
PHP
<?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ı.');
|
||
}
|
||
}
|