diff --git a/app/Http/Controllers/CompareController.php b/app/Http/Controllers/CompareController.php new file mode 100644 index 0000000..3bb3756 --- /dev/null +++ b/app/Http/Controllers/CompareController.php @@ -0,0 +1,62 @@ +id() === $user->id) { + return redirect()->route('users.show', $user->username); + } + + $myGames = auth()->user()->library()->with('game')->get()->keyBy('game_id'); + $theirGames = $user->library()->with('game')->get()->keyBy('game_id'); + + $myIds = $myGames->keys()->values(); + $theirIds = $theirGames->keys()->values(); + + $bothIds = $myIds->intersect($theirIds)->values(); + $theyHaveNotYouIds = $theirIds->diff($myIds)->values(); + $youHaveNotThemIds = $myIds->diff($theirIds)->values(); + + $both = $bothIds->map(fn($id) => [ + 'game' => $myGames[$id]->game, + 'myEntry' => $myGames[$id], + 'theirEntry' => $theirGames[$id], + ])->filter(fn($item) => $item['game'] !== null)->values(); + + $theyHaveNotYou = $theyHaveNotYouIds + ->map(fn($id) => $theirGames[$id]) + ->filter(fn($ug) => $ug->game !== null) + ->values(); + + $youHaveNotThem = $youHaveNotThemIds + ->map(fn($id) => $myGames[$id]) + ->filter(fn($ug) => $ug->game !== null) + ->values(); + + // Average rating diff on games both rated + $diffSum = 0; + $diffCount = 0; + foreach ($bothIds as $id) { + $myRating = $myGames[$id]->rating; + $theirRating = $theirGames[$id]->rating; + if ($myRating !== null && $theirRating !== null) { + $diffSum += abs($myRating - $theirRating); + $diffCount++; + } + } + + $stats = [ + 'both_count' => $bothIds->count(), + 'they_not_you' => $theyHaveNotYouIds->count(), + 'you_not_them' => $youHaveNotThemIds->count(), + 'avg_rating_diff' => $diffCount > 0 ? round($diffSum / $diffCount, 1) : null, + ]; + + return view('compare.show', compact('user', 'both', 'theyHaveNotYou', 'youHaveNotThem', 'stats')); + } +} diff --git a/app/Http/Controllers/GameCommentController.php b/app/Http/Controllers/GameCommentController.php new file mode 100644 index 0000000..cda3f17 --- /dev/null +++ b/app/Http/Controllers/GameCommentController.php @@ -0,0 +1,49 @@ +validate([ + 'body' => 'required|string|max:1000', + 'parent_id' => 'nullable|integer|exists:game_comments,id', + ]); + + if (isset($data['parent_id'])) { + $parent = GameComment::findOrFail($data['parent_id']); + abort_if($parent->game_id !== $game->id, 422, 'Parent comment does not belong to this game.'); + } + + $comment = $game->comments()->create([ + 'user_id' => auth()->id(), + 'body' => $data['body'], + 'parent_id' => $data['parent_id'] ?? null, + ]); + + $comment->load('user'); + + return response()->json([ + 'id' => $comment->id, + 'body' => $comment->body, + 'user' => ['name' => $comment->user->name, 'username' => $comment->user->username], + 'user_id' => $comment->user_id, + 'created_at' => $comment->created_at->diffForHumans(), + 'replies_count' => 0, + ], 201); + } + + public function destroy(GameComment $comment): JsonResponse + { + $this->authorize('delete', $comment); + $comment->delete(); + + return response()->json(['deleted' => true]); + } +} diff --git a/app/Http/Controllers/GameController.php b/app/Http/Controllers/GameController.php index f32216f..051f526 100644 --- a/app/Http/Controllers/GameController.php +++ b/app/Http/Controllers/GameController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Models\Game; +use App\Models\GameComment; use App\Models\Genre; use App\Models\Platform; use Illuminate\Http\Request; @@ -54,7 +55,13 @@ class GameController extends Controller $userLists = auth()->user()->lists()->get(['id', 'title']); } - $response = view('games.show', compact('game', 'userGame', 'userReview', 'userLists')); + $comments = $game->comments()->whereNull('parent_id') + ->with(['user', 'replies.user']) + ->latest() + ->limit(20) + ->get(); + + $response = view('games.show', compact('game', 'userGame', 'userReview', 'userLists', 'comments')); return auth()->check() ? $response : response($response)->header('Cache-Control', 'public, max-age=300'); diff --git a/app/Models/Game.php b/app/Models/Game.php index ede2bc2..5251060 100644 --- a/app/Models/Game.php +++ b/app/Models/Game.php @@ -62,6 +62,11 @@ class Game extends Model return $this->hasMany(Review::class)->latest(); } + public function comments(): HasMany + { + return $this->hasMany(GameComment::class); + } + public function toSearchableArray(): array { return [ diff --git a/app/Models/GameComment.php b/app/Models/GameComment.php new file mode 100644 index 0000000..c2c6c5c --- /dev/null +++ b/app/Models/GameComment.php @@ -0,0 +1,19 @@ +belongsTo(User::class); } + public function game(): BelongsTo { return $this->belongsTo(Game::class); } + public function replies(): HasMany { return $this->hasMany(GameComment::class, 'parent_id')->with('user')->latest(); } + + public function scopeTopLevel(Builder $query): Builder { return $query->whereNull('parent_id'); } +} diff --git a/app/Policies/GameCommentPolicy.php b/app/Policies/GameCommentPolicy.php new file mode 100644 index 0000000..a217cb3 --- /dev/null +++ b/app/Policies/GameCommentPolicy.php @@ -0,0 +1,14 @@ +id === $comment->user_id; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 0946796..6cc9d94 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -25,5 +25,6 @@ class AppServiceProvider extends ServiceProvider 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); + Gate::policy(\App\Models\GameComment::class, \App\Policies\GameCommentPolicy::class); } } diff --git a/database/migrations/2026_05_30_000001_create_game_comments_table.php b/database/migrations/2026_05_30_000001_create_game_comments_table.php new file mode 100644 index 0000000..478a566 --- /dev/null +++ b/database/migrations/2026_05_30_000001_create_game_comments_table.php @@ -0,0 +1,25 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignId('game_id')->constrained()->cascadeOnDelete(); + $table->foreignId('parent_id')->nullable()->constrained('game_comments')->cascadeOnDelete(); + $table->text('body'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('game_comments'); + } +}; diff --git a/resources/views/compare/show.blade.php b/resources/views/compare/show.blade.php new file mode 100644 index 0000000..a723233 --- /dev/null +++ b/resources/views/compare/show.blade.php @@ -0,0 +1,206 @@ +@extends('layouts.app') + +@section('title', 'Your Library vs ' . ($user->username ?? $user->name) . ' — GameLog') + +@section('content') +
+ Log in + to join the discussion. +
+No comments yet. Be the first!
+ + +