466 lines
17 KiB
PHP
466 lines
17 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Frontend;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\Anime;
|
||
use App\Models\Episode;
|
||
use App\Models\Watchlist;
|
||
use App\Models\EpisodeVote;
|
||
use App\Models\AnimeRating;
|
||
use App\Models\AnimeRequest;
|
||
use App\Models\AnimeRequestVote;
|
||
use App\Models\ContinueWatching;
|
||
use App\Models\UserAchievement;
|
||
use App\Models\AnimeFollow;
|
||
use App\Models\UserNotification;
|
||
use App\Models\EpisodeNote;
|
||
use App\Services\AchievementService;
|
||
use Illuminate\Http\Request;
|
||
|
||
class UserFeatureController extends Controller
|
||
{
|
||
// ── Watchlist ─────────────────────────────────────────────────────────────
|
||
|
||
public function watchlistIndex()
|
||
{
|
||
$items = Watchlist::where('user_id', auth()->id())
|
||
->with(['anime.genres'])
|
||
->orderByDesc('created_at')
|
||
->get()
|
||
->groupBy('status');
|
||
|
||
$continues = ContinueWatching::where('user_id', auth()->id())
|
||
->with(['anime', 'episode'])
|
||
->where('percent_complete', '<', 95)
|
||
->orderByDesc('updated_at')
|
||
->limit(20)
|
||
->get();
|
||
|
||
$achievements = UserAchievement::where('user_id', auth()->id())
|
||
->with('achievement')
|
||
->orderByDesc('earned_at')
|
||
->get();
|
||
|
||
return view('frontend.profile', compact('items', 'continues', 'achievements'));
|
||
}
|
||
|
||
public function watchlistToggle(Request $request, Anime $anime)
|
||
{
|
||
$this->requireAuth();
|
||
|
||
$status = $request->input('status', 'plan');
|
||
if (!array_key_exists($status, Watchlist::STATUSES)) {
|
||
$status = 'plan';
|
||
}
|
||
|
||
$existing = Watchlist::where('user_id', auth()->id())
|
||
->where('anime_id', $anime->id)
|
||
->first();
|
||
|
||
if ($existing) {
|
||
if ($existing->status === $status) {
|
||
$existing->delete();
|
||
$inList = false;
|
||
$newStatus = null;
|
||
} else {
|
||
$existing->update(['status' => $status]);
|
||
$inList = true;
|
||
$newStatus = $status;
|
||
}
|
||
} else {
|
||
Watchlist::create([
|
||
'user_id' => auth()->id(),
|
||
'anime_id' => $anime->id,
|
||
'status' => $status,
|
||
'created_at' => now(),
|
||
]);
|
||
$inList = true;
|
||
$newStatus = $status;
|
||
}
|
||
|
||
$newlyEarned = AchievementService::check(auth()->user());
|
||
|
||
return response()->json([
|
||
'in_list' => $inList,
|
||
'status' => $newStatus,
|
||
'status_label' => $newStatus ? (Watchlist::STATUSES[$newStatus] ?? '') : null,
|
||
'achievements' => array_map(fn($a) => ['title' => $a->title, 'icon' => $a->icon, 'color' => $a->color], $newlyEarned),
|
||
]);
|
||
}
|
||
|
||
// ── Watchlist Export ─────────────────────────────────────────────────────
|
||
|
||
public function watchlistExport(Request $request)
|
||
{
|
||
$user = auth()->user();
|
||
|
||
if (!$user->hasPerk('watchlist_export')) {
|
||
abort(403, 'Bu özellik için premium üyelik gerekiyor.');
|
||
}
|
||
|
||
$format = in_array($request->query('format'), ['csv', 'json']) ? $request->query('format') : 'json';
|
||
|
||
$items = Watchlist::where('user_id', $user->id)
|
||
->with('anime:id,title,mal_score,genres')
|
||
->orderBy('status')
|
||
->orderByDesc('created_at')
|
||
->get()
|
||
->map(fn($w) => [
|
||
'title' => $w->anime->title ?? '',
|
||
'status' => $w->status,
|
||
'added_at' => $w->created_at?->toDateString(),
|
||
'mal_score' => $w->anime->mal_score ?? null,
|
||
]);
|
||
|
||
if ($format === 'csv') {
|
||
$csv = "title,status,added_at,mal_score\n";
|
||
foreach ($items as $row) {
|
||
$csv .= '"' . str_replace('"', '""', $row['title']) . '",'
|
||
. $row['status'] . ','
|
||
. $row['added_at'] . ','
|
||
. $row['mal_score'] . "\n";
|
||
}
|
||
return response($csv, 200, [
|
||
'Content-Type' => 'text/csv; charset=utf-8',
|
||
'Content-Disposition' => 'attachment; filename="watchlist.csv"',
|
||
]);
|
||
}
|
||
|
||
return response()->json($items, 200, [
|
||
'Content-Disposition' => 'attachment; filename="watchlist.json"',
|
||
]);
|
||
}
|
||
|
||
// ── Episode Vote ──────────────────────────────────────────────────────────
|
||
|
||
public function episodeVote(Request $request, Episode $episode)
|
||
{
|
||
$this->requireAuth();
|
||
|
||
$vote = $request->input('vote') == 1 ? 1 : -1;
|
||
|
||
$existing = EpisodeVote::where('user_id', auth()->id())
|
||
->where('episode_id', $episode->id)
|
||
->first();
|
||
|
||
if ($existing) {
|
||
if ($existing->vote === $vote) {
|
||
$existing->delete(); // toggle off
|
||
} else {
|
||
$existing->update(['vote' => $vote]);
|
||
}
|
||
} else {
|
||
EpisodeVote::create([
|
||
'user_id' => auth()->id(),
|
||
'episode_id' => $episode->id,
|
||
'vote' => $vote,
|
||
'created_at' => now(),
|
||
]);
|
||
}
|
||
|
||
$likes = EpisodeVote::where('episode_id', $episode->id)->where('vote', 1)->count();
|
||
$dislikes = EpisodeVote::where('episode_id', $episode->id)->where('vote', -1)->count();
|
||
$myVote = EpisodeVote::where('user_id', auth()->id())->where('episode_id', $episode->id)->value('vote');
|
||
|
||
return response()->json([
|
||
'likes' => $likes,
|
||
'dislikes' => $dislikes,
|
||
'my_vote' => $myVote,
|
||
]);
|
||
}
|
||
|
||
// ── Anime Rating ─────────────────────────────────────────────────────────
|
||
|
||
public function animeRate(Request $request, Anime $anime)
|
||
{
|
||
$this->requireAuth();
|
||
|
||
$rating = (int) $request->input('rating');
|
||
if ($rating < 1 || $rating > 10) {
|
||
return response()->json(['error' => 'Geçersiz puan'], 422);
|
||
}
|
||
|
||
AnimeRating::updateOrCreate(
|
||
['user_id' => auth()->id(), 'anime_id' => $anime->id],
|
||
['rating' => $rating]
|
||
);
|
||
|
||
$avg = AnimeRating::where('anime_id', $anime->id)->avg('rating');
|
||
$count = AnimeRating::where('anime_id', $anime->id)->count();
|
||
|
||
// Anime tablosunu güncelle (ağırlıklı ortalama)
|
||
$anime->update(['rating' => round($avg, 1)]);
|
||
|
||
$newlyEarned = AchievementService::check(auth()->user());
|
||
|
||
return response()->json([
|
||
'avg' => round($avg, 1),
|
||
'count' => $count,
|
||
'my_rating' => $rating,
|
||
'achievements' => array_map(fn($a) => ['title' => $a->title, 'icon' => $a->icon, 'color' => $a->color], $newlyEarned),
|
||
]);
|
||
}
|
||
|
||
// ── Continue Watching (güncelleme) ───────────────────────────────────────
|
||
|
||
public function continueWatchingUpdate(Request $request)
|
||
{
|
||
if (!auth()->check()) {
|
||
return response()->json(['ok' => false]);
|
||
}
|
||
|
||
$data = $request->validate([
|
||
'anime_id' => 'required|integer',
|
||
'episode_id' => 'required|integer',
|
||
'season_number' => 'required|integer',
|
||
'episode_number' => 'required|integer',
|
||
'seconds' => 'required|integer|min:0',
|
||
'total' => 'nullable|integer|min:0',
|
||
'percent' => 'nullable|integer|min:0|max:100',
|
||
]);
|
||
|
||
$userId = auth()->id();
|
||
|
||
ContinueWatching::updateOrCreate(
|
||
['user_id' => $userId, 'anime_id' => $data['anime_id']],
|
||
[
|
||
'episode_id' => $data['episode_id'],
|
||
'season_number' => $data['season_number'],
|
||
'episode_number' => $data['episode_number'],
|
||
'seconds_watched' => $data['seconds'],
|
||
'total_seconds' => $data['total'] ?? 0,
|
||
'percent_complete'=> $data['percent'] ?? 0,
|
||
'updated_at' => now(),
|
||
]
|
||
);
|
||
|
||
// stream_history perki yoksa en eski kayıtları silerek 30 limiti uygula
|
||
if (!auth()->user()->hasPerk('stream_history')) {
|
||
$count = ContinueWatching::where('user_id', $userId)->count();
|
||
if ($count > 30) {
|
||
$idsToDelete = ContinueWatching::where('user_id', $userId)
|
||
->orderBy('updated_at')
|
||
->limit($count - 30)
|
||
->pluck('id');
|
||
ContinueWatching::whereIn('id', $idsToDelete)->delete();
|
||
}
|
||
}
|
||
|
||
// Başarım kontrolü (her 5 bölümde bir — performans için)
|
||
if ($data['seconds'] % 300 < 35) {
|
||
AchievementService::check(auth()->user());
|
||
}
|
||
|
||
return response()->json(['ok' => true]);
|
||
}
|
||
|
||
// ── Anime İsteği ─────────────────────────────────────────────────────────
|
||
|
||
public function requestIndex()
|
||
{
|
||
$requests = AnimeRequest::withCount('votes')
|
||
->whereIn('status', ['pending', 'approved', 'added'])
|
||
->orderByDesc('vote_count')
|
||
->orderByDesc('created_at')
|
||
->paginate(20);
|
||
|
||
$myRequests = auth()->check()
|
||
? AnimeRequest::where('user_id', auth()->id())->orderByDesc('id')->limit(5)->get()
|
||
: collect();
|
||
|
||
$votedIds = [];
|
||
if (auth()->check()) {
|
||
$votedIds = AnimeRequestVote::where('user_id', auth()->id())
|
||
->pluck('anime_request_id')->toArray();
|
||
}
|
||
|
||
return view('frontend.anime-request', compact('requests', 'myRequests', 'votedIds'));
|
||
}
|
||
|
||
public function requestStore(Request $request)
|
||
{
|
||
$this->requireAuth();
|
||
|
||
$data = $request->validate([
|
||
'title' => 'required|string|max:200',
|
||
'original_title' => 'nullable|string|max:200',
|
||
'note' => 'nullable|string|max:1000',
|
||
]);
|
||
|
||
// Benzer istek var mı?
|
||
$existing = AnimeRequest::whereRaw('LOWER(title) = ?', [strtolower($data['title'])])->first();
|
||
if ($existing) {
|
||
// Oy ekle
|
||
$voted = AnimeRequestVote::where('anime_request_id', $existing->id)
|
||
->where('user_id', auth()->id())
|
||
->exists();
|
||
if (!$voted) {
|
||
AnimeRequestVote::create(['anime_request_id' => $existing->id, 'user_id' => auth()->id(), 'created_at' => now()]);
|
||
$existing->increment('vote_count');
|
||
}
|
||
return response()->json(['ok' => true, 'merged' => true, 'request_id' => $existing->id, 'vote_count' => $existing->fresh()->vote_count]);
|
||
}
|
||
|
||
$req = AnimeRequest::create([
|
||
'user_id' => auth()->id(),
|
||
'title' => $data['title'],
|
||
'original_title' => $data['original_title'] ?? null,
|
||
'note' => $data['note'] ?? null,
|
||
'status' => 'pending',
|
||
'vote_count' => 1,
|
||
]);
|
||
|
||
AnimeRequestVote::create(['anime_request_id' => $req->id, 'user_id' => auth()->id(), 'created_at' => now()]);
|
||
|
||
AchievementService::check(auth()->user());
|
||
|
||
return response()->json(['ok' => true, 'merged' => false, 'request_id' => $req->id, 'vote_count' => 1]);
|
||
}
|
||
|
||
public function requestVote(AnimeRequest $animeRequest)
|
||
{
|
||
$this->requireAuth();
|
||
|
||
$voted = AnimeRequestVote::where('anime_request_id', $animeRequest->id)
|
||
->where('user_id', auth()->id())
|
||
->exists();
|
||
|
||
if ($voted) {
|
||
AnimeRequestVote::where('anime_request_id', $animeRequest->id)
|
||
->where('user_id', auth()->id())
|
||
->delete();
|
||
$animeRequest->decrement('vote_count');
|
||
$isVoted = false;
|
||
} else {
|
||
AnimeRequestVote::create(['anime_request_id' => $animeRequest->id, 'user_id' => auth()->id(), 'created_at' => now()]);
|
||
$animeRequest->increment('vote_count');
|
||
$isVoted = true;
|
||
}
|
||
|
||
return response()->json(['ok' => true, 'voted' => $isVoted, 'vote_count' => $animeRequest->fresh()->vote_count]);
|
||
}
|
||
|
||
// ── Anime Takip ──────────────────────────────────────────────────────────
|
||
|
||
public function followToggle(Anime $anime)
|
||
{
|
||
$this->requireAuth();
|
||
$userId = auth()->id();
|
||
|
||
$existing = AnimeFollow::where('user_id', $userId)->where('anime_id', $anime->id)->first();
|
||
|
||
if ($existing) {
|
||
$existing->delete();
|
||
$following = false;
|
||
} else {
|
||
AnimeFollow::create(['user_id' => $userId, 'anime_id' => $anime->id]);
|
||
$following = true;
|
||
}
|
||
|
||
$count = AnimeFollow::where('anime_id', $anime->id)->count();
|
||
|
||
return response()->json(['following' => $following, 'count' => $count]);
|
||
}
|
||
|
||
// ── Bildirimler ───────────────────────────────────────────────────────────
|
||
|
||
public function notificationsIndex()
|
||
{
|
||
$this->requireAuth();
|
||
|
||
$notifications = UserNotification::where('user_id', auth()->id())
|
||
->orderByDesc('created_at')
|
||
->paginate(30);
|
||
|
||
// Görüntülenince hepsini okundu yap
|
||
UserNotification::where('user_id', auth()->id())
|
||
->whereNull('read_at')
|
||
->update(['read_at' => now()]);
|
||
|
||
return view('frontend.notifications', compact('notifications'));
|
||
}
|
||
|
||
public function notificationsCount()
|
||
{
|
||
if (!auth()->check()) {
|
||
return response()->json(['count' => 0]);
|
||
}
|
||
$count = UserNotification::where('user_id', auth()->id())->whereNull('read_at')->count();
|
||
return response()->json(['count' => $count]);
|
||
}
|
||
|
||
// ── Bölüm Notları ─────────────────────────────────────────────────────────
|
||
|
||
public function noteStore(Request $request, Episode $episode)
|
||
{
|
||
$this->requireAuth();
|
||
|
||
$data = $request->validate([
|
||
'content' => 'required|string|max:500',
|
||
'timestamp_at' => 'nullable|integer|min:0',
|
||
]);
|
||
|
||
$note = EpisodeNote::create([
|
||
'user_id' => auth()->id(),
|
||
'episode_id' => $episode->id,
|
||
'anime_id' => $episode->anime_id,
|
||
'content' => $data['content'],
|
||
'timestamp_at' => $data['timestamp_at'] ?? null,
|
||
]);
|
||
|
||
return response()->json([
|
||
'ok' => true,
|
||
'note' => [
|
||
'id' => $note->id,
|
||
'content' => $note->content,
|
||
'timestamp_label' => $note->timestamp_label,
|
||
'timestamp_at' => $note->timestamp_at,
|
||
'created_at' => $note->created_at->format('d.m.Y H:i'),
|
||
],
|
||
]);
|
||
}
|
||
|
||
public function noteDelete(EpisodeNote $note)
|
||
{
|
||
$this->requireAuth();
|
||
|
||
if ($note->user_id !== auth()->id()) {
|
||
abort(403);
|
||
}
|
||
|
||
$note->delete();
|
||
|
||
return response()->json(['ok' => true]);
|
||
}
|
||
|
||
public function episodeNotesList(Episode $episode)
|
||
{
|
||
$this->requireAuth();
|
||
|
||
$notes = EpisodeNote::where('user_id', auth()->id())
|
||
->where('episode_id', $episode->id)
|
||
->orderBy('timestamp_at')
|
||
->orderBy('created_at')
|
||
->get()
|
||
->map(fn($n) => [
|
||
'id' => $n->id,
|
||
'content' => $n->content,
|
||
'timestamp_label' => $n->timestamp_label,
|
||
'timestamp_at' => $n->timestamp_at,
|
||
'created_at' => $n->created_at->format('d.m.Y H:i'),
|
||
]);
|
||
|
||
return response()->json(['notes' => $notes]);
|
||
}
|
||
|
||
// ── Helper ───────────────────────────────────────────────────────────────
|
||
|
||
private function requireAuth()
|
||
{
|
||
if (!auth()->check()) {
|
||
abort(401);
|
||
}
|
||
}
|
||
}
|