Story::published() ->with('author', 'tags') ->where('slug', $slug) ->firstOrFail() ); // Increment view counter (fire-and-forget, no cache invalidation needed) Story::where('id', $story->id)->increment('views'); // Related stories: shared tags → same author → newest $related = Cache::remember('stories:related:' . $story->id, 600, function () use ($story) { $tagIds = $story->tags->pluck('id'); $related = collect(); if ($tagIds->isNotEmpty()) { $related = Story::published() ->with('author', 'tags') ->whereHas('tags', fn ($q) => $q->whereIn('stories_tags.id', $tagIds)) ->where('id', '!=', $story->id) ->orderByDesc('published_at') ->limit(6) ->get(); } if ($related->count() < 3 && $story->author_id) { $byAuthor = Story::published() ->with('author', 'tags') ->where('author_id', $story->author_id) ->where('id', '!=', $story->id) ->whereNotIn('id', $related->pluck('id')) ->orderByDesc('published_at') ->limit(6 - $related->count()) ->get(); $related = $related->merge($byAuthor); } if ($related->count() < 3) { $newest = Story::published() ->with('author', 'tags') ->where('id', '!=', $story->id) ->whereNotIn('id', $related->pluck('id')) ->orderByDesc('published_at') ->limit(6 - $related->count()) ->get(); $related = $related->merge($newest); } return $related->take(6); }); return view('web.stories.show', [ 'story' => $story, 'related' => $related, 'page_title' => $story->title . ' — Skinbase Stories', 'page_meta_description' => $story->meta_excerpt, 'page_canonical' => $story->url, 'page_robots' => 'index,follow', 'breadcrumbs' => collect([ (object) ['name' => 'Stories', 'url' => '/stories'], (object) ['name' => $story->title, 'url' => $story->url], ]), ]); } }