Initial commit: Animexe Laravel platform

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 00:01:48 +03:00
co-authored by Claude Opus 4.8
commit a63515cfc6
366 changed files with 74773 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Ad;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class AdController extends Controller
{
public function index()
{
$ads = Ad::orderByDesc('created_at')->get();
$settings = [
'vad_enabled' => Setting::get('vad_enabled', '0'),
'vad_freq_episodes' => Setting::get('vad_freq_episodes', 2),
'vad_freq_minutes' => Setting::get('vad_freq_minutes', 5),
'vad_upsell_percent' => Setting::get('vad_upsell_percent', 20),
'banner_ads_enabled' => Setting::get('banner_ads_enabled', '0'),
];
$stats = [
'total_impressions' => $ads->sum('impressions'),
'total_clicks' => $ads->sum('clicks'),
'avg_ctr' => $ads->sum('impressions') > 0
? round($ads->sum('clicks') / $ads->sum('impressions') * 100, 2) : 0,
'active_count' => $ads->where('is_active', true)->count(),
];
return view('admin.ads.index', compact('ads', 'settings', 'stats'));
}
public function store(Request $request)
{
$data = $this->validateAd($request);
if ($request->hasFile('media_file')) {
$data['file_path'] = $this->storeMedia($request->file('media_file'));
}
unset($data['media_file']);
Ad::create($data);
return back()->with('success', 'Reklam eklendi.');
}
public function edit(Ad $ad)
{
return view('admin.ads.edit', compact('ad'));
}
public function update(Request $request, Ad $ad)
{
$data = $this->validateAd($request, $ad);
if ($request->hasFile('media_file')) {
$newPath = $this->storeMedia($request->file('media_file'));
if ($ad->file_path) Storage::disk('public')->delete($ad->file_path);
$data['file_path'] = $newPath;
}
unset($data['media_file']);
$ad->update($data);
return redirect()->route('admin.ads.index')->with('success', 'Reklam güncellendi.');
}
public function destroy(Ad $ad)
{
if ($ad->file_path) Storage::disk('public')->delete($ad->file_path);
$ad->delete();
return back()->with('success', 'Reklam silindi.');
}
public function toggle(Ad $ad)
{
$ad->update(['is_active' => !$ad->is_active]);
return back()->with('success', $ad->is_active ? 'Reklam aktifleştirildi.' : 'Reklam durduruldu.');
}
public function saveSettings(Request $request)
{
Setting::set('vad_enabled', $request->boolean('vad_enabled') ? '1' : '0', 'ads');
Setting::set('vad_freq_episodes', max(1, (int) $request->input('vad_freq_episodes', 2)), 'ads');
Setting::set('vad_freq_minutes', max(1, (int) $request->input('vad_freq_minutes', 5)), 'ads');
Setting::set('vad_upsell_percent', min(100, max(0, (int) $request->input('vad_upsell_percent', 20))), 'ads');
Setting::set('banner_ads_enabled', $request->boolean('banner_ads_enabled') ? '1' : '0', 'ads');
return back()->with('success', 'Reklam ayarları kaydedildi.');
}
private function validateAd(Request $request, ?Ad $existing = null): array
{
$type = $request->input('type', 'video');
// Sunucu upload limitini aşan dosya: PHP boş/bozuk upload gönderir.
// Sessizce medyasız reklam kaydetmek yerine net hata ver.
$this->guardUploadError($request);
// Yüklenmiş dosya da dış URL de yoksa reklam gösterilemez (media_url null olur).
// Düzenlemede mevcut dosya varsa yeniden yükleme zorunlu değil.
$hasExisting = $existing?->file_path || $existing?->external_url;
$needsMedia = !$request->hasFile('media_file') && !$hasExisting;
$data = $request->validate([
'name' => 'required|string|max:120',
'type' => 'required|in:video,banner',
'placement' => 'required|in:preroll,home_mid,home_bottom',
'media_file' => [
'nullable', 'file',
$type === 'video' ? 'mimes:mp4,m4v' : 'mimes:jpg,jpeg,png,webp,gif',
$type === 'video' ? 'max:102400' : 'max:20480', // video 100MB, görsel/gif 20MB
],
'external_url' => [$needsMedia ? 'required' : 'nullable', 'nullable', 'url', 'max:2000'],
'click_url' => 'nullable|url|max:2000',
'skip_after' => 'required|integer|min:0|max:60',
'weight' => 'required|integer|min:1|max:100',
'is_active' => 'boolean',
'starts_at' => 'nullable|date',
'ends_at' => 'nullable|date|after:starts_at',
], [
'external_url.required' => 'Bir medya dosyası yükleyin veya dış URL girin. '
. 'Dosya seçtiyseniz sunucu yükleme limitini aşmış olabilir (maks. '
. ini_get('upload_max_filesize') . ').',
'media_file.mimes' => $type === 'video'
? 'Video dosyası MP4 formatında olmalı.'
: 'Görsel JPG, PNG, WebP veya GIF formatında olmalı.',
'media_file.max' => 'Dosya çok büyük.',
]);
// Checkbox işaretli değilse request'te hiç gelmez — açıkça boolean'a çevir
$data['is_active'] = $request->boolean('is_active');
return $data;
}
/** PHP upload hatalarını (limit aşımı, kısmi yükleme) net mesajla yüzeye çıkar. */
private function guardUploadError(Request $request): void
{
$file = $request->file('media_file');
if (!$file || $file->isValid()) {
return;
}
$msg = match ($file->getError()) {
UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE =>
'Dosya sunucunun yükleme limitini aşıyor (maks. ' . ini_get('upload_max_filesize')
. '). Daha küçük bir dosya seçin veya hosting limitini yükseltin.',
UPLOAD_ERR_PARTIAL => 'Dosya yalnızca kısmen yüklendi, lütfen tekrar deneyin.',
UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE =>
'Sunucu dosyayı geçici klasöre yazamadı. Hosting sağlayıcınıza bildirin.',
default => 'Dosya yüklenemedi (hata kodu: ' . $file->getError() . ').',
};
throw \Illuminate\Validation\ValidationException::withMessages(['media_file' => $msg]);
}
/** Dosyayı public diske yaz ve tam yazıldığını doğrula. */
private function storeMedia(\Illuminate\Http\UploadedFile $file): string
{
// NOT: klasör adı bilerek nötr ('ads' değil) — adblocker /media/ads/ yolunu
// ERR_BLOCKED_BY_CLIENT ile engelliyor. 'content' engellenmez.
$expected = $file->getSize();
$path = $file->store('content', 'public');
if (!$path || !Storage::disk('public')->exists($path)) {
throw \Illuminate\Validation\ValidationException::withMessages([
'media_file' => 'Dosya sunucuya kaydedilemedi. storage/app/public klasörünün yazma izni olduğundan emin olun.',
]);
}
// Kısmi yazma (disk dolu / kesilen upload) sessizce bozuk reklam bırakmasın
$written = Storage::disk('public')->size($path);
if ($expected > 0 && $written !== $expected) {
Storage::disk('public')->delete($path);
throw \Illuminate\Validation\ValidationException::withMessages([
'media_file' => "Dosya eksik yüklendi ({$written}/{$expected} byte). Tekrar deneyin.",
]);
}
return $path;
}
}