Initial commit: Animexe Laravel platform
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
<?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ı.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user