228 lines
8.5 KiB
PHP
228 lines
8.5 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Admin;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\Anime;
|
||
use App\Models\Episode;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
class HealthController extends Controller
|
||
{
|
||
public function index()
|
||
{
|
||
// 1. Duplike anime — aynı MAL ID'ye sahip birden fazla anime
|
||
$malDuplicates = DB::table('animes')
|
||
->whereNotNull('mal_id')
|
||
->where('mal_id', '>', 0)
|
||
->select('mal_id', DB::raw('COUNT(*) as cnt'))
|
||
->groupBy('mal_id')
|
||
->having('cnt', '>', 1)
|
||
->get()
|
||
->map(function ($row) {
|
||
$animes = Anime::where('mal_id', $row->mal_id)
|
||
->withCount('episodes')
|
||
->get(['id', 'title', 'slug', 'mal_id', 'created_at']);
|
||
return ['mal_id' => $row->mal_id, 'animes' => $animes];
|
||
});
|
||
|
||
// 2. Karışık kaynak — aynı anime içinde hem animecix hem anizium bölüm var
|
||
$mixedSources = DB::table('episodes')
|
||
->whereIn('source', ['anizium', 'animecix'])
|
||
->whereNotNull('anime_id')
|
||
->select('anime_id', 'source', DB::raw('COUNT(*) as cnt'))
|
||
->groupBy('anime_id', 'source')
|
||
->get()
|
||
->groupBy('anime_id')
|
||
->filter(fn($group) => $group->pluck('source')->unique()->count() > 1)
|
||
->map(function ($group) {
|
||
$anime = Anime::find($group->first()->anime_id, ['id', 'title', 'slug']);
|
||
if (!$anime) return null;
|
||
$sources = $group->mapWithKeys(fn($r) => [$r->source => $r->cnt]);
|
||
return ['anime' => $anime, 'sources' => $sources];
|
||
})
|
||
->filter()
|
||
->values();
|
||
|
||
// 3. Eksik bölümler — episode_count > gerçek bölüm sayısı
|
||
$missingEpisodes = Anime::whereNotNull('episode_count')
|
||
->where('episode_count', '>', 0)
|
||
->withCount('episodes')
|
||
->get(['id', 'title', 'slug', 'episode_count'])
|
||
->filter(fn($a) => $a->episodes_count < $a->episode_count)
|
||
->map(fn($a) => [
|
||
'id' => $a->id,
|
||
'title' => $a->title,
|
||
'slug' => $a->slug,
|
||
'expected' => $a->episode_count,
|
||
'actual' => $a->episodes_count,
|
||
'missing' => $a->episode_count - $a->episodes_count,
|
||
])
|
||
->sortByDesc('missing')
|
||
->values();
|
||
|
||
// 4. Harici CDN bölümler — BunnyCDN'e taşınmamış, Anizium CDN'de kalan
|
||
$externalCount = Episode::whereNull('video_url')
|
||
->where(function ($q) {
|
||
$q->where('m3u8_url', 'like', '%aniziumserver%')
|
||
->orWhere('m3u8_url', 'like', '%anizium%');
|
||
})
|
||
->count();
|
||
|
||
$externalSample = Episode::whereNull('video_url')
|
||
->where(function ($q) {
|
||
$q->where('m3u8_url', 'like', '%aniziumserver%')
|
||
->orWhere('m3u8_url', 'like', '%anizium%');
|
||
})
|
||
->with('anime:id,title,slug')
|
||
->select('id', 'anime_id', 'season_id', 'episode_number', 'm3u8_url', 'source')
|
||
->orderByDesc('id')
|
||
->limit(100)
|
||
->get();
|
||
|
||
// 5. Sıfır bölümlü animeler
|
||
$zeroEpisodeAnimes = Anime::whereDoesntHave('episodes')
|
||
->get(['id', 'title', 'slug', 'created_at']);
|
||
|
||
return view('admin.health.index', compact(
|
||
'malDuplicates',
|
||
'mixedSources',
|
||
'missingEpisodes',
|
||
'externalCount',
|
||
'externalSample',
|
||
'zeroEpisodeAnimes'
|
||
));
|
||
}
|
||
|
||
// ── Sistem Temizliği ─────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Depolama istatistiklerini döndür — inode tüketimini gösterir.
|
||
*/
|
||
public function storageStats()
|
||
{
|
||
$dirs = [
|
||
'seg_cache' => storage_path('app/seg_cache'),
|
||
'cache_data' => storage_path('framework/cache/data'),
|
||
'sessions' => storage_path('framework/sessions'),
|
||
'views' => storage_path('framework/views'),
|
||
'logs' => storage_path('logs'),
|
||
'app_public' => storage_path('app/public'),
|
||
];
|
||
|
||
$stats = [];
|
||
foreach ($dirs as $key => $path) {
|
||
if (!is_dir($path)) {
|
||
$stats[$key] = ['count' => 0, 'size' => 0, 'path' => $path];
|
||
continue;
|
||
}
|
||
$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS));
|
||
$count = 0;
|
||
$size = 0;
|
||
foreach ($files as $f) {
|
||
$count++;
|
||
$size += $f->getSize();
|
||
}
|
||
$stats[$key] = ['count' => $count, 'size' => $size, 'path' => $path];
|
||
}
|
||
|
||
return response()->json(['stats' => $stats, 'total_files' => array_sum(array_column($stats, 'count'))]);
|
||
}
|
||
|
||
/**
|
||
* Belirtilen depolama dizinini temizle.
|
||
*/
|
||
public function cleanupStorage(Request $request)
|
||
{
|
||
$target = $request->input('target');
|
||
$allowed = [
|
||
'seg_cache' => storage_path('app/seg_cache'),
|
||
'cache_data' => storage_path('framework/cache/data'),
|
||
'sessions' => storage_path('framework/sessions'),
|
||
'views' => storage_path('framework/views'),
|
||
'old_logs' => storage_path('logs'),
|
||
];
|
||
|
||
if (!array_key_exists($target, $allowed)) {
|
||
return response()->json(['error' => 'Geçersiz hedef.'], 422);
|
||
}
|
||
|
||
$path = $allowed[$target];
|
||
$deleted = 0;
|
||
|
||
if (!is_dir($path)) {
|
||
return response()->json(['ok' => true, 'deleted' => 0, 'message' => 'Dizin yok.']);
|
||
}
|
||
|
||
if ($target === 'old_logs') {
|
||
// Logları tamamen silme — sadece 7 günden eskilerini sil
|
||
foreach (glob($path . '/*.log') ?: [] as $f) {
|
||
if (filemtime($f) < time() - 604800) { // 7 gün
|
||
@unlink($f);
|
||
$deleted++;
|
||
}
|
||
}
|
||
// Laravel her gün yeni log açar, bugünküne dokunma
|
||
} else {
|
||
// Diğer dizinler: tümünü temizle
|
||
$files = new \RecursiveIteratorIterator(
|
||
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
|
||
\RecursiveIteratorIterator::CHILD_FIRST
|
||
);
|
||
foreach ($files as $f) {
|
||
if ($f->isFile()) {
|
||
@unlink($f->getRealPath());
|
||
$deleted++;
|
||
} elseif ($f->isDir()) {
|
||
@rmdir($f->getRealPath());
|
||
}
|
||
}
|
||
}
|
||
|
||
// Laravel cache'i PHP seviyesinde de temizle
|
||
if ($target === 'cache_data') {
|
||
try { \Illuminate\Support\Facades\Cache::flush(); } catch (\Throwable) {}
|
||
}
|
||
|
||
return response()->json([
|
||
'ok' => true,
|
||
'deleted' => $deleted,
|
||
'message' => "{$deleted} dosya silindi.",
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Session driver bilgisi + önerisi.
|
||
*/
|
||
public function sessionInfo()
|
||
{
|
||
$driver = config('session.driver', 'file');
|
||
$sessionPath = storage_path('framework/sessions');
|
||
$sessionCount = is_dir($sessionPath) ? count(glob($sessionPath . '/*') ?: []) : 0;
|
||
|
||
return response()->json([
|
||
'driver' => $driver,
|
||
'session_files' => $sessionCount,
|
||
'recommendation'=> $driver === 'file'
|
||
? 'SESSION_DRIVER=database veya cookie kullanmanız önerilir (inode tasarrufu).'
|
||
: 'Session sürücüsü inode-dostu.',
|
||
]);
|
||
}
|
||
|
||
public function deleteAnime(Request $request, Anime $anime)
|
||
{
|
||
$title = $anime->title;
|
||
$anime->delete();
|
||
return back()->with('success', "\"$title\" silindi.");
|
||
}
|
||
|
||
public function deleteSourceEpisodes(Request $request, Anime $anime)
|
||
{
|
||
$source = $request->validate(['source' => 'required|in:anizium,animecix'])['source'];
|
||
$count = Episode::where('anime_id', $anime->id)->where('source', $source)->count();
|
||
Episode::where('anime_id', $anime->id)->where('source', $source)->delete();
|
||
return back()->with('success', "$anime->title — $source kaynağından $count bölüm silindi.");
|
||
}
|
||
}
|