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')
@@ -77,7 +81,7 @@ @endif {{-- Add to Library --}} -
+
@if($userGame) {{-- Already in library --}} @@ -179,6 +183,46 @@
+ {{-- Add to List --}} + @auth +
+ + +
+ @else +
+ @endauth + {{-- Companies --}} @if($game->companies->isNotEmpty())
@@ -272,43 +316,137 @@ @endif {{-- Reviews --}} -
+

Reviews

- @if($game->reviews->isEmpty()) -

No reviews yet. Be the first!

- @else -
- @foreach($game->reviews->take(5) as $review) -
+
+ + {{-- Review List --}} +
+ @forelse($game->reviews->take(10) as $review) +
{{ strtoupper(substr($review->user->name, 0, 1)) }}
{{ $review->user->username ?? $review->user->name }} + @if($review->has_spoilers) + Spoilers + @endif
- @if($review->rating) -
- - - - {{ $review->rating }} - /10 +
+ @if($review->rating) +
+ + + + {{ $review->rating }} + /10 +
+ @endif + @auth + @if(auth()->id() === $review->user_id) + + @endif + @endauth
- @endif
- @if($review->body)

{{ $review->body }}

- @endif
- @endforeach + @empty +

No reviews yet. Be the first!

+ @endforelse
- @endif + + {{-- Write / Edit Review Form --}} + @auth +
+ +

+ +
+ +

+ +
+
+ + + /10 +
+ +
+ +
+ + + +
+
+ +
+

You've already reviewed this game.

+ + +
+
+ @else +
+

+ Log in + to write a review. +

+
+ @endauth + +
@push('scripts') + @endpush diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 9ea4d6b..f7b3d94 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -11,10 +11,12 @@ @endif - - + + + + @@ -42,15 +44,51 @@
- -