Files
animexe/app/Http/Controllers/Frontend/UserFeatureController.php
T
2026-07-14 00:01:48 +03:00

466 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
}
}
}