# GameLog Feature Batch Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add follow system, activity feed, notifications, sitemap, Scout search, and caching to the GameLog Laravel 11 app. **Architecture:** All features hook into existing User/Game/UserGame/Review models. Migrations for follows/activities already ran. The nav (layouts/app.blade.php) handles all global UI additions. Scout uses the `database` driver for dev (SQLite). **Tech Stack:** Laravel 13 + Tailwind + Alpine.js, SQLite dev, spatie/laravel-sitemap, laravel/scout --- ### Task 1: FollowController + Routes **Files:** - Create: `app/Http/Controllers/FollowController.php` - Modify: `routes/web.php` - [ ] Create FollowController with store/destroy returning JSON ```php user(); if ($me->id === $user->id) { return response()->json(['error' => 'Cannot follow yourself'], 422); } if (!$me->following()->where('following_id', $user->id)->exists()) { $me->following()->attach($user->id); $user->notifications()->create([ 'id' => \Illuminate\Support\Str::uuid(), 'type' => 'new_follower', 'data' => json_encode([ 'follower_id' => $me->id, 'follower_username' => $me->username, 'follower_name' => $me->name, ]), ]); } return response()->json([ 'following' => true, 'count' => $user->followers()->count(), ]); } public function destroy(User $user): JsonResponse { auth()->user()->following()->detach($user->id); return response()->json([ 'following' => false, 'count' => $user->followers()->count(), ]); } } ``` - [ ] Add routes to web.php (auth group) ```php Route::middleware('auth')->group(function () { Route::post('/users/{user:username}/follow', [\App\Http\Controllers\FollowController::class, 'store'])->name('users.follow'); Route::delete('/users/{user:username}/follow', [\App\Http\Controllers\FollowController::class, 'destroy'])->name('users.unfollow'); }); ``` - [ ] Commit: `git commit -m "feat: add FollowController and routes"` --- ### Task 2: Notifications Migration + Model **Files:** - Create: `database/migrations/2026_05_30_000001_create_notifications_table.php` - Create: `app/Models/Notification.php` - [ ] Create notifications migration ```php uuid('id')->primary(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->string('type', 100); $table->json('data'); $table->timestamp('read_at')->nullable(); $table->timestamp('created_at')->nullable(); }); } public function down(): void { Schema::dropIfExists('notifications'); } }; ``` - [ ] Create Notification model ```php 'array', 'read_at' => 'datetime', 'created_at' => 'datetime']; public function user(): BelongsTo { return $this->belongsTo(User::class); } } ``` - [ ] Run migration: `php artisan migrate` - [ ] Commit: `git commit -m "feat: add notifications table and model"` --- ### Task 3: Follow Button on users/show.blade.php **Files:** - Modify: `resources/views/users/show.blade.php` - [ ] Add Alpine.js follow button block after `
` (replaces the lone "View Library" button container) Replace the `
` block with: ```html
{{-- Follower / Following counts --}}
{{ $user->followers()->count() }}
Followers
{{ $user->following()->count() }}
Following
@auth @if(auth()->id() !== $user->id)
@endif @endauth View Library
``` - [ ] Commit: `git commit -m "feat: add follow button and follower counts to user profile"` --- ### Task 4: ActivityService + Hook into Models **Files:** - Create: `app/Services/ActivityService.php` - Modify: `app/Models/UserGame.php` - Modify: `app/Models/Review.php` - [ ] Create ActivityService (thin wrapper around Activity::log) ```php id, $type, $subject, $data); } } ``` - [ ] Add boot() hooks to UserGame model ```php use App\Services\ActivityService; protected static function boot(): void { parent::boot(); static::created(function (UserGame $ug) { ActivityService::log($ug->user, 'added_to_library', $ug->game, ['status' => $ug->status]); }); static::updated(function (UserGame $ug) { if ($ug->isDirty('rating') && $ug->rating) { ActivityService::log($ug->user, 'rated_game', $ug->game, ['rating' => $ug->rating]); } }); } ``` - [ ] Add to Review model boot() after the existing saved/deleted callbacks ```php use App\Services\ActivityService; // Inside the static::created callback (add this inside the boot method after existing code): static::created(function (Review $review) { ActivityService::log($review->user, 'reviewed_game', $review->game, ['rating' => $review->rating]); }); ``` - [ ] Commit: `git commit -m "feat: add ActivityService and log hooks on UserGame/Review"` --- ### Task 5: ActivityController + Routes + View **Files:** - Create: `app/Http/Controllers/ActivityController.php` - Modify: `routes/web.php` - Create: `resources/views/activity/index.blade.php` - [ ] Create ActivityController ```php user(); $followingIds = $user->following()->pluck('users.id')->push($user->id); $activities = Activity::with(['user', 'subject']) ->whereIn('user_id', $followingIds) ->latest('created_at') ->paginate(40); return view('activity.index', compact('activities')); } public function global() { $activities = Activity::with(['user', 'subject']) ->latest('created_at') ->paginate(40); return view('activity.index', compact('activities')); } } ``` - [ ] Add activity routes to web.php ```php Route::get('/activity', [\App\Http\Controllers\ActivityController::class, 'index'])->name('activity.index')->middleware('auth'); Route::get('/activity/global', [\App\Http\Controllers\ActivityController::class, 'global'])->name('activity.global'); ``` - [ ] Create activity/index.blade.php view (see full content below) - [ ] Commit: `git commit -m "feat: add activity feed controller, routes, and view"` --- ### Task 6: NotificationController + Routes + View **Files:** - Create: `app/Http/Controllers/NotificationController.php` - Modify: `routes/web.php` - Create: `resources/views/notifications/index.blade.php` - [ ] Create NotificationController ```php user() ->notifications() ->latest('created_at') ->limit(20) ->get(); auth()->user()->notifications()->whereNull('read_at')->update(['read_at' => now()]); return view('notifications.index', compact('notifications')); } public function markRead(Notification $notification) { abort_if($notification->user_id !== auth()->id(), 403); $notification->update(['read_at' => now()]); return response()->json(['ok' => true]); } } ``` - [ ] Add notification routes to web.php (auth group) ```php Route::get('/notifications', [\App\Http\Controllers\NotificationController::class, 'index'])->name('notifications.index')->middleware('auth'); Route::post('/notifications/{notification}/read', [\App\Http\Controllers\NotificationController::class, 'markRead'])->name('notifications.read')->middleware('auth'); ``` - [ ] Add to User model: `public function notifications() { return $this->hasMany(Notification::class)->latest('created_at'); }` - [ ] Create notifications/index.blade.php view - [ ] Add bell icon to nav in layouts/app.blade.php - [ ] Commit: `git commit -m "feat: add notifications controller, routes, view, and nav bell"` --- ### Task 7: Sitemap **Files:** - Run composer require - Create: `app/Console/Commands/GenerateSitemapCommand.php` - Modify: `routes/console.php` (Laravel 11 schedules here) - Modify: `routes/web.php` - Modify: `public/robots.txt` - [ ] Install spatie/laravel-sitemap: `composer require spatie/laravel-sitemap` - [ ] Create GenerateSitemapCommand - [ ] Add weekly schedule in routes/console.php - [ ] Add sitemap route in web.php - [ ] Update robots.txt - [ ] Commit: `git commit -m "feat: add sitemap generation command and route"` --- ### Task 8: Scout Full-Text Search **Files:** - Run composer require + vendor:publish - Modify: `app/Models/Game.php` - Modify: `app/Http/Controllers/GameController.php` - Modify: `.env` - [ ] Install Scout: `composer require laravel/scout && php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"` - [ ] Add SCOUT_DRIVER=database to .env - [ ] Add Searchable trait and toSearchableArray to Game model - [ ] Update GameController@search to use Scout - [ ] Commit: `git commit -m "feat: add Laravel Scout search to Game model"` --- ### Task 9: Performance Caching **Files:** - Modify: `app/Http/Controllers/HomeController.php` - Modify: `app/Http/Controllers/GameController.php` - Modify: `app/Models/Game.php` - [ ] Wrap HomeController queries in Cache::remember - [ ] Wrap GameController topRated/newReleases in Cache::remember with page key - [ ] Flush caches in Game::recalculateRating() - [ ] Add cache header to GameController@show response - [ ] Commit: `git commit -m "feat: add file cache to home/top-rated/new-releases queries"` ---