select(['id', 'user_id', 'title', 'slug']) ->find($id); if (! $artwork) { return response()->json([ 'prev_id' => null, 'next_id' => null, 'prev_url' => null, 'next_url' => null, 'prev_slug' => null, 'next_slug' => null, ]); } $scope = Artwork::published() ->select(['id', 'title', 'slug']) ->where('user_id', $artwork->user_id); $prev = (clone $scope)->where('id', '<', $id)->orderByDesc('id')->first(); $next = (clone $scope)->where('id', '>', $id)->orderBy('id')->first(); // Infinite loop: wrap around when reaching the first or last artwork if (! $prev) { $prev = (clone $scope)->where('id', '!=', $id)->orderByDesc('id')->first(); } if (! $next) { $next = (clone $scope)->where('id', '!=', $id)->orderBy('id')->first(); } $prevSlug = $prev ? (Str::slug($prev->slug ?: $prev->title) ?: (string) $prev->id) : null; $nextSlug = $next ? (Str::slug($next->slug ?: $next->title) ?: (string) $next->id) : null; return response()->json([ 'prev_id' => $prev?->id, 'next_id' => $next?->id, 'prev_url' => $prev ? url('/art/' . $prev->id . '/' . $prevSlug) : null, 'next_url' => $next ? url('/art/' . $next->id . '/' . $nextSlug) : null, 'prev_slug' => $prevSlug, 'next_slug' => $nextSlug, ]); } /** * GET /api/artworks/{id}/page * * Returns full artwork resource by numeric ID for client-side (no-reload) navigation. */ public function pageData(int $id): JsonResponse { $artwork = Artwork::with(['user.profile', 'categories.contentType', 'categories.parent.contentType', 'tags', 'stats']) ->published() ->find($id); if (! $artwork) { return response()->json(['error' => 'Not found'], 404); } $resource = (new ArtworkResource($artwork))->toArray(request()); return response()->json($resource); } }