diff --git a/app/Http/Controllers/GameController.php b/app/Http/Controllers/GameController.php index f1d3987..96e252c 100644 --- a/app/Http/Controllers/GameController.php +++ b/app/Http/Controllers/GameController.php @@ -44,12 +44,16 @@ class GameController extends Controller { $game->load(['genres', 'platforms', 'companies', 'screenshots', 'reviews.user']); - $userGame = null; + $userGame = null; + $userReview = null; + $userLists = collect(); if (auth()->check()) { - $userGame = $game->userGames()->where('user_id', auth()->id())->first(); + $userGame = $game->userGames()->where('user_id', auth()->id())->first(); + $userReview = $game->reviews()->where('user_id', auth()->id())->first(); + $userLists = auth()->user()->lists()->get(['id', 'title']); } - return view('games.show', compact('game', 'userGame')); + return view('games.show', compact('game', 'userGame', 'userReview', 'userLists')); } public function search(Request $request) diff --git a/app/Http/Controllers/ListController.php b/app/Http/Controllers/ListController.php new file mode 100644 index 0000000..252bfa8 --- /dev/null +++ b/app/Http/Controllers/ListController.php @@ -0,0 +1,91 @@ +middleware('auth')->except('show'); + } + + public function index() + { + $lists = auth()->user()->lists()->withCount('listGames')->latest()->get(); + return view('lists.index', compact('lists')); + } + + public function store(Request $request) + { + $data = $request->validate([ + 'title' => 'required|string|max:100', + 'description' => 'nullable|string|max:1000', + 'is_public' => 'boolean', + ]); + + $slug = Str::slug($data['title']); + $base = $slug; + $i = 1; + while (auth()->user()->lists()->where('slug', $slug)->exists()) { + $slug = $base . '-' . $i++; + } + + $list = UserList::create([ + 'user_id' => auth()->id(), + 'title' => $data['title'], + 'slug' => $slug, + 'description' => $data['description'] ?? null, + 'is_public' => $data['is_public'] ?? true, + ]); + + return redirect()->route('lists.show', $list)->with('success', 'List created!'); + } + + public function show(UserList $list) + { + if (!$list->is_public && (!auth()->check() || auth()->id() !== $list->user_id)) { + abort(403); + } + + $list->load(['listGames.game', 'user']); + + return view('lists.show', compact('list')); + } + + public function addGame(Request $request, UserList $list) + { + $this->authorize('manage', $list); + + $data = $request->validate(['game_id' => 'required|exists:games,id']); + + UserListGame::firstOrCreate([ + 'user_list_id' => $list->id, + 'game_id' => $data['game_id'], + ], ['order' => $list->listGames()->count()]); + + return response()->json(['success' => true]); + } + + public function removeGame(UserList $list, Game $game) + { + $this->authorize('manage', $list); + + $list->listGames()->where('game_id', $game->id)->delete(); + + return response()->json(['success' => true]); + } + + public function destroy(UserList $list) + { + $this->authorize('manage', $list); + $list->delete(); + + return redirect()->route('lists.index')->with('success', 'List deleted.'); + } +} diff --git a/app/Http/Controllers/ReviewController.php b/app/Http/Controllers/ReviewController.php new file mode 100644 index 0000000..50305ee --- /dev/null +++ b/app/Http/Controllers/ReviewController.php @@ -0,0 +1,60 @@ +authorize('create', [Review::class, $game]); + + if ($game->reviews()->where('user_id', auth()->id())->exists()) { + return response()->json(['message' => 'You have already reviewed this game.'], 409); + } + + $data = $request->validate([ + 'body' => 'required|string|min:50', + 'rating' => 'nullable|integer|min:1|max:10', + 'has_spoilers' => 'boolean', + ]); + + $review = Review::create([ + 'user_id' => auth()->id(), + 'game_id' => $game->id, + 'body' => $data['body'], + 'rating' => $data['rating'] ?? null, + 'has_spoilers' => $data['has_spoilers'] ?? false, + ]); + + $review->load('user'); + + return response()->json(['success' => true, 'review' => $review]); + } + + public function update(Request $request, Review $review) + { + $this->authorize('update', $review); + + $data = $request->validate([ + 'body' => 'required|string|min:50', + 'rating' => 'nullable|integer|min:1|max:10', + 'has_spoilers' => 'boolean', + ]); + + $review->update($data); + + return response()->json(['success' => true, 'review' => $review]); + } + + public function destroy(Review $review) + { + $this->authorize('delete', $review); + $review->delete(); + + return response()->json(['success' => true]); + } +} diff --git a/app/Models/Review.php b/app/Models/Review.php index 7cc30ce..495fb36 100644 --- a/app/Models/Review.php +++ b/app/Models/Review.php @@ -1,13 +1,29 @@ 'boolean']; + protected $fillable = ['user_id', 'game_id', 'body', 'rating', 'has_spoilers']; + + protected $casts = ['has_spoilers' => 'boolean']; + + protected static function boot(): void + { + parent::boot(); + + static::saved(function (Review $review) { + $review->game->recalculateRating(); + }); + + static::deleted(function (Review $review) { + $review->game->recalculateRating(); + }); + } public function user(): BelongsTo { return $this->belongsTo(User::class); } public function game(): BelongsTo { return $this->belongsTo(Game::class); } diff --git a/app/Models/UserList.php b/app/Models/UserList.php index facb4d4..4f5d8c1 100644 --- a/app/Models/UserList.php +++ b/app/Models/UserList.php @@ -3,8 +3,31 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Str; class UserList extends Model { - // + protected $fillable = ['user_id', 'title', 'slug', 'description', 'is_public']; + + protected $casts = ['is_public' => 'boolean']; + + protected static function boot(): void + { + parent::boot(); + + static::creating(function (UserList $list) { + if (empty($list->slug)) { + $list->slug = Str::slug($list->title); + } + }); + } + + public function user(): BelongsTo { return $this->belongsTo(User::class); } + + public function listGames(): HasMany + { + return $this->hasMany(UserListGame::class, 'user_list_id')->orderBy('order'); + } } diff --git a/app/Models/UserListGame.php b/app/Models/UserListGame.php index 86edefe..5385e46 100644 --- a/app/Models/UserListGame.php +++ b/app/Models/UserListGame.php @@ -3,8 +3,12 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; class UserListGame extends Model { - // + protected $fillable = ['user_list_id', 'game_id', 'order', 'notes']; + + public function list(): BelongsTo { return $this->belongsTo(UserList::class, 'user_list_id'); } + public function game(): BelongsTo { return $this->belongsTo(Game::class); } } diff --git a/app/Policies/ListPolicy.php b/app/Policies/ListPolicy.php new file mode 100644 index 0000000..e193a8e --- /dev/null +++ b/app/Policies/ListPolicy.php @@ -0,0 +1,14 @@ +id === $list->user_id; + } +} diff --git a/app/Policies/ReviewPolicy.php b/app/Policies/ReviewPolicy.php new file mode 100644 index 0000000..6e96976 --- /dev/null +++ b/app/Policies/ReviewPolicy.php @@ -0,0 +1,25 @@ +library()->where('game_id', $game->id)->exists(); + } + + public function update(User $user, Review $review): bool + { + return $user->id === $review->user_id; + } + + public function delete(User $user, Review $review): bool + { + return $user->id === $review->user_id; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 107d503..0946796 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -23,5 +23,7 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { Gate::policy(UserGame::class, UserGamePolicy::class); + Gate::policy(\App\Models\Review::class, \App\Policies\ReviewPolicy::class); + Gate::policy(\App\Models\UserList::class, \App\Policies\ListPolicy::class); } } diff --git a/resources/views/games/show.blade.php b/resources/views/games/show.blade.php index 647452a..615283e 100644 --- a/resources/views/games/show.blade.php +++ b/resources/views/games/show.blade.php @@ -3,6 +3,10 @@ @section('title', $game->title . ' — GameLog') @section('description', $game->summary ? Str::limit($game->summary, 155) : 'Track ' . $game->title . ' on GameLog.') +@section('og_title', $game->title . ' — GameLog') +@section('og_description', $game->summary ? Str::limit($game->summary, 155) : 'Track ' . $game->title . ' on GameLog.') +@section('og_image', $game->cover_url ?? asset('images/og-default.jpg')) +@section('og_type', 'game') @section('content')
No reviews yet. Be the first!
- @else -{{ $review->body }}
- @endifNo reviews yet. Be the first!
+ @endforelseYou've already reviewed this game.
+ + ++ Log in + to write a review. +
+